[SCM] Packaging for sardana branch, master, updated. debian/1.0.0_rc6-2-24-g452aa9a

Picca Frédéric-Emmanuel picca at debian.org
Thu Jul 25 20:21:31 UTC 2013


The following commit has been merged in the master branch:
commit 7111139f6da3051a96fc3bbe117059a7b7f5a4f2
Author: Picca Frédéric-Emmanuel <picca at debian.org>
Date:   Thu Jul 25 21:56:03 2013 +0200

    Imported Upstream version 1.2.0

diff --git a/PKG-INFO b/PKG-INFO
index 716a724..2eaff69 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -3,8 +3,8 @@ Name: sardana
 Version: 1.2.0
 Summary: Sardana is a generic program for control applications in large and small installations
 Home-page: http://packages.python.org/sardana
-Author: Tiago Coutinho
-Author-email: tcoutinho at cells.es
+Author: Carlos Pascual-Izarra
+Author-email: cpascual at cells.es
 License: LGPL
 Download-URL: http://pypi.python.org/packages/source/s/sardana
 Description: Produce a modular, high performance, robust and generic user environment 
@@ -31,9 +31,9 @@ Classifier: Programming Language :: Python
 Classifier: Topic :: Scientific/Engineering
 Classifier: Topic :: Software Development :: Libraries
 Requires: PyTango (>=7.2.3)
-Requires: taurus (>= 2.1)
+Requires: taurus (>= 3.1)
 Requires: lxml (>=2.1)
-Requires: ipython (>=0.10)
+Requires: ipython (>=0.10, !=0.11)
 Provides: sardana
 Provides: sardana.pool
 Provides: sardana.macroserver
diff --git a/doc/source/_static/sardana_sketch.png b/doc/source/_static/sardana_sketch.png
new file mode 100644
index 0000000..3cd2d13
Binary files /dev/null and b/doc/source/_static/sardana_sketch.png differ
diff --git a/doc/source/_static/snapshot11.png b/doc/source/_static/snapshot11.png
deleted file mode 100644
index faac55a..0000000
Binary files a/doc/source/_static/snapshot11.png and /dev/null differ
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 6b77b24..a6d3d79 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -290,7 +290,7 @@ intersphinx_mapping = {
     'http://ipython.org/ipython-doc/stable/' : None,
     'http://www.tango-controls.org/static/PyTango/latest/doc/html' : None,
     'http://www.tango-controls.org/static/taurus/latest/doc/html' : None,
-    'http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html' : None,
+    'http://pyqt.sourceforge.net/Docs/PyQt4/' : None,
     'http://matplotlib.sourceforge.net/' : None,
     'http://packages.python.org/guiqwt/' : None,
 }
diff --git a/doc/source/devel/api/sardana/macroserver/macros.rst b/doc/source/devel/api/sardana/macroserver/macros.rst
index 3be2752..f28f96c 100644
--- a/doc/source/devel/api/sardana/macroserver/macros.rst
+++ b/doc/source/devel/api/sardana/macroserver/macros.rst
@@ -67,7 +67,7 @@
     The step size for each motor is (start_pos-final_pos)/nr_interv.
     The number of data points collected will be nr_interv+1.
     Count time is given by time which if positive, specifies seconds and
-    if negative, specifies monitor counts."""
+    if negative, specifies monitor counts.
     
 
 .. class:: scan.ascan
@@ -95,8 +95,9 @@
     will be nr_interv+1. Count time is given by time which if positive,
     specifies seconds and if negative, specifies monitor counts. 
     
-.. autoclass:: demo.clear_sar_demo
+.. class:: demo.clear_sar_demo
 
+    Undoes changes done with sar_demo
 
 .. class:: expert.commit_ctrllib
 
@@ -211,7 +212,7 @@
     The step size for each motor is (start_pos-final_pos)/nr_interv.
     The number of data points collected will be nr_interv+1.
     Count time is given by time which if positive, specifies seconds and
-    if negative, specifies monitor counts."""
+    if negative, specifies monitor counts.
     
 
 .. class:: scan.dscan
@@ -375,8 +376,9 @@
 
     Lists all existing motors
     
-.. autoclass:: env.lsvo
+.. class:: env.lsvo
 
+    Lists the view options
 
 .. class:: mca.mca_start
 
@@ -404,7 +406,9 @@
 
     2d grid scan. scans continuous
     
-.. autoclass:: standard.mstate
+.. class:: standard.mstate
+
+    Prints the state of a motor
 
 
 .. class:: standard.mv
@@ -469,8 +473,9 @@
 
     Logs a new record into the message report system (if active)
     
-.. autoclass:: demo.sar_demo
+.. class:: demo.sar_demo
 
+   Sets up a demo environment. It creates many elements for testing
 
 .. class:: expert.sar_info
 
diff --git a/doc/source/devel/guide_coding.rst b/doc/source/devel/guide_coding.rst
index dddc570..7b56ef6 100644
--- a/doc/source/devel/guide_coding.rst
+++ b/doc/source/devel/guide_coding.rst
@@ -14,21 +14,25 @@ development of sardana. So if you want to help out, read on!
 How to contribute to sardana
 ----------------------------
 
-Sardana development is done using SVN. Because sardana is part of Tango_, it uses
-its `tango-cs sourceforge project <https://sourceforge.net/projects/tango-cs/>`_
-to host the source code. This makes it easy for people to contribute to the 
-development of sardana.
+Sardana development is managed with the `Sardana sourceforge project
+<https://sourceforge.net/projects/sardana/>`_. Until release 1.2, a svn
+repository has been used to host the source code. From then on, the version
+control will be moved to a git repository.
 
 How to checkout sardana from SVN
 --------------------------------
 
+.. warning:: These instructions will become obsolete as soon as Sardana 1.2 
+             is released because the code development will be moved from SVN to
+             Git. Updated instructions for using Git will be posted ASAP.
+
 **For read-only**::
 
-    svn co https://tango-cs.svn.sourceforge.net/svnroot/tango-cs/share/Sardana/trunk Sardana
+    svn co https://svn.code.sf.net/p/sardana/code/trunk Sardana
 
 **To being able to commit**::
 
-    svn co https://<user name>@tango-cs.svn.sourceforge.net/svnroot/tango-cs/share/Sardana/trunk Sardana
+    svn co https://<user name>@svn.code.sf.net/p/sardana/code/trunk Sardana
 
 .. note::
     
diff --git a/doc/source/users/faq.rst b/doc/source/users/faq.rst
index 7079da1..ac8c1ee 100644
--- a/doc/source/users/faq.rst
+++ b/doc/source/users/faq.rst
@@ -1,18 +1,28 @@
 
 .. _sardana-faq:
 
+
+.. todo:: The FAQ is work-in-progress. Many answers need polishing and mostly
+          links need to be added
+
+
 ===
 FAQ
 ===
 
 What is the Sardana SCADA_ and how do I get an overview over the different components?
 ---------------------------------------------------------------------------------------
-An overview over the different Sardana components can be found here **<LINK>**.
+An overview over the different Sardana components is shown in the following figure:
+
+.. image:: /_static/sardana_sketch.png
+  :align: center 
+  :width: 500
+
 The basic Sardana SCADA_ philosophy can be found :ref:`here <sardana-overview>`.
 
 How do I install Sardana?
 -------------------------
-The Sardan SCADA_ system consists of different components which have to be
+The Sardana SCADA_ system consists of different components which have to be
 installed:
     
     * Tango_: The control system middleware and tools
@@ -26,7 +36,7 @@ The complete sardana installation instructions can be found
 How to work with Taurus_ :term:`GUI`?
 -------------------------------------
 A user documentation for the Taurus_ :term:`GUI` application can be found
-`here <http://packages.python.org/taurus/>`_.
+`here <http://packages.python.org/taurus/>`__.
 
 How to produce your own Taurus_ :term:`GUI` panel?
 --------------------------------------------------
@@ -34,10 +44,10 @@ How to produce your own Taurus_ :term:`GUI` panel?
 The basic philosophy of Taurus_ :term:`GUI` is to provide automatic
 :term:`GUI` s which are automatically replaced by more and more specific
 :term:`GUI` s if these are found.
-The documentation how to create a generic panel which can be filled via
-selection and cut and paste can be found here **<LINK>**.
-A more advanced usage is to create a Taurus_ widget and ingrate it into the
-application. Documentation for this approach can be found here **<LINK>**.
+
+Refer to the `user documentation on TaurusGUI <http://www.tango-
+controls.org/static/taurus/latest/doc/html/users/ui/taurusgui.html>`_  for more
+details on how to work with panels
 
 How to call procedures?
 -----------------------
@@ -46,12 +56,11 @@ The execution can be started from either:
 
     * *spock* offers a command line interface with commands very similar to SPEC_.
       It is documented :ref:`here <sardana-spock>`.
-    * Procedures can also be executed with a  macro executor :term:`GUI`.
-      This :term:`GUI` interface offering input from the keyboard and the generic
-      widgets is documented here **<LINK>**. A macro can be associated with a
-      specific :term:`GUI` interface. This mechanism is documented here **<LINK>**.
+    * Procedures can also be executed with from a :term:`GUI`. Taurus provides
+      `generic widgets for macro execution <http://www.tango-
+      controls.org/static/taurus/latest/doc/html/users/ui/macros/>`__.
     * Procedures can also be executed in specific :term:`GUI` s and specific Taurus_
-      widgets. The :term:`API` to execute macros from this python code is documented
+      widgets. The :term:`API` to execute macros from python code is documented
       here **<LINK>**.
 
 How to write procedures?
@@ -64,16 +73,15 @@ Macro writers might also find the following documentation interesting:
     * In addition of the strength of the python language macro writers can
       interface with common elements (motors, counters) , call other macros
       and use many utilities provided. The macro :term:`API` can be found 
-      :class:`here <MacroServer.macro.Macro>`.
+      :ref:`here <sardana-macro-api>`.
     * Documentation how to document your macros can be found 
       :ref:`here <sardana-macros-howto>`
 
 How to write scan procedures?
 -----------------------------
-A very common type of procedure is the *ascan* where some quantity is 
-varied while recording some other quantities. Many common types of 
-general-purpose scans procedures are already available in Sardana **<LINK>**,
-and a simple :term:`API` is provided for writing more specific ones.
+A very common type of procedure is the *scan* where some quantity is 
+varied while recording some other quantities. See the documentation on the 
+:ref:`Sardana Scan API <sardana-macros-scanframework>`
 
 How to adapt SARDANA to your own hardware?
 ------------------------------------------
diff --git a/doc/source/users/getting_started/installing.rst b/doc/source/users/getting_started/installing.rst
index 203cb7f..282a361 100644
--- a/doc/source/users/getting_started/installing.rst
+++ b/doc/source/users/getting_started/installing.rst
@@ -14,8 +14,8 @@ Installing
 
         #. Download the sardana source code:
             #. from latest stable version of `sardana <http://pypi.python.org/pypi/sardana>`_ (|version|)
-            #. from `SVN snapshot <http://tango-cs.svn.sourceforge.net/viewvc/tango-cs/share/Sardana/trunk/?view=tar>`_
-
+            #. from `SVN snapshot <https://sourceforge.net/p/sardana/code/HEAD/tarball?path=/trunk>`_
+            
         #. Extract the downloaded tar.gz into a temporary directory
 
         #. type [2]_ ::
@@ -45,7 +45,7 @@ necessary to run sardana on your windows machine
 
     #. from scratch:
         #. Download and install `PyQwt`_ < 6.0 from `PyQwt downdoad page <http://pyqwt.sourceforge.net/download.html>`_
-            #. Download and install compatible python with from link in the same `PyQwt`_ page
+            #. Download and install compatible python from link in the same `PyQwt`_ page
             #. Download and install compatible `numpy`_ from link in the same `PyQwt`_ page.
             #. Download and install compatible `PyQt`_ from link in the same `PyQwt`_ page.
 
@@ -58,6 +58,10 @@ necessary to run sardana on your windows machine
 Working from SVN
 ----------------
 
+.. warning:: These instructions will become obsolete as soon as Sardana 1.2 
+             is released because the code development will be moved from SVN to
+             Git. Updated instructions for using Git will be posted ASAP.
+
 You can checkout sardana from SVN from the following location::
 
     svn co http://svn.code.sf.net/p/sardana/code/trunk Sardana
diff --git a/doc/source/users/getting_started/running_cli.rst b/doc/source/users/getting_started/running_cli.rst
index 1a5ff09..d21e2f7 100644
--- a/doc/source/users/getting_started/running_cli.rst
+++ b/doc/source/users/getting_started/running_cli.rst
@@ -40,7 +40,7 @@ you should get an output like this::
 
     Door_lab-01_1 [1]: 
 
-That't it! You now have a running sardana client. Still not impressed, I see!
+That's it! You now have a running sardana client. Still not impressed, I see!
 The next chapter describes how to start adding new elements to your sardana
 environment.
 
diff --git a/doc/source/users/getting_started/running_server.rst b/doc/source/users/getting_started/running_server.rst
index 50382d8..e1b4ffc 100644
--- a/doc/source/users/getting_started/running_server.rst
+++ b/doc/source/users/getting_started/running_server.rst
@@ -47,7 +47,7 @@ Running Pool and MacroServer tango servers separately
 
 .. note::
 
-    You should only read this chapter if you are if you have Tango <= 7.2.6
+    You should only read this chapter if you have Tango <= 7.2.6
     without all patches applied. If you do, please follow in instructions from
     :ref:`sardana-getting-started-running-server` instead.
 
diff --git a/doc/source/users/overview.rst b/doc/source/users/overview.rst
index 53f983f..7f31ed7 100644
--- a/doc/source/users/overview.rst
+++ b/doc/source/users/overview.rst
@@ -209,7 +209,7 @@ Symbolic Sketch
 I would like to end this summary with a symbolic sketch of the different
 subsystems in Sardana.
 
-.. image:: /_static/snapshot11.png
+.. image:: /_static/sardana_sketch.png
   :align: center 
   :width: 500
 
diff --git a/doc/source/users/scan.rst b/doc/source/users/scan.rst
index 79a6c32..3996ac0 100644
--- a/doc/source/users/scan.rst
+++ b/doc/source/users/scan.rst
@@ -69,8 +69,8 @@ wait for motors to accelerate and decelerate between acquisitions.
 .. note:: The synchronization of movement and acquisition can be done via
    hardware or via software. Currently Sardana only provides an interface for
    software-synchronized continuous scans. An API abstracting the specificities
-   of hardware-synchronized systems has not been yet implemented, and therefore
-   it cannot be supported by Sardana yet.
+   of hardware-synchronized systems is being implemented too but it is not yet
+   available for production.
 
 The (software-synchronized) continuous scans introduce some constraints and
 issues that should be considered.
diff --git a/scripts/h5toascii b/scripts/h5toascii
index ddb3ef8..4a42be1 100755
--- a/scripts/h5toascii
+++ b/scripts/h5toascii
@@ -1,5 +1,28 @@
 #!/usr/bin/env python
 
+##############################################################################
+##
+## This file is part of Sardana
+##
+## http://www.tango-controls.org/static/sardana/latest/doc/html/index.html
+##
+## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
+## 
+## Sardana is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+## 
+## Sardana is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU Lesser General Public License for more details.
+## 
+## You should have received a copy of the GNU Lesser General Public License
+## along with Sardana.  If not, see <http://www.gnu.org/licenses/>.
+##
+##############################################################################
+
 '''
 Script for extracting "scan tables" from Sardana NeXus files into an tab-separated ASCII tables.
 When data from multiple entries are extracted from a file, each table is stored in a different file.
diff --git a/scripts/h5tospec b/scripts/h5tospec
index 0dcb85a..724ada0 100755
--- a/scripts/h5tospec
+++ b/scripts/h5tospec
@@ -1,5 +1,28 @@
 #!/usr/bin/env python
 
+##############################################################################
+##
+## This file is part of Sardana
+##
+## http://www.tango-controls.org/static/sardana/latest/doc/html/index.html
+##
+## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
+## 
+## Sardana is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+## 
+## Sardana is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU Lesser General Public License for more details.
+## 
+## You should have received a copy of the GNU Lesser General Public License
+## along with Sardana.  If not, see <http://www.gnu.org/licenses/>.
+##
+##############################################################################
+
 '''
 Script for extracting "scans" from Sardana NeXus files into Sardana Spec Files.
 
@@ -33,7 +56,7 @@ def _getMovables(mystr):
     _a.pop(0)
     for i in _a:
         if re.match('[^-_0-9\.\+\\n]',i):
-           _m.append(i)
+            _m.append(i)
     return _m 
 
 def getMetadata(fd, entry):
diff --git a/scripts/spectoascii b/scripts/spectoascii
new file mode 100755
index 0000000..875a493
--- /dev/null
+++ b/scripts/spectoascii
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+##############################################################################
+##
+## This file is part of Sardana
+##
+## http://www.tango-controls.org/static/sardana/latest/doc/html/index.html
+##
+## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
+## 
+## Sardana is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+## 
+## Sardana is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU Lesser General Public License for more details.
+## 
+## You should have received a copy of the GNU Lesser General Public License
+## along with Sardana.  If not, see <http://www.gnu.org/licenses/>.
+##
+##############################################################################
+
+'''
+Script for extracting "scan tables" from Sardana Spec files into single-scan tab-separated ASCII tables.
+When data from multiple scans are extracted from a file, each table is stored in a different file.
+The output files for a given input file are stored in a directory whose name matches the input scan ID
+
+Usage:
+python spectoascii.py <specfile> [<scan_ID_1> [<scan_ID_2>] ...]  
+
+If no scan IDs are provided, all scans from the given Spec file will be extracted.
+
+Note that this is not a generic Spec conversor. Spec files created by means other than the Sardana 
+SPEC_FileRecorder may not be converted properly.
+ 
+'''
+
+
+import sys,os
+
+def main():
+    if len(sys.argv) > 1:
+        fname = sys.argv[1]
+    else:
+        print "Usage:\nspectoascii <specfile> [<scan_ID_1> [<scan_ID_2>] ...] "
+        sys.exit(1)
+        
+    scanids = sys.argv[2:] or None
+    
+    f = open(fname,'r')
+    s = f.read()
+    f.close()
+    
+    dirname,ext = os.path.splitext(fname)
+    try:
+        os.makedirs(dirname)
+    except:
+        print 'Cannot create dir "%s". Skipping.'%dirname
+    
+    for scan in s.split('\n\n'):
+        lines = scan.split('\n')
+        if not lines[0].startswith('#S '):
+            continue
+        scan_name = lines[0].split()[1]
+        if scanids is None or scan_name in scanids: #extract only selected ids
+            ofname = os.path.join(dirname,"%s.dat"%scan_name)
+            print "Extracting %s:%s to %s"%(fname,scan_name,ofname)
+            try:
+                ofile = open(ofname,'w')
+            except:
+                print 'Cannot create file "%s". Skipping.'%ofname
+                continue
+            for line in lines[1:]:
+                if line.startswith('#L '):
+                    ofile.write('\t'.join(line[3:].split('  '))+'\n')
+                elif line.startswith('#'):
+                    continue
+                else:
+                    ofile.write('\t'.join(line.split())+'\n')
+            ofile.close()
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 232a4d4..915461c 100644
--- a/setup.py
+++ b/setup.py
@@ -36,15 +36,14 @@ from distutils.core import setup, Extension, Command
 from distutils.dist import Distribution
 from distutils.command.build import build as dftbuild
 from distutils.command.install import install as dftinstall
-
+from distutils.version import StrictVersion as V
 import distutils.sysconfig
 
 try:
     import sphinx
     import sphinx.util.console
     sphinx.util.console.color_terminal = lambda : False
-    sphinx_v = tuple(map(int, sphinx.__version__.split(".",3)))
-    if sphinx_v < (1,0,0):
+    if V(sphinx.__version__) < V("1.0.0"):
         sphinx = None
 except:
     sphinx = None
@@ -245,6 +244,8 @@ def main():
     Release = get_release_info()
 
     author = Release.authors['Tiago']
+    maintainer = Release.authors['Pascual-Izarra']
+    
     package_name = Release.name
 
     package_dir = { 'sardana' : abspath('src', 'sardana') }
@@ -290,9 +291,9 @@ def main():
 
     requires = [ 
         'PyTango (>=7.2.3)',
-        'taurus (>= 2.1)',
+        'taurus (>= 3.1)',
         'lxml (>=2.1)',
-        'ipython (>=0.10)'
+        'ipython (>=0.10, !=0.11)'
     ]
      
     package_data = { 
@@ -328,6 +329,8 @@ def main():
           long_description = Release.long_description,
           author           = author[0],
           author_email     = author[1],
+          maintainer       = maintainer[0],
+          maintainer_email = maintainer[1],
           url              = Release.url,
           download_url     = Release.download_url,
           platforms        = Release.platforms,
diff --git a/src/sardana/macroserver/macro.py b/src/sardana/macroserver/macro.py
index b112a4c..c730d3d 100644
--- a/src/sardana/macroserver/macro.py
+++ b/src/sardana/macroserver/macro.py
@@ -1618,7 +1618,7 @@ class Macro(Logger):
     #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
 
     @mAPI
-    def getEnv(self, key, macro_name=None, door_name=None):
+    def getEnv(self, key=None, macro_name=None, door_name=None):
         """**Macro API**. Gets the local environment matching the given
         parameters:
 
diff --git a/src/sardana/macroserver/macros/demo.py b/src/sardana/macroserver/macros/demo.py
index 5a4130f..5275b1f 100644
--- a/src/sardana/macroserver/macros/demo.py
+++ b/src/sardana/macroserver/macros/demo.py
@@ -52,6 +52,7 @@ def get_free_names(db, prefix, nb, start_at=1):
 
 @macro()
 def clear_sar_demo(self):
+    """Undoes changes done with sar_demo"""
     try:
         SAR_DEMO = self.getEnv(_ENV)
     except:
@@ -76,6 +77,7 @@ def clear_sar_demo(self):
     
 @macro()
 def sar_demo(self):
+    """Sets up a demo environment. It creates many elements for testing"""
     
     try:
         SAR_DEMO = self.getEnv(_ENV)
diff --git a/src/sardana/macroserver/macros/env.py b/src/sardana/macroserver/macros/env.py
index a6edd7a..35244db 100644
--- a/src/sardana/macroserver/macros/env.py
+++ b/src/sardana/macroserver/macros/env.py
@@ -62,7 +62,7 @@ class dumpenv(Macro):
 
 
 class lsvo(Macro):
-
+    """Lists the view options"""
     def run(self):
         vo = self.getViewOptions()
         out = List(['View option', 'Value'])
@@ -121,12 +121,11 @@ class lsenv(Macro):
         else:
             # list the environment for the current door for the given macros
             out = List(['Macro', 'Name', 'Value', 'Type'])
-            for macro_name in macro_list:
-                macro_env = m.getMacroClass(macro_name).env
-                env = m.getDoorMacroEnv(door_name, macro_name, macro_env)
+            for macro in macro_list:
+                env = self.getEnv(key=None, macro_name=macro.name)
                 for k, v in env.iteritems():
                     type_name = type(v).__name__
-                    out.appendRow([ macro_name, k, self.reprValue(v), type_name ])
+                    out.appendRow([ macro.name, k, self.reprValue(v), type_name ])
 
         for line in out.genOutput():
             self.output(line)
diff --git a/src/sardana/macroserver/macros/examples/hooks.py b/src/sardana/macroserver/macros/examples/hooks.py
index cb2992e..9789fdd 100644
--- a/src/sardana/macroserver/macros/examples/hooks.py
+++ b/src/sardana/macroserver/macros/examples/hooks.py
@@ -99,7 +99,7 @@ class hooked_scan(Macro):
     demonstration purposes"""
     
     param_def = [
-       ['motor',      Type.Motor,   None, 'Motor to move'],
+       ['motor',      Type.Moveable,None, 'Motor to move'],
        ['start_pos',  Type.Float,   None, 'Scan start position'],
        ['final_pos',  Type.Float,   None, 'Scan final position'],
        ['nr_interv',  Type.Integer, None, 'Number of scan intervals'],
@@ -114,9 +114,23 @@ class hooked_scan(Macro):
     def hook3(self):
         self.info("\thook3 execution")
     
+    def hook4(self):
+        self.info("\thook4 execution")
+    
+    def hook5(self):
+        self.info("\thook5 execution")
+    
+    def hook6(self):
+        self.info("\thook6 execution")
+    
     def run(self, motor, start_pos, final_pos, nr_interv, integ_time):
         ascan, pars = self.createMacro("ascan",motor, start_pos, final_pos, nr_interv, integ_time)
-        ascan.hooks = [ (self.hook1, ["pre-acq"]), (self.hook2, ["pre-acq","post-acq","pre-move", "post-move","aaaa"]), self.hook3 ]
+        ascan.hooks = [ (self.hook1, ["pre-acq"]), 
+                       (self.hook2, ["pre-acq","post-acq","pre-move", "post-move","aaaa"]), 
+                       self.hook3,
+                       (self.hook4, ["pre-scan"]),
+                       (self.hook5, ["pre-scan", "post-scan"]),
+                       (self.hook6, ["post-step"])]
         self.runMacro(ascan)
 
 
diff --git a/src/sardana/macroserver/macros/examples/scans.py b/src/sardana/macroserver/macros/examples/scans.py
index 2f2a103..57fbcc0 100644
--- a/src/sardana/macroserver/macros/examples/scans.py
+++ b/src/sardana/macroserver/macros/examples/scans.py
@@ -564,4 +564,42 @@ class ascanc_demo(Macro):
     def run(self,*args):
         for step in self._gScan.step_scan():
             yield step
+
+
+
+class ascan_with_addcustomdata(ascan_demo):
+    '''
+    example of an ascan-like macro where we demonstrate how to pass custom data to the data handler.
+    This is an extension of the ascan_demo macro. Wemake several calls to `:meth:DataHandler.addCustomData`
+    exemplifying different features.
+    At least the following recorders will act on custom data: 
+      - OutputRecorder (this will ignore array data)
+      - NXscan_FileRecorder
+      - SPEC_FileRecorder (this will ignore array data)
+    '''
+    def run(self, motor, start_pos, final_pos, nr_interv, integ_time, **opts):
+        #we get the datahandler
+        dh = self._gScan._data_handler
+        #at this point the entry name is not yet set, so we give it explicitly (otherwise it would default to "entry")
+        dh.addCustomData('Hello world1', 'dummyChar1', nxpath='/custom_entry:NXentry/customdata:NXcollection')
+        #this is the normal scan loop
+        for step in self._gScan.step_scan():
+            yield step
+        #the entry number is known and the default nxpath is used "/<currententry>/custom_data") if none given
+        dh.addCustomData('Hello world1', 'dummyChar1')
+        #you can pass arrays (but not all recorders will handle them) 
+        dh.addCustomData(range(10), 'dummyArray1') 
+        #you can pass a custom nxpath *relative* to the current entry
+        dh.addCustomData('Hello world2', 'dummyChar2', nxpath='sample:NXsample') 
+        
+        #calculate a linear fit to the timestamps VS motor positions and store it 
+        x = [r.data [motor.getName()] for r in self.data.records]
+        y = [r.data['timestamp'] for r in self.data.records]
+        fitted_y = numpy.polyval(numpy.polyfit(x,y,1), x)
+        dh.addCustomData(fitted_y, 'fittedtime', nxpath='measurement:NXcollection')
+        
+        #as a bonus, plot the fit
+        self.pyplot.plot(x, y, 'ro')
+        self.pyplot.plot(x, fitted_y, 'b-')
+
     
\ No newline at end of file
diff --git a/src/sardana/macroserver/macros/scan.py b/src/sardana/macroserver/macros/scan.py
index 77cb04a..f38e1be 100644
--- a/src/sardana/macroserver/macros/scan.py
+++ b/src/sardana/macroserver/macros/scan.py
@@ -39,6 +39,7 @@ __all__ = ["a2scan", "a3scan", "a4scan", "amultiscan", "aNscan", "ascan",
            "a2scanc", "a3scanc", "a4scanc", "ascanc",
            "d2scanc", "d3scanc", "d4scanc", "dNScanc", "dscanc",
            "meshc", 
+           "a2scanct", "a3scanct", "a4scanct", "ascanct",  
            "scanhist", "getCallable", "UNCONSTRAINED"]
 
 __docformat__ = 'restructuredtext'
@@ -57,11 +58,13 @@ from sardana.macroserver.msexception import UnknownEnv
 from sardana.macroserver.macro import *
 from sardana.macroserver.scan import *
 from sardana.util.motion import Motor, MotionPath
+from sardana.util.tree import BranchNode, LeafNode, Tree
 
 UNCONSTRAINED="unconstrained"
 
 StepMode = 's'
-ContinuousMode = 'c'
+ContinuousMode = 'c' #TODO: change it to be more verbose e.g. ContinuousSwMode
+ContinuousHwTimeMode = 'ct'
 HybridMode = 'h'
 
 def getCallable(repr):
@@ -106,8 +109,9 @@ class aNscan(Hookable):
         constrains = [getCallable(cns) for cns in opts.get('constrains',[UNCONSTRAINED])]
         extrainfodesc = opts.get('extrainfodesc',[])
 
-        self.pre_scan_hooks = self.getHooks('pre-scan')
-        self.post_scan_hooks = self.getHooks('post-scan')
+        #Hooks are not always set at this point. We will call getHooks later on in the scan_loop
+        #self.pre_scan_hooks = self.getHooks('pre-scan')
+        #self.post_scan_hooks = self.getHooks('post-scan'
           
         if mode == StepMode:
             self.nr_interv = scan_length
@@ -115,12 +119,28 @@ class aNscan(Hookable):
             self.interv_sizes = ( self.finals - self.starts) / self.nr_interv
             self.name = opts.get('name','a%iscan'%self.N)
             self._gScan = SScan(self, self._stepGenerator, moveables, env, constrains, extrainfodesc)
-        elif mode == ContinuousMode:
-            self.slow_down = scan_length
-            self.nr_waypoints = 2 #aNscans will only have two waypoints (the start and the final positions)
-            self.way_lengths = ( self.finals - self.starts) / (self.nr_waypoints -1)
-            self.name = opts.get('name','a%iscanc'%self.N)
-            self._gScan = CScan(self, self._waypoint_generator, self._period_generator, moveables, env, constrains, extrainfodesc)
+        elif mode in [ContinuousMode, ContinuousHwTimeMode]:
+            #TODO: probably not 100% correct, 
+            #      the idea is to allow passing a list of waypoints
+            if isinstance(endlist[0],list):
+                self.waypoints = self.finals
+            else:
+                self.waypoints = [self.finals]
+            self.nr_waypoints = len(self.waypoints)
+            if mode == ContinuousMode: 
+                self.slow_down = scan_length
+                self.nr_waypoints = 2 #aNscans will only have two waypoints (the start and the final positions)
+                self.way_lengths = ( self.finals - self.starts) / (self.nr_waypoints -1)
+                self.name = opts.get('name','a%iscanc'%self.N)
+                self._gScan = CSScan(self, self._waypoint_generator, self._period_generator, moveables, env, constrains, extrainfodesc)
+            elif mode == ContinuousHwTimeMode:
+                self.nr_of_points = scan_length
+                self.name = opts.get('name','a%iscanct'%self.N)
+                self._gScan = CTScan(self, self._waypoint_generator_hwtime, 
+                                           moveables, 
+                                           env, 
+                                           constrains, 
+                                           extrainfodesc)
         elif mode == HybridMode:
             self.nr_interv = scan_length
             self.nr_points = self.nr_interv+1
@@ -155,7 +175,63 @@ class aNscan(Hookable):
             step["positions"] = self.starts + point_no * self.way_lengths
             step["waypoint_id"] = point_no
             yield step
-    
+            
+    def _waypoint_generator_hwtime(self):
+        #TODO: remove starts
+        def calculate_positions(moveable_node, start, end):
+            '''Function to calculate starting and ending positions on the physical 
+            motors level. 
+            :param moveable_node: (BaseNode) node representing a moveable. 
+                                  Can be a BranchNode representing a PseudoMotor,
+                                  or a LeafNode representing a PhysicalMotor).
+            :param start: (float) starting position of the moveable
+            :param end: (float) ending position of the moveable
+        
+            :return: (list<(float,float)>) a list of tuples comprising starting
+                     and ending positions. List order is important and preserved.'''
+            start_positions = []
+            end_positions = []
+            if isinstance(moveable_node, BranchNode):
+                pseudo_node = moveable_node
+                moveable = pseudo_node.data
+                moveable_nodes = moveable_node.children
+                starts = moveable.calcPhysical(start)
+                ends = moveable.calcPhysical(end)
+                for moveable_node, start, end in zip(moveable_nodes, starts,
+                                                        ends):
+                    _start_positions, _end_positions = calculate_positions(
+                                                                moveable_node, 
+                                                                start, end)
+                    start_positions += _start_positions
+                    end_positions += _end_positions
+            else:
+                start_positions = [start]
+                end_positions = [end]
+                
+            return start_positions, end_positions
+        
+        #CScan in its constructor populates a list of data structures - trees. 
+        #Each tree represent one Moveables with its hierarchy of inferior moveables.                                                
+        moveables_trees = self._gScan.get_moveables_trees()  
+        step = {}
+        step["pre-move-hooks"] = self.getHooks('pre-move')
+        step["post-move-hooks"] = self.getHooks('post-move')
+        step["check_func"] = []
+        step["active_time"] = self.nr_of_points * self.integ_time
+        step["positions"] = []
+        step["start_positions"] = []
+        starts = self.starts
+        for point_no, waypoint in enumerate(self.waypoints):
+            for start, end, moveable_tree in zip(starts, waypoint, 
+                                                 moveables_trees):
+                moveable_root = moveable_tree.root()
+                start_positions, end_positions = calculate_positions(moveable_root, start, end)
+                step["start_positions"] += start_positions
+                step["positions"] += end_positions
+                step["waypoint_id"] = point_no
+                starts = waypoint
+            yield step
+        
     def _period_generator(self):
         step = {}
         step["integ_time"] =  self.integ_time
@@ -202,6 +278,7 @@ class aNscan(Hookable):
 
         elif mode == ContinuousMode:
             total_time = gScan.waypoint_estimation()
+        #TODO: add time estimation for ContinuousHwTimeMode
         return total_time
                         
     def getIntervalEstimation(self):
@@ -553,8 +630,9 @@ class mesh(Macro,Hookable):
         env=opts.get('env',{})
         constrains=[getCallable(cns) for cns in opts.get('constrains',[UNCONSTRAINED])]
     
-        self.pre_scan_hooks = self.getHooks('pre-scan')
-        self.post_scan_hooks = self.getHooks('post-scan')
+        #Hooks are not always set at this point. We will call getHooks later on in the scan_loop
+        #self.pre_scan_hooks = self.getHooks('pre-scan')
+        #self.post_scan_hooks = self.getHooks('post-scan')
 
         self._gScan=SScan(self, generator, moveables, env, constrains)
 
@@ -580,7 +658,7 @@ class mesh(Macro,Hookable):
                 space = m1_space_inv
             for m1pos in space:
                 step["positions"] = numpy.array([m1pos,m2pos])
-                step["point_id"]= point_no  #@TODO: maybe another ID would be better? (e.g. "(A,B)")
+                step["point_id"]= point_no  #TODO: maybe another ID would be better? (e.g. "(A,B)")
                 point_no+=1
                 yield step
     
@@ -670,8 +748,10 @@ class fscan(Macro,Hookable):
         env=opts.get('env',{})
         constrains=[getCallable(cns) for cns in opts.get('constrains',[UNCONSTRAINED])]
         
-        self.pre_scan_hooks = self.getHooks('pre-scan')
-        self.post_scan_hooks = self.getHooks('post-scan')
+        #Hooks are not always set at this point. We will call getHooks later on in the scan_loop
+        #self.pre_scan_hooks = self.getHooks('pre-scan')
+        #self.post_scan_hooks = self.getHooks('post-scan'
+        
         self._gScan=SScan(self, generator, moveables, env, constrains)
         
     def _generator(self):
@@ -1020,8 +1100,9 @@ class meshc(Macro,Hookable):
         constrains=[getCallable(cns) for cns in opts.get('constrains',[UNCONSTRAINED])]
         extrainfodesc = opts.get('extrainfodesc',[])
     
-        self.pre_scan_hooks = self.getHooks('pre-scan')
-        self.post_scan_hooks = self.getHooks('post-scan')
+        #Hooks are not always set at this point. We will call getHooks later on in the scan_loop
+        #self.pre_scan_hooks = self.getHooks('pre-scan')
+        #self.post_scan_hooks = self.getHooks('post-scan'
 
         self._gScan = CScan(self, self._waypoint_generator, self._period_generator, moveables, env, constrains, extrainfodesc)
         self._gScan.frozen_motors = [m2]
@@ -1073,3 +1154,210 @@ class meshc(Macro,Hookable):
     @property
     def data(self):
         return self._gScan.data
+    
+class ascanct(aNscan, Macro):
+    '''Continuous scan controlled by hardware trigger signals. A sequence of 
+    trigger pulses is programmed by time. The scan active time is calculated from
+    nr_of_points * point time. It corresponds to the time while all the involved 
+    in the scan moveables are at the constant velocity. Experimental channels are
+    configured to acquire during acquistion time calculated from acq_time [%] of  
+    the point_time. 
+    Temporary solution used to configure trigger device (pulse train generator): 
+    "TriggerDevices" evironment variable must be set to Ni660X device name 
+    or any other Tango device name implementing:
+    +) following attributes: 
+    - InitialDelayTime [s] - delay time from calling Start to generating first pulse 
+    - HighTime [s] - time interval while signal will maintain its high state
+    - LowTime [s] - time interval while signal will maintain its low state
+    - SampPerChan - nr of pulses to be generated
+    - IdleState - state (high or low) which signal will take after the Start command
+      and which will maintain during the InitialDelayTime.  
+    +) following commands:
+    - Start 
+    - Stop)'''
+
+    hints = {'scan' : 'ascanct', 'allowsHooks': ('pre-configuration', 
+                                                 'post-configuration', 
+                                                 'pre-start', 
+                                                 'pre-cleanup', 
+                                                 'post-cleanup') }
+
+    param_def = [['motor', Type.Moveable, None, 'Moveable name'],
+                 ['start_pos', Type.Float, None, 'Starting position'],
+                 ['end_pos', Type.Float, None, 'Ending pos value'],
+                 ['nr_of_points', Type.Integer, None, 'Nr of scan points'],
+                 ['point_time', Type.Float, None, 'Time interval reserved for ' + 
+                                                   'each scan point [s].'],
+                 ['acq_time', Type.Float, 99, 'Acquisition time per scan point. ' +
+                      'Expressed in percentage of point_time. Default: 99 [%]'],
+                 ['samp_freq', Type.Float, -1, 'Sampling frequency. ' + 
+                                        'Default: -1 (means maximum possible)']]
+
+        
+    def prepare(self, motor, start_pos, end_pos, nr_of_points, 
+                point_time, acq_time, samp_freq, **opts):
+        self._prepare([motor], [start_pos], [end_pos], nr_of_points,
+                      point_time, mode=ContinuousHwTimeMode, **opts)
+        self.acq_time = acq_time
+        self.samp_freq = samp_freq
+
+
+class a2scanct(aNscan, Macro):
+    '''Continuous scan controlled by hardware trigger signals. A sequence of 
+    trigger pulses is programmed by time. The scan active time is calculated from
+    nr_of_points * point time. It corresponds to the time while all the involved 
+    in the scan moveables are at the constant velocity. Experimental channels are
+    configured to acquire during acquistion time calculated from acq_time [%] of  
+    the point_time. 
+    Temporary solution used to configure trigger device (pulse train generator): 
+    "TriggerDevices" evironment variable must be set to Ni660X device name 
+    or any other Tango device name implementing:
+    +) following attributes: 
+    - InitialDelayTime [s] - delay time from calling Start to generating first pulse 
+    - HighTime [s] - time interval while signal will maintain its high state
+    - LowTime [s] - time interval while signal will maintain its low state
+    - SampPerChan - nr of pulses to be generated
+    - IdleState - state (high or low) which signal will take after the Start command
+      and which will maintain during the InitialDelayTime.  
+    +) following commands:
+    - Start 
+    - Stop)'''
+
+    hints = {'scan' : 'a2scanct', 'allowsHooks': ('pre-configuration', 
+                                                  'post-configuration',
+                                                  'pre-start',
+                                                  'pre-cleanup',
+                                                  'post-cleanup') }
+
+    param_def = [
+           ['motor1',      Type.Moveable,   None, 'Moveable 1 to move'],
+           ['start_pos1',  Type.Float,   None, 'Scan start position 1'],
+           ['final_pos1',  Type.Float,   None, 'Scan final position 1'],
+           ['motor2',      Type.Moveable,   None, 'Moveable 2 to move'],
+           ['start_pos2',  Type.Float,   None, 'Scan start position 2'],
+           ['final_pos2',  Type.Float,   None, 'Scan final position 2'],
+           ["nr_of_points", Type.Integer, None, "Nr of scan points"],
+           ['point_time', Type.Float,   None, 'Time interval reserved for ' + 
+                                              'each scan point [s].'],
+           ["acq_time", Type.Float, 99, 'Acquisition time per scan point. ' + 
+                      'Expressed in percentage of point_time. Default: 99 [%]'],
+           ["samp_freq", Type.Float, -1, 'Sampling frequency. ' + 
+                                       'Default: -1 (means maximum possible)']]
+           
+    def prepare(self, m1, s1, f1, m2, s2, f2, nr_of_points, 
+                point_time, acq_time, samp_freq, **opts):
+        self._prepare([m1, m2], [s1, s2], [f1, f2], nr_of_points,
+                      point_time, mode=ContinuousHwTimeMode, **opts)
+        self.acq_time = acq_time
+        self.samp_freq = samp_freq
+        
+        
+        
+class a3scanct(aNscan, Macro):
+    '''Continuous scan controlled by hardware trigger signals. A sequence of 
+    trigger pulses is programmed by time. The scan active time is calculated from
+    nr_of_points * point time. It corresponds to the time while all the involved 
+    in the scan moveables are at the constant velocity. Experimental channels are
+    configured to acquire during acquistion time calculated from acq_time [%] of  
+    the point_time. 
+    Temporary solution used to configure trigger device (pulse train generator): 
+    "TriggerDevices" evironment variable must be set to Ni660X device name 
+    or any other Tango device name implementing:
+    +) following attributes: 
+    - InitialDelayTime [s] - delay time from calling Start to generating first pulse 
+    - HighTime [s] - time interval while signal will maintain its high state
+    - LowTime [s] - time interval while signal will maintain its low state
+    - SampPerChan - nr of pulses to be generated
+    - IdleState - state (high or low) which signal will take after the Start command
+      and which will maintain during the InitialDelayTime.  
+    +) following commands:
+    - Start 
+    - Stop)'''
+    hints = {'scan' : 'a2scanct', 'allowsHooks': ('pre-configuration', 
+                                                  'post-configuration',
+                                                  'pre-start',
+                                                  'pre-cleanup',
+                                                  'post-cleanup') }
+
+    param_def = [
+           ['motor1',      Type.Moveable,   None, 'Moveable 1 to move'],
+           ['start_pos1',  Type.Float,   None, 'Scan start position 1'],
+           ['final_pos1',  Type.Float,   None, 'Scan final position 1'],
+           ['motor2',      Type.Moveable,   None, 'Moveable 2 to move'],
+           ['start_pos2',  Type.Float,   None, 'Scan start position 2'],
+           ['final_pos2',  Type.Float,   None, 'Scan final position 2'],
+           ['motor3',      Type.Moveable,   None, 'Moveable 3 to move'],
+           ['start_pos3',  Type.Float,   None, 'Scan start position 3'],
+           ['final_pos3',  Type.Float,   None, 'Scan final position 3'],
+           ["nr_of_points", Type.Integer, None, "Nr of scan points"],
+           ['point_time', Type.Float,   None, 'Time interval reserved for ' + 
+                                              'each scan point [s].'],
+           ["acq_time", Type.Float, 99, 'Acquisition time per scan point. ' + 
+                      'Expressed in percentage of point_time. Default: 99 [%]'],
+           ["samp_freq", Type.Float, -1, 'Sampling frequency. ' + 
+                                       'Default: -1 (means maximum possible)']]
+           
+    def prepare(self, m1, s1, f1, m2, s2, f2, m3, s3, f3, nr_of_points, 
+                point_time, acq_time, samp_freq, **opts):
+        self._prepare([m1, m2, m3], [s1, s2, s3], [f1, f2, f3], nr_of_points,
+                      point_time, mode=ContinuousHwTimeMode, **opts)
+        self.acq_time = acq_time
+        self.samp_freq = samp_freq
+        
+class a4scanct(aNscan, Macro):
+    '''Continuous scan controlled by hardware trigger signals. A sequence of 
+    trigger pulses is programmed by time. The scan active time is calculated from
+    nr_of_points * point time. It corresponds to the time while all the involved 
+    in the scan moveables are at the constant velocity. Experimental channels are
+    configured to acquire during acquistion time calculated from acq_time [%] of  
+    the point_time. 
+    Temporary solution used to configure trigger device (pulse train generator): 
+    "TriggerDevices" evironment variable must be set to Ni660X device name 
+    or any other Tango device name implementing:
+    +) following attributes: 
+    - InitialDelayTime [s] - delay time from calling Start to generating first pulse 
+    - HighTime [s] - time interval while signal will maintain its high state
+    - LowTime [s] - time interval while signal will maintain its low state
+    - SampPerChan - nr of pulses to be generated
+    - IdleState - state (high or low) which signal will take after the Start command
+      and which will maintain during the InitialDelayTime.  
+    +) following commands:
+    - Start 
+    - Stop)'''
+
+    hints = {'scan' : 'a2scanct', 'allowsHooks': ('pre-configuration', 
+                                                  'post-configuration',
+                                                  'pre-start',
+                                                  'pre-cleanup',
+                                                  'post-cleanup') }
+
+    param_def = [
+           ['motor1',      Type.Moveable,   None, 'Moveable 1 to move'],
+           ['start_pos1',  Type.Float,   None, 'Scan start position 1'],
+           ['final_pos1',  Type.Float,   None, 'Scan final position 1'],
+           ['motor2',      Type.Moveable,   None, 'Moveable 2 to move'],
+           ['start_pos2',  Type.Float,   None, 'Scan start position 2'],
+           ['final_pos2',  Type.Float,   None, 'Scan final position 2'],
+           ['motor3',      Type.Moveable,   None, 'Moveable 3 to move'],
+           ['start_pos3',  Type.Float,   None, 'Scan start position 3'],
+           ['final_pos3',  Type.Float,   None, 'Scan final position 3'],
+           ['motor4',      Type.Moveable,   None, 'Moveable 4 to move'],
+           ['start_pos4',  Type.Float,   None, 'Scan start position 4'],
+           ['final_pos4',  Type.Float,   None, 'Scan final position 4'],
+           ["nr_of_points", Type.Integer, None, "Nr of scan points"],
+           ['point_time', Type.Float,   None, 'Time interval reserved for ' + 
+                                              'each scan point [s].'],
+           ["acq_time", Type.Float, 99, 'Acquisition time per scan point. ' + 
+                      'Expressed in percentage of point_time. Default: 99 [%]'],
+           ["samp_freq", Type.Float, -1, 'Sampling frequency. ' + 
+                                       'Default: -1 (means maximum possible)']]
+   
+           
+    def prepare(self, m1, s1, f1, m2, s2, f2, m3, s3, f3, m4, s4, f4, 
+                nr_of_points, point_time, acq_time, samp_freq, **opts):
+        self._prepare([m1, m2, m3, m4], [s1, s2, s3, s4], [f1, f2, f3, f4],
+                      nr_of_points, point_time, mode=ContinuousHwTimeMode,
+                      **opts)
+        self.acq_time = acq_time
+        self.samp_freq = samp_freq
+        
diff --git a/src/sardana/macroserver/macros/standard.py b/src/sardana/macroserver/macros/standard.py
index d65eccf..2fbc578 100644
--- a/src/sardana/macroserver/macros/standard.py
+++ b/src/sardana/macroserver/macros/standard.py
@@ -340,11 +340,12 @@ class mv(Macro):
             self.info("\n".join(msg))
 
 class mstate(Macro):
+    """Prints the state of a motor"""
+    
+    param_def = [['motor', Type.Moveable, None, 'Motor to check state']]
 
-        param_def = [['motor', Type.Moveable, None, 'Motor to check state']]
-
-        def run(self, motor):
-            self.info("Motor %s" % str(motor.getState()))
+    def run(self, motor):
+        self.info("Motor %s" % str(motor.getState()))
 
 class umv(Macro):
     """Move motor(s) to the specified position(s) and update"""
diff --git a/src/sardana/macroserver/scan/gscan.py b/src/sardana/macroserver/scan/gscan.py
index 5c5e83e..30ef9ed 100644
--- a/src/sardana/macroserver/scan/gscan.py
+++ b/src/sardana/macroserver/scan/gscan.py
@@ -27,7 +27,7 @@
 scan"""
 
 __all__ = ["ScanSetupError", "ScanException", "ExtraData", "TangoExtraData",
-           "GScan", "SScan", "CScan", "HScan"]
+           "GScan", "SScan", "CScan", "CSScan", "CTScan", "HScan"]
 
 __docformat__ = 'restructuredtext'
 
@@ -36,12 +36,15 @@ import datetime
 import operator
 import time
 import threading
+import numpy as np
 
+import PyTango
 import taurus
 from taurus.core.util import USER_NAME, Logger
 from taurus.core.tango import FROM_TANGO_TO_STR_TYPE
 from taurus.core.tango.sardana.pool import Ready
 
+from sardana.util.tree import BranchNode, LeafNode, Tree
 from sardana.util.motion import Motor as VMotor
 from sardana.util.motion import MotionPath
 from sardana.macroserver.msexception import MacroServerException, UnknownEnv, \
@@ -51,8 +54,6 @@ from scandata import ColumnDesc, MoveableDesc, ScanFactory, ScanDataEnvironment
 from recorder import OutputRecorder, JsonRecorder, SharedMemoryRecorder, \
     FileRecorder
 
-
-
 class ScanSetupError(Exception): pass
 
 class ScanException(MacroServerException): pass
@@ -136,19 +137,21 @@ class GScan(Logger):
     a generator function pointer, a list of moveable items, an extra 
     environment and a sequence of constrains.
     
+    If the referenced macro is hookable, 'pre-scan' and 'post-scan' hook hints
+    will be used to execute callables before the start and after the end of the
+    scan, respectively
+    
     The generator must be a function yielding a dictionary with the following
     content (minimum) at each step of the scan:
       - 'positions'  : In a step scan, the position where the moveables should go
       - 'integ_time' : In a step scan, a number representing the integration time for the step 
                      (in seconds)
       - 'integ_time' : In a continuous scan, the time between acquisitions
-      - 'pre-scan-hooks' : (optional) a sequence of callables to be called in strict order before starting the scan.
       - 'pre-move-hooks' : (optional) a sequence of callables to be called in strict order before starting to move.
       - 'post-move-hooks': (optional) a sequence of callables to be called in strict order after finishing the move.
       - 'pre-acq-hooks'  : (optional) a sequence of callables to be called in strict order before starting to acquire.
       - 'post-acq-hooks' : (optional) a sequence of callables to be called in strict order after finishing acquisition but before recording the step.
       - 'post-step-hooks' : (optional) a sequence of callables to be called in strict order after finishing recording the step.
-      - 'post-scan-hooks' : (optional) a sequence of callables to be called in strict order after finishing the scan
       - 'hooks' : (deprecated, use post-acq-hooks instead)
       - 'point_id' : a hashable identifing the scan point.
       - 'check_func' : (optional) a list of callable objects. callable(moveables, counters)
@@ -156,6 +159,7 @@ class GScan(Logger):
                        field. The extra information fields must be described in
                        extradesc (passed in the constructor of the Gscan) 
     
+    
     The moveables must be a sequence Motion or MoveableDesc objects.
     
     The environment is a dictionary of extra environment to be added specific
@@ -213,8 +217,8 @@ class GScan(Logger):
         self._generator = generator
         self._extrainfodesc = extrainfodesc
         
-        #nasty hack to make sure macro has access to gScan as soon as possible
-        self._macro._gScan = self
+        #nasty hack to make sure macro has access to gScan as soon as possible 
+        self._macro._gScan = self #TODO: CAUTION! this may be causing a circular reference! 
         
         self._moveables, moveable_names = [], []
         for moveable in moveables:
@@ -490,13 +494,12 @@ class GScan(Logger):
             
         env = ScanDataEnvironment(
                 { 'serialno' : serialno,
-                      'user' : USER_NAME, #todo: this should be got from self.measurement_group.getChannelsInfo()
+                      'user' : USER_NAME, #TODO: this should be got from self.measurement_group.getChannelsInfo()
                      'title' : self.macro.getCommand() } )
         
         # Initialize the data_desc list (and add the point number column)
         data_desc = [
-            ColumnDesc(name='point_nb', label='#Pt No', dtype='int64'),
-            ColumnDesc(name='timestamp', label='dt', dtype='float64')
+            ColumnDesc(name='point_nb', label='#Pt No', dtype='int64')
         ]
         
         # add motor columns
@@ -556,6 +559,8 @@ class GScan(Logger):
             data_desc.append(extra_column.getColumnDesc())
         # add extra columns 
         data_desc += self._extrainfodesc
+        data_desc.append(ColumnDesc(name='timestamp', label='dt', dtype='float64'))
+        
         env['datadesc'] = data_desc
         
         #set the data compression default
@@ -617,7 +622,6 @@ class GScan(Logger):
         :return: (list<ColumnDesc>) a list of :class:`ColumnDesc`, each including a 
                  "pre_scan_value" attribute with the read value for that attr
         '''
-        import PyTango,numpy
         manager = self.macro.getManager()
         all_elements_info = manager.get_elements_with_interface('Element')
         ret = []
@@ -636,8 +640,8 @@ class GScan(Logger):
                 
                 v = PyTango.AttributeProxy(column.source).read().value #@Fixme: Tango-centric. It should work for any Taurus Attribute
                 column.pre_scan_value = v
-                column.shape = numpy.shape(v)
-                column.dtype = getattr(v, 'dtype', numpy.dtype(type(v))).name
+                column.shape = np.shape(v)
+                column.dtype = getattr(v, 'dtype', np.dtype(type(v))).name
                 ret.append(column)
             except:
                 self.macro.warning('Error taking pre-scan snapshot of %s (%s)',label,src)
@@ -831,8 +835,8 @@ class SScan(GScan):
         else:
             yield 0.0
         
-        if hasattr(macro, "pre_scan_hooks"):
-            for hook in macro.pre_scan_hooks:
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('pre-scan'):
                 hook()
         
         self._sum_motion_time = 0
@@ -846,8 +850,8 @@ class SScan(GScan):
             if scream:
                 yield ((i+1) / nr_points) * 100.0
         
-        if hasattr(macro, "post_scan_hooks"):
-            for hook in macro.post_scan_hooks:
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('post-scan'):
                 hook()
 
         if not scream:
@@ -977,66 +981,87 @@ class SScan(GScan):
         
 
 class CScan(GScan):
-    """Continuous scan"""
+    """Continuous scan abstract class. Implements helper methods."""
     
-    def __init__(self, macro, waypointGenerator=None, periodGenerator=None,
-                 moveables=[], env={}, constraints=[], extrainfodesc=[]):
-        GScan.__init__(self, macro, generator=waypointGenerator,
+    def __init__(self, macro, generator=None, moveables=[],
+                 env={}, constraints=[], extrainfodesc=[]):
+        GScan.__init__(self, macro, generator=generator,
                        moveables=moveables, env=env, constraints=constraints,
                        extrainfodesc=extrainfodesc)
-        self._periodGenerator = periodGenerator
         self._current_waypoint_finished = False
         self._all_waypoints_finished = False
         self.motion_event = threading.Event()
         self.motion_end_event = threading.Event()
-
-    def do_backup(self):
-        super(CScan, self).do_backup()
-        self._backup = backup = []
-        for moveable in self.moveables:
-            # first backup all motor parameters
-            motor = moveable.moveable
-            try:
-                velocity = motor.getVelocity()
-                accel_time = motor.getAcceleration()
-                decel_time = motor.getDeceleration()
-                motor_backup = dict(moveable=moveable, velocity=velocity,
-                                    acceleration=accel_time,
-                                    deceleration=decel_time)
-                self.debug("Backup of %s", motor)
-            except AttributeError:
-                motor_backup = None
-            backup.append(motor_backup)
-           
-    def do_restore(self):
-        super(CScan, self).do_restore()
-        # restore changed motors to initial state
-        for motor_backup in self._backup:
-            if motor_backup is None:
-                continue
-            try:
-                motor = motor_backup['moveable'].moveable
-                motor.setVelocity(motor_backup['velocity'])
-                motor.setAcceleration(motor_backup['acceleration'])
-                motor.setDeceleration(motor_backup['deceleration'])
-                self.debug("Restored %s", motor)
-            except:
-                self.macro.warning("Failed to restore %s", motor)
-                self.debug("Details:", exc_info=1)
-
-    def _calculateTotalAcquisitionTime(self):
-        return None
-        
-    @property
-    def period_generator(self):
-        return self._periodGenerator
-    
-    @property
-    def period_steps(self):
-        if not hasattr(self, '_period_steps'):
-            self._period_steps = enumerate(self.period_generator())
-        return self._period_steps
+        data_structures = self.populate_moveables_data_structures(moveables)
+        self._moveables_trees, \
+        physical_moveables_names, \
+        self._physical_moveables = data_structures
+        self._physical_motion = self.macro.getMotion(physical_moveables_names)
+        
+    def populate_moveables_data_structures(self, moveables):
+        '''Populates moveables data structures.
+        :param moveables: (list<Moveable>) data structures will be generated 
+                          for these moveables
+        :return (moveable_trees, physical_moveables_names, physical_moveables)
+                - moveable_trees (list<Tree>) - each tree represent one Moveables
+                            with its hierarchy of inferior moveables.
+                - physical_moveables_names (list<str> - list of the names of the 
+                            physical moveables. List order is important and preserved.
+                - physical_moveables (list<Moveable> - list of the moveable objects.
+                            List order is important and preserved.'''
+        
+        def generate_moveable_node(macro, moveable):
+            '''Function to generate a moveable data structures based on moveable object.
+            Internally can be recursively called if moveable is a PseudoMotor.
+            :param moveable: moveable object
+            :return (moveable_node, physical_moveables_names, physical_moveables)
+                - moveable_node (BaseNode) - can be a BranchNode if moveable is a PseudoMotor
+                                      or a LeafNode if moveable is a PhysicalMotor.
+                - physical_moveables_names (list<str> - list of the names of the 
+                            physical moveables. List order is important and preserved.
+                - physical_moveables (list<Moveable> - list of the moveable objects.
+                            List order is important and preserved.'''
+            moveable_node = None
+            physical_moveables_names = []
+            physical_moveables = []
+            moveable_type = moveable.getType()
+            if moveable_type == "PseudoMotor":
+                moveable_node = BranchNode(moveable)    
+                moveables_names = moveable.elements                
+                sub_moveables = [macro.getMoveable(name) \
+                                 for name in moveables_names]
+                for sub_moveable in sub_moveables:
+                    sub_moveable_node, \
+                    _physical_moveables_names, \
+                    _physical_moveables = generate_moveable_node(macro,
+                                                                 sub_moveable)
+                    physical_moveables_names += _physical_moveables_names
+                    physical_moveables += _physical_moveables
+                    moveable_node.addChild(sub_moveable_node)
+            elif moveable_type == "Motor":
+                moveable_node = LeafNode(moveable)
+                moveable_name = moveable.getName()
+                physical_moveables_names.append(moveable_name)
+                physical_moveables.append(moveable)
+            return moveable_node, physical_moveables_names, physical_moveables
+        
+        moveable_trees = []
+        physical_moveables_names = [] 
+        physical_moveables = []
+                
+        for moveable in moveables:
+            moveable_root_node, _physical_moveables_names, _physical_moveables = \
+                      generate_moveable_node(self.macro, moveable.moveable) 
+            moveable_tree = Tree(moveable_root_node)
+            moveable_trees.append(moveable_tree)
+            physical_moveables_names += _physical_moveables_names
+            physical_moveables += _physical_moveables 
+        return moveable_trees, physical_moveables_names, physical_moveables
     
+    def get_moveables_trees(self):
+        '''Returns reference to the list of the moveables trees'''
+        return self._moveables_trees
+                    
     def on_waypoints_end(self, restore_positions=None):
         """To be called by the waypoint thread to handle the end of waypoints
         (either because no more waypoints or because a macro abort was
@@ -1044,89 +1069,25 @@ class CScan(GScan):
         self.set_all_waypoints_finished(True)
         if restore_positions is not None:
             self.macro.info("Correcting overshoot...")
-            self.motion.move(restore_positions)
+            self._physical_motion.move(restore_positions)
+            #self.motion.move(restore_positions)
         self.motion_end_event.set()        
         self.motion_event.set()
             
     def go_through_waypoints(self, iterate_only=False):
-        """go through the different waypoints."""
+        """Go through the different waypoints."""
         try:
             self._go_through_waypoints()
         except:
             self.macro.error("An error occured moving to waypoints. Aborting...")
-            self.debug("Details:", exc_info=1)
+            self.macro.debug("Details:", exc_info=1)
             self.on_waypoints_end()
 
     def _go_through_waypoints(self):
         """Internal, unprotected method to go through the different waypoints."""
-        macro, motion, waypoints = self.macro, self.motion, self.steps
-        
-        last_positions = None
-        for _, waypoint in waypoints:
-            start_positions = waypoint.get('start_positions')
-            positions = waypoint['positions']
-            if start_positions is None:
-                start_positions = last_positions
-            if start_positions is None:
-                last_positions = positions
-                continue
-            
-            waypoint_info = self.prepare_waypoint(waypoint, start_positions)
-            motion_paths, delta_start, acq_duration = waypoint_info
-            
-            self.acq_duration = acq_duration
-                        
-            #execute pre-move hooks
-            for hook in waypoint.get('pre-move-hooks',[]): 
-                hook()
-    
-            start_pos, final_pos = [] , []
-            for path in motion_paths:
-                start_pos.append(path.initial_user_pos)
-                final_pos.append(path.final_user_pos)
-            
-            if macro.isStopped():
-                self.on_waypoints_end()
-                return
-                       
-            # move to start position
-            
-            motion.move(start_pos)
-            
-            if macro.isStopped():
-                return self.on_waypoints_end()
-            
-            # prepare motor(s) to move with their maximum velocity
-            for path in motion_paths:
-                if not path.apply_correction:
-                    continue
-                vmotor = path.motor
-                motor = path.moveable.moveable
-                motor.setVelocity(vmotor.getMaxVelocity())
-
-            if macro.isStopped():
-                return self.on_waypoints_end()
-                        
-            self.timestamp_to_start = time.time() + delta_start
-            self.motion_event.set()
-            
-            # move to waypoint end position
-            motion.move(final_pos)
-
-            self.motion_event.clear()
-
-            if macro.isStopped():
-                return self.on_waypoints_end()
-            
-            #execute post-move hooks
-            for hook in waypoint.get('post-move-hooks',[]):
-                hook()
-            
-            if start_positions is None:  
-                last_positions = positions
-        
-        self.on_waypoints_end(positions)
-        
+        raise NotImplementedError("_go_through_waypoints must be implemented " +
+                            "in CScan derived classes")
+                
     def waypoint_estimation(self):
         """Internal, unprotected method to go through the different waypoints."""
         motion, waypoints = self.motion, self.generator()
@@ -1183,6 +1144,47 @@ class CScan(GScan):
         total_duration += overshoot_duration
         return total_duration       
     
+    def prepare_waypoint(self, waypoint, start_positions, iterate_only=False):
+        raise NotImplementedError("prepare_waypoint must be implemented in " + 
+                                  "CScan derived classes")
+        
+    def set_all_waypoints_finished(self, v):
+        self._all_waypoints_finished = v
+
+    def do_backup(self):
+        super(CScan, self).do_backup()
+        self._backup = backup = []
+        for moveable in self._physical_moveables:
+            # first backup all motor parameters
+            motor = moveable
+            try:
+                velocity = motor.getVelocity()
+                accel_time = motor.getAcceleration()
+                decel_time = motor.getDeceleration()
+                motor_backup = dict(moveable=moveable, velocity=velocity,
+                                    acceleration=accel_time,
+                                    deceleration=decel_time)
+                self.debug("Backup of %s", motor)
+            except AttributeError:
+                motor_backup = None
+            backup.append(motor_backup)
+           
+    def do_restore(self):
+        super(CScan, self).do_restore()
+        # restore changed motors to initial state
+        for motor_backup in self._backup:
+            if motor_backup is None:
+                continue
+            try:
+                motor = motor_backup['moveable']
+                motor.setVelocity(motor_backup['velocity'])
+                motor.setAcceleration(motor_backup['acceleration'])
+                motor.setDeceleration(motor_backup['deceleration'])
+                self.debug("Restored %s", motor)
+            except:
+                self.macro.warning("Failed to restore %s", motor)
+                self.debug("Details:", exc_info=1)
+                
     def get_max_top_velocity(self, motor):
         """Helper method to find the maximum top velocity for the motor.
         If the motor doesn't have a defined range for top velocity,
@@ -1201,7 +1203,33 @@ class CScan(GScan):
                 max_top_vel = self._maxVelDict[motor]
             except AttributeError:
                 pass
-        return max_top_vel        
+        return max_top_vel
+    
+    def get_min_acc_time(self, motor):
+        """Helper method to find the minimum acceleration time for the motor.
+        If the motor doesn't have a defined range for the acceleration time,
+        then use the current acceleration time"""
+        
+        acc_time_obj = motor.getAccelerationObj()
+        min_acc_time, max_acc_time = acc_time_obj.getRange()
+        try:
+            min_acc_time = float(min_acc_time)
+        except ValueError:
+            min_acc_time = motor.getAcceleration()
+        return min_acc_time
+    
+    def get_min_dec_time(self, motor):
+        """Helper method to find the minimum deceleration time for the motor.
+        If the motor doesn't have a defined range for the acceleration time,
+        then use the current acceleration time"""
+        
+        dec_time_obj = motor.getDecelerationObj()
+        min_dec_time, max_dec_time = dec_time_obj.getRange()
+        try:
+            min_dec_time = float(min_dec_time)
+        except ValueError:
+            min_dec_time = motor.getDeceleration()
+        return min_dec_time
  
 #    def set_max_top_velocity(self, motor):
 #        """Helper method to set the maximum top velocity for the motor to 
@@ -1213,8 +1241,32 @@ class CScan(GScan):
 #        except:
 #            pass
 
-    def prepare_waypoint(self, waypoint, start_positions, iterate_only=False):
+                
+class CSScan(CScan):
+    """Continuous scan controlled by software"""
+    
+    def __init__(self, macro, waypointGenerator=None, periodGenerator=None,
+                 moveables=[], env={}, constraints=[], extrainfodesc=[]):
+        CScan.__init__(self, macro, generator=waypointGenerator,
+                       moveables=moveables, env=env, constraints=constraints,
+                       extrainfodesc=extrainfodesc)
+        self._periodGenerator = periodGenerator
+        
 
+    def _calculateTotalAcquisitionTime(self):
+        return None
+        
+    @property
+    def period_generator(self):
+        return self._periodGenerator
+    
+    @property
+    def period_steps(self):
+        if not hasattr(self, '_period_steps'):
+            self._period_steps = enumerate(self.period_generator())
+        return self._period_steps
+    
+    def prepare_waypoint(self, waypoint, start_positions, iterate_only=False):
         slow_down = waypoint.get('slow_down', 1)
         positions = waypoint['positions']
         
@@ -1309,9 +1361,90 @@ class CScan(GScan):
             path.setFinalUserPos(new_final_pos)
         
         return ideal_paths, delta_start, cruise_duration
+
     
-    def set_all_waypoints_finished(self, v):
-        self._all_waypoints_finished = v
+    def go_through_waypoints(self, iterate_only=False):
+        """go through the different waypoints."""
+        try:
+            self._go_through_waypoints()
+        except Exception, e:
+            self.macro.error("An error occured moving to waypoints. Aborting...")
+            self.macro.debug("Details:", exc_info=1)
+            self.on_waypoints_end()
+            raise e
+
+    def _go_through_waypoints(self):
+        """Internal, unprotected method to go through the different waypoints."""
+        macro, motion, waypoints = self.macro, self.motion, self.steps
+        self.macro.debug("_go_through_waypoints() entering...")
+        
+        last_positions = None
+        for _, waypoint in waypoints:
+            self.macro.debug("Waypoint iteration...")
+            start_positions = waypoint.get('start_positions')
+            positions = waypoint['positions']
+            if start_positions is None:
+                start_positions = last_positions
+            if start_positions is None:
+                last_positions = positions
+                continue
+            
+            waypoint_info = self.prepare_waypoint(waypoint, start_positions)
+            motion_paths, delta_start, acq_duration = waypoint_info
+            
+            self.acq_duration = acq_duration
+                        
+            #execute pre-move hooks
+            for hook in waypoint.get('pre-move-hooks',[]): 
+                hook()
+    
+            start_pos, final_pos = [] , []
+            for path in motion_paths:
+                start_pos.append(path.initial_user_pos)
+                final_pos.append(path.final_user_pos)
+            
+            if macro.isStopped():
+                self.on_waypoints_end()
+                return
+                       
+            # move to start position
+            self.macro.debug("Moving to start position: %s" % repr(start_pos))
+            motion.move(start_pos)
+            
+            if macro.isStopped():
+                return self.on_waypoints_end()
+            
+            # prepare motor(s) to move with their maximum velocity
+            for path in motion_paths:
+                if not path.apply_correction:
+                    continue
+                vmotor = path.motor
+                motor = path.moveable.moveable
+                motor.setVelocity(vmotor.getMaxVelocity())
+
+            if macro.isStopped():
+                return self.on_waypoints_end()
+                        
+            self.timestamp_to_start = time.time() + delta_start
+            self.motion_event.set()
+            
+            # move to waypoint end position
+            motion.move(final_pos)
+
+            self.motion_event.clear()
+
+            if macro.isStopped():
+                return self.on_waypoints_end()
+            
+            #execute post-move hooks
+            for hook in waypoint.get('post-move-hooks',[]):
+                hook()
+            
+            if start_positions is None:  
+                last_positions = positions
+        
+        self.on_waypoints_end(positions)
+
     
     def scan_loop(self):
         motion, mg, waypoints = self.motion, self.measurement_group, self.steps
@@ -1335,8 +1468,8 @@ class CScan(GScan):
         point_nb, step = -1, None
         data = self.data
         
-        if hasattr(macro, "pre_scan_hooks"):
-            for hook in macro.pre_scan_hooks:
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('pre-scan'):
                 hook()
         
         # start move & acquisition as close as possible
@@ -1449,10 +1582,478 @@ class CScan(GScan):
 
         self.motion_end_event.wait()
 
-        if hasattr(macro, "post_scan_hooks"):
-            for hook in macro.post_scan_hooks:
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('post-scan'):
+                hook()
+        
+        
+        env = self._env
+        env['acqtime'] = sum_integ_time
+        env['delaytime'] = sum_delay
+        
+        if not scream:
+            yield 100.0   
+
+
+class CTScan(CScan):
+    '''Continuous scan controlled by hardware trigger signals. 
+    Sequence of trigger signals is programmed in time. '''
+    
+    class ExtraTrigger:
+        '''Helper class and temporary solution for configuring trigger device.
+           It is used to configure any Tango device name implementing:
+           +) following attributes: 
+           - InitialDelayTime [s] - delay time from calling Start to generating first pulse 
+           - HighTime [s] - time interval while signal will maintain its high state
+           - LowTime [s] - time interval while signal will maintain its low state
+           - SampPerChan - nr of pulses to be generated
+           - IdleState - state (high or low) which signal will take after the Start command
+                         and which will maintain during the InitialDelayTime.  
+           +) following commands:
+           - Start 
+           - Stop)'''
+         
+        MIN_HIGH_TIME = 0.0000002
+        MIN_TIME_PER_TRIGGER = 0.000001
+    
+        def __init__(self, macro):
+            self.macro = macro
+            
+            triggerDeviceName = self.macro.getEnv("TriggerDevice")
+            self.master = None
+            self.slaves = []
+            masterName = None
+            slaveNames = []
+            
+            if isinstance(triggerDeviceName, str):
+                masterName = triggerDeviceName
+            elif isinstance(triggerDeviceName, list):
+                masterName = triggerDeviceName[0]
+                slaveNames = triggerDeviceName[1:]
+            
+            for name in slaveNames:
+                slave = PyTango.DeviceProxy(name)
+                self.slaves.append(slave)
+            if masterName != None:
+                self.master = PyTango.DeviceProxy(masterName)        
+    
+        def configure(self, scanTime=None, nrOfTriggers=None, idleState="Low", lowTime=None, highTime=None, delayTime=0):
+            if not None in (scanTime, nrOfTriggers, delayTime, idleState):
+                timePerTrigger = scanTime / nrOfTriggers
+                if timePerTrigger < self.MIN_TIME_PER_TRIGGER:
+                    raise Exception("scanTime is not long enough to manage this amount of triggers")
+                highTime = self.MIN_HIGH_TIME
+                lowTime = timePerTrigger - highTime
+            elif not None in (lowTime, highTime, delayTime, nrOfTriggers, idleState):
+                pass
+            else:
+                raise Exception("Missing parameters.")
+            
+            self.master.write_attribute("InitialDelayTime", delayTime)
+            self.master.write_attribute("HighTime", highTime) # 162.5 ns
+            self.master.write_attribute("LowTime", lowTime) # 2.75 ms
+            self.master.write_attribute("SampPerChan", long(nrOfTriggers))
+            self.master.write_attribute("IdleState", idleState)
+            self.master.write_attribute("SampleTimingType", "Implicit")
+    
+            for slave in self.slaves:
+                slave.write_attribute("HighTime", highTime) # 162.5 ns
+                slave.write_attribute("LowTime", lowTime) # 2.75 ms
+                slave.write_attribute("SampPerChan", long(nrOfTriggers))
+                slave.write_attribute("IdleState", idleState)
+                slave.write_attribute("SampleTimingType", "Implicit")
+                
+            return timePerTrigger
+    
+        def getConfiguration(self):
+            return None, None, None, None
+    
+        def start(self):
+            for slave in self.slaves:
+                self.macro.debug("Staring  %s" % slave.name())
+                slave.Start()
+            if self.master != None:
+                self.master.Start()
+    
+        def stop(self):
+            for slave in self.slaves:
+                self.macro.debug("Stopping  %s" % slave.name())
+                slave.Stop()
+            if self.master != None:
+                self.master.Stop()
+
+    
+    class ExtraMntGrp:
+        '''Helper class and temporary solution for configuring experimental channels.
+        It assumes that experimental channels are implementing:
+        +) following attributes:
+        - Data - an array of acquired data
+        - TriggerMode - Soft or Gate - to configure/unconfigure channel for hardware trigger
+        - NrOfTriggers - to specify how many hardware triggers and acquistions will be done
+        - SamplingFrequency - have sence only for sampling experimental channels
+        +) following SendToCtrl strings:
+        - "pre-start"
+        - "start"
+        - "pre-stop"
+        - "stop"'''
+        
+        def __init__(self, macro):
+            self.macro = macro
+            activeMntGrpName = self.macro.getEnv("ActiveMntGrp")
+            self.mntGrp = self.macro.getMeasurementGroup(activeMntGrpName)        
+            self.activeChannels = []
+            self.nrOfTriggers = 0
+            channels = self.mntGrp.getChannels()
+            for channel in channels:
+                channelName = channel["name"]
+                expChannel = self.macro.getExpChannel(channelName)
+                expChannel.getHWObj().set_timeout_millis(120000) #in case of readout of position channels, it can take really long...
+                self.activeChannels.append(expChannel)
+    
+        def isMoving(self):
+            for channel in self.activeChannels:
+                if channel.State() == PyTango.DevState.MOVING:
+                    return True
+            return False
+    
+        def start(self):
+            for channel in self.activeChannels:
+                pool = channel.getPoolObj()
+                ctrlName = channel.getControllerName()
+                axis = channel.getAxis()
+                self.macro.debug("Pre-starting controller: %s, axis: %d", ctrlName, axis)
+                pool.SendToController([ctrlName, 'pre-start %d' % axis])
+    
+            for channel in self.activeChannels:
+                pool = channel.getPoolObj()
+                ctrlName = channel.getControllerName()
+                axis = channel.getAxis()
+                self.macro.debug("Starting controller: %s, axis: %d", ctrlName, axis)
+                pool.SendToController([ctrlName, 'start %d' % axis])
+     
+        def stop(self):
+            for channel in self.activeChannels:
+                pool = channel.getPoolObj()
+                ctrlName = channel.getControllerName()
+                axis = channel.getAxis()
+                self.macro.debug("Pre-stopping controller: %s, axis: %d", ctrlName, axis)
+                pool.SendToController([ctrlName, 'pre-stop %d' % axis])
+    
+            for channel in self.activeChannels:
+                pool = channel.getPoolObj()
+                ctrlName = channel.getControllerName()
+                axis = channel.getAxis()
+                self.macro.debug("Stopping controller: %s, axis: %d", ctrlName, axis)
+                pool.SendToController([ctrlName, 'stop %d' % axis])
+    
+        def getDataList(self):
+            dataList = [ {"point_nb" : i, "timestamp" : 0} for i in xrange(self.nrOfTriggers) ]
+            for channel in self.activeChannels:
+                dataDesc = channel.getFullName()
+                channelData = channel.getAttribute("Data").read().value
+                for i, data in enumerate(channelData):
+                    dataList[i][dataDesc] = data
+            return dataList
+    
+        def setSamplingFrequency(self, freq):
+            for channel in self.activeChannels:
+                channel.getAttribute('SamplingFrequency').write(freq)
+    
+        def setAcquisitionTime(self, acqTime):
+            for channel in self.activeChannels:
+                channel.getAttribute('AcquisitionTime').write(acqTime)
+    
+        def setTriggerMode(self, mode):
+            if mode not in ["soft", "gate"]:
+                raise Exception("Trigger mode must be either soft or gate.")
+            for channel in self.activeChannels:
+                channel.getAttribute('TriggerMode').write(mode)
+    
+        def setNrOfTriggers(self, nrOfTriggers):
+            self.nrOfTriggers = nrOfTriggers
+            for channel in self.activeChannels:
+                channel.getAttribute('NrOfTriggers').write(nrOfTriggers)
+    
+        def configure(self, nrOfTriggers, acqTime, timePerTrigger, sampFreq=-1, triggerMode="gate"):
+            self.macro.debug("acqTime: %s" % acqTime)
+            if timePerTrigger == None:
+                raise Exception("TimePerTrigger attribute must be set")
+            acqTime = timePerTrigger * acqTime / 100.0
+            self.setTriggerMode(triggerMode)
+            self.setNrOfTriggers(nrOfTriggers)
+            self.setSamplingFrequency(sampFreq)
+            self.setAcquisitionTime(acqTime)
+            self.macro.debug("MG: nrOfTriggers: %s, timePerTrigger: %s, acqTime: %s, sampFreq: %s" % (nrOfTriggers,timePerTrigger,acqTime,sampFreq))
+    
+        def getConfiguration(self):
+            return None
+    
+        def setConfiguration(self, configuration):
+            pass
+
+    
+    def __init__(self, macro, generator=None,
+                 moveables=[], env={}, constraints=[], extrainfodesc=[]):
+        CScan.__init__(self, macro, generator=generator,
+                       moveables=moveables, env=env, constraints=constraints,
+                       extrainfodesc=extrainfodesc)
+        self._measurement_group = self.ExtraMntGrp(macro)
+        self.extraTrigger = self.ExtraTrigger(macro)
+            
+        
+    def prepare_waypoint(self, waypoint, start_positions, iterate_only=False):
+        '''Prepare list of MotionPath objects per each physical motor. 
+        :param waypoint: (dict) waypoint dictionary with necessary information
+        :param start_positions: (list<float>) list of starting position per each
+                                 physical motor
+        :return (ideal_paths, acc_time, active_time)
+                - ideal_paths: (list<MotionPath> representing motion attributes 
+                               of each physical motor)
+                - acc_time: acceleration time which will be used during the scan
+                            it corresponds to the longest acceleration time of 
+                            all the motors
+                - active_time: time interval while all the physical motors will
+                               maintain constant velocity'''
+
+        positions = waypoint['positions']
+        active_time = waypoint["active_time"]
+        
+        ideal_paths = []
+        
+        max_acc_time, max_dec_time = 0, 0                
+        for moveable, end_position in zip(self._physical_moveables, positions):
+            motor = moveable
+            self.macro.debug("Motor: %s" % motor.getName())
+            self.macro.debug("AccTime: %f" % self.get_min_acc_time(motor))
+            self.macro.debug("DecTime: %f" % self.get_min_dec_time(motor))
+            max_acc_time = max(self.get_min_acc_time(motor), max_acc_time)
+            max_dec_time = max(self.get_min_dec_time(motor), max_dec_time)
+            
+        acc_time = max_acc_time
+        dec_time = max_dec_time
+            
+        for moveable, start_position, end_position in \
+                      zip(self._physical_moveables, start_positions, positions):
+            base_vel = moveable.getBaseRate()        
+            ideal_vmotor = VMotor(accel_time=acc_time,
+                                  decel_time=dec_time,
+                                  min_vel=base_vel)
+            ideal_path = MotionPath(ideal_vmotor, 
+                                    start_position, 
+                                    end_position, 
+                                    active_time)            
+            ideal_path.moveable = moveable
+            ideal_path.apply_correction = True        
+            ideal_paths.append(ideal_path)                                 
+        
+        return ideal_paths, acc_time, active_time
+    
+    def _go_through_waypoints(self):
+        """Internal, unprotected method to go through the different waypoints.
+           It controls all the three objects: motion, trigger and measurement
+           group."""
+        macro, motion, waypoints = self.macro, self._physical_motion, self.steps
+        self.macro.debug("_go_through_waypoints() entering...")
+        
+        last_positions = None
+        for _, waypoint in waypoints:
+            self.macro.debug("Waypoint iteration...")
+            start_positions = waypoint.get('start_positions')
+            positions = waypoint['positions']
+            if start_positions is None:
+                start_positions = last_positions
+            if start_positions is None:
+                last_positions = positions
+                continue
+            
+            waypoint_info = self.prepare_waypoint(waypoint, start_positions)
+            motion_paths, delta_start, acq_duration = waypoint_info
+            
+            self.acq_duration = acq_duration
+                        
+            #execute pre-move hooks
+            for hook in waypoint.get('pre-move-hooks',[]): 
+                hook()
+    
+            start_pos, final_pos = [] , []
+            for path in motion_paths:
+                start_pos.append(path.initial_user_pos)
+                final_pos.append(path.final_user_pos)
+            
+            if macro.isStopped():
+                self.on_waypoints_end()
+                return
+
+            self.__mntGrpConfigured = False
+            self.__triggerConfigured = False
+            self.__mntGrpStarted = False
+            self.__triggerStarted = False
+            
+            #validation of parameters
+            for start, end in zip(self.macro.starts, self.macro.finals):
+                if start == end:
+                    raise Exception("Start and End can not be equal.")
+
+            startTimestamp = time.time()
+        
+            #extra pre configuration
+            if hasattr(macro, 'getHooks'):
+                for hook in macro.getHooks('pre-configuration'):
+                    hook()
+            self.macro.checkPoint()
+    
+            #configuring trigger lines
+            oldHighTime, oldLowTime, oldDelay, oldNrOfTriggers = \
+                                        self.extraTrigger.getConfiguration()
+            self.__triggerConfigured = True
+            timePerTrigger = self.extraTrigger.configure(delayTime=delta_start,
+                                           scanTime=acq_duration,
+                                           nrOfTriggers=self.macro.nr_of_points)
+            self.macro.checkPoint()
+    
+            #configuring measurementGroup
+            self.mntGrpConfiguration = self._measurement_group.getConfiguration()
+            self.__mntGrpConfigured = True
+            self._measurement_group.configure(self.macro.nr_of_points, 
+                                       self.macro.acq_time, 
+                                       timePerTrigger)
+            self.macro.checkPoint()
+    
+            #extra post configuration
+            if hasattr(macro, 'getHooks'):
+                for hook in macro.getHooks('post-configuration'):
+                    hook()
+            self.macro.checkPoint()
+    
+            endTimestamp = time.time()
+            self.macro.info("Configuration took %s time." % repr(endTimestamp - startTimestamp))
+
+            # move to start position
+            self.macro.debug("Moving to start position: %s" % repr(start_pos))
+            motion.move(start_pos)
+            
+            if macro.isStopped():
+                return self.on_waypoints_end()
+            
+            # prepare motor(s) to move with their maximum velocity
+            for path in motion_paths:
+                motor = path.moveable
+                self.macro.debug("Motor: %s" % motor.getName())
+                self.macro.debug("Velocity: %f" % path.max_vel)
+                self.macro.debug("AccTime: %f" % path.max_vel_time)
+                self.macro.debug("DecTime: %f" % path.min_vel_time)
+                #TODO: check why we have 0 here
+                #if 0 in [path.max_vel, path.max_vel_time, path.min_vel_time]:
+                #    continue
+                motor.setVelocity(path.max_vel)
+                motor.setAcceleration(path.max_vel_time)                
+                motor.setDeceleration(path.min_vel_time)
+                
+            if macro.isStopped():
+                return self.on_waypoints_end()
+            
+            if hasattr(macro, 'getHooks'):
+                for hook in macro.getHooks('pre-start'):
+                    hook()
+            self.macro.checkPoint()
+    
+            self.macro.debug("Starting measurement group")
+            self.__mntGrpStarted = True
+            self._measurement_group.start()            
+            
+            self.timestamp_to_start = time.time() + delta_start
+                        
+            self.motion_event.set()
+            
+            # move to waypoint end position
+            self.macro.debug("Moving to waypoint position: %s" % repr(final_pos))
+            self.macro.debug("Starting triggers")
+            self.__triggerStarted = True
+            self.extraTrigger.start()
+            motion.move(final_pos)
+                        
+            self.motion_event.clear()
+
+            if macro.isStopped():
+                return self.on_waypoints_end()
+            
+            #execute post-move hooks
+            for hook in waypoint.get('post-move-hooks',[]):
+                hook()
+                    
+            self.macro.debug("Waiting for measurement group to finish")            
+            while self._measurement_group.isMoving():
+                self.macro.checkPoint()
+                time.sleep(0.1)
+                
+            self.macro.debug("Getting data")                
+            data_list = self._measurement_group.getDataList()
+            
+            def populate_ideal_positions():
+                moveables = self.moveables
+                nr_of_points = self.macro.nr_of_points
+                starts = self.macro.starts
+                finals = self.macro.finals
+                positions_records = [{} for i in xrange(nr_of_points)]
+                
+                for moveable, start, final in zip(moveables, starts, finals):
+                    name = moveable.moveable.getName()
+                    step_size = abs((end-start)/nr_of_points)
+                    for point_nr, position in enumerate(np.arange(start, \
+                                                            final, step_size)):
+                        positions_records[point_nr][name] = position    
+                    
+                return positions_records
+            
+            #TODO: decide what to do with moveables
+            position_list = populate_ideal_positions()
+
+            self.macro.debug("Storing data")
+            for data_dict, position_dict in zip(data_list,position_list):
+                data_dict.update(position_dict)
+                self.data.addRecord(data_dict)    
+            
+            if start_positions is None:  
+                last_positions = positions
+        
+        self.on_waypoints_end(positions)
+        
+    def on_waypoints_end(self, restore_positions=None):
+        self.macro.debug("on_waypoints_end() entering...")
+        CScan.on_waypoints_end(self, restore_positions=restore_positions)
+        self.cleanup()
+
+    def scan_loop(self):        
+        macro = self.macro
+        manager = macro.getManager()
+        scream = False
+        motion_event = self.motion_event
+        startts = self._env['startts']
+        
+        sum_delay = 0
+        sum_integ_time = 0
+        
+        if hasattr(macro, "nr_points"):
+            nr_points = float(macro.nr_points)
+            scream = True
+        else:
+            yield 0.0
+
+        moveables = [ m.moveable for m in self.moveables ]
+        
+        point_nb, step = -1, None
+        data = self.data
+        
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('pre-scan'):
                 hook()
+                
+        self.go_through_waypoints()
+        
         
+        if hasattr(macro, 'getHooks'):
+            for hook in macro.getHooks('post-scan'):
+                hook()
         
         env = self._env
         env['acqtime'] = sum_integ_time
@@ -1461,6 +2062,57 @@ class CScan(GScan):
         if not scream:
             yield 100.0   
 
+    def cleanup(self):
+        '''This method is responsible for restoring state of measurement group 
+        and trigger to its state before the scan.'''
+        startTimestamp = time.time()
+
+        if self.__mntGrpStarted:
+            self.debug("Stopping measurement group")
+            try:
+                self._measurement_group.stop()
+            except Exception, e:
+                self.warning("Exception while trying to stop measurement group.")
+                self.debug(e)
+
+        if self.__triggerStarted:
+            self.debug("Stopping triggers")
+            try:
+                self.extraTrigger.stop()
+            except Exception, e:
+                self.warning("Exception while trying to stop trigger.")
+                self.debug(e)
+
+        if hasattr(self.macro, 'getHooks'):
+            for hook in self.macro.getHooks('pre-cleanup'):
+                self.debug("Executing pre-cleanup hook")
+                try:
+                    hook()
+                except Exception, e:
+                    self.warning("Exception while trying to execute a pre-cleanup hook")
+                    self.debug(e)
+
+        if self.__mntGrpConfigured:
+            self.debug("Restoring configuration of measurement group")
+            try:
+                self._measurement_group.setConfiguration(self.mntGrpConfiguration)
+                #TODO: mntGrp configuration should contain also: nrOfTriggers, acqTime, sampling frequency
+            except:
+                self.warning("Exception while trying to restore measurement group parameters")
+                self.debug(e)
+
+        if hasattr(self.macro, 'getHooks'):
+            for hook in self.macro.getHooks('post-cleanup'):
+                self.debug("Executing post-cleanup hook")
+                try:
+                    hook()
+                except Exception, e:
+                    self.warning("Exception while trying to execute a post-cleanup hook")
+                    self.debug(e)
+
+        endTimestamp = time.time()
+        self.debug("Cleanup took %s time." % repr(endTimestamp - startTimestamp))
+
 
 class HScan(SScan):
     """Hybrid scan"""
diff --git a/src/sardana/macroserver/scan/recorder/datarecorder.py b/src/sardana/macroserver/scan/recorder/datarecorder.py
index 2ddaed0..7f0a18a 100644
--- a/src/sardana/macroserver/scan/recorder/datarecorder.py
+++ b/src/sardana/macroserver/scan/recorder/datarecorder.py
@@ -69,6 +69,20 @@ class DataHandler:
             else:    # blockSave
                 pass
            
+    def addCustomData(self, value, name, **kwargs):
+        '''Write data other than a record. 
+        
+        :param value: The value to be written
+        :param name: An identification for this value
+        
+        Optional keyword arguments can be passed with information that some
+        recorders may need in order to record this value. For example: the NeXus
+        recorder will make use of "nxpath" info if available to place the value
+        where it belongs in the nexus hierarchy. Check the `addCustomData`
+        method of each recorder to see what they use/require.
+        '''
+        for recorder in self.recorders:
+            recorder.addCustomData(value, name, **kwargs )
 #
 # Recorders
 #
@@ -130,7 +144,13 @@ class DataRecorder(Logger):
 
     def setSaveMode( self, mode ):
         self.savemode = mode
-
+        
+    def addCustomData(self, value, name, **kwargs):
+        self._addCustomData(value, name, **kwargs)
+        
+    def _addCustomData(self, value, name, **kwargs):
+        pass
+    
 
 class DumbRecorder(DataRecorder):
     def _startRecordList(self, recordlist):
diff --git a/src/sardana/macroserver/scan/recorder/output.py b/src/sardana/macroserver/scan/recorder/output.py
index 37d3e6d..61a2560 100644
--- a/src/sardana/macroserver/scan/recorder/output.py
+++ b/src/sardana/macroserver/scan/recorder/output.py
@@ -99,6 +99,18 @@ class JsonRecorder(DataRecorder):
         #data = self._codec.encode(('', kwargs))
         #self._stream.sendRecordData(*data)
         self._stream.sendRecordData(kwargs, codec='json')
+        
+    def _addCustomData(self, value, name, **kwargs):
+        '''
+        The custom data will be sent as a packet with type='custom_data' and its
+        data will be the dictionary of keyword arguments passed to this method
+        plus 'name' and 'value'
+        '''
+        macro_id = self.recordlist.getEnvironValue('macro_id')
+        data = dict(kwargs) #shallow copy
+        data['name'] = name
+        data['value'] = value
+        self._sendPacket(type="custom_data", data=data, macro_id=macro_id)
 
 
 class OutputRecorder(DataRecorder):
@@ -230,3 +242,15 @@ class OutputRecorder(DataRecorder):
             
         self._stream.output(scan_line)
         self._stream.flushOutput()
+        
+    def _addCustomData(self, value, name, **kwargs):
+        '''
+        The custom data will be added as an info line in the form:: 
+        Custom data: name : value
+        '''
+        if numpy.rank(value)>0: 
+            v = 'Array(%s)'%str(numpy.shape(value))
+        else:
+            v = str(value)
+        self._stream.output('Custom data: %s : %s'%(name,v) )
+        self._stream.flushOutput()
diff --git a/src/sardana/macroserver/scan/recorder/storage.py b/src/sardana/macroserver/scan/recorder/storage.py
index f576741..fbd2434 100644
--- a/src/sardana/macroserver/scan/recorder/storage.py
+++ b/src/sardana/macroserver/scan/recorder/storage.py
@@ -32,6 +32,7 @@ __docformat__ = 'restructuredtext'
 import os
 import time
 import itertools
+import re
 
 import numpy
 
@@ -363,6 +364,39 @@ class SPEC_FileRecorder(BaseFileRecorder):
         self.fd.flush()
         self.fd.close()
 
+                    
+    def _addCustomData(self, value, name, **kwargs):
+        '''
+        The custom data will be added as a comment line in the form:: 
+        
+        #C name : value
+        
+        ..note:: non-scalar values (or name/values containing end-of-line) will not be written
+        '''
+        if self.filename is None:
+            self.info('Custom data "%s" will not be stored in SPEC file. Reason: uninitialized file',name)
+            return
+        if numpy.rank(value)>0: #ignore non-scalars
+            self.info('Custom data "%s" will not be stored in SPEC file. Reason: value is non-scalar',name)
+            return
+        v = str(value)
+        if '\n' in v or '\n' in name: #ignore if name or the string representation of the value contains end-of-line
+            self.info('Custom data "%s" will not be stored in SPEC file. Reason: unsupported format',name)
+            return
+        
+        fileWasClosed = self.fd is None or self.fd.closed
+        if fileWasClosed:
+            try:
+                self.fd = open(self.filename,'a')
+            except:
+                self.info('Custom data "%s" will not be stored in SPEC file. Reason: cannot open file',name)
+                return
+        self.fd.write( '#C %s : %s\n'%(name,v) )
+        self.fd.flush()
+        if fileWasClosed:
+            self.fd.close() #leave the file descriptor as found
+        
+        
 
 class BaseNEXUS_FileRecorder(BaseFileRecorder):
     """Base class for NeXus file recorders"""   
@@ -383,7 +417,8 @@ class BaseNEXUS_FileRecorder(BaseFileRecorder):
             self.nxs = nxs
         except ImportError:
             raise Exception("NeXus is not available")
-
+        
+        self.macro = macro
         self.overwrite = overwrite
         if filename:
             self.setFileName(filename)
@@ -467,6 +502,8 @@ class BaseNAPI_FileRecorder(BaseNEXUS_FileRecorder):
     # Convenience methods to make NAPI less tedious
     #===========================================================================
     
+    _nxentryInPath = re.compile(r'/[^/:]+:NXentry')
+    
     def _makedata(self, name, dtype=None, shape=None, mode='lzw', chunks=None, comprank=None):
         '''
         combines :meth:`nxs.NeXus.makedata` and :meth:`nxs.NeXus.compmakedata` by selecting between 
@@ -544,26 +581,43 @@ class BaseNAPI_FileRecorder(BaseNEXUS_FileRecorder):
         self.fd.makelink(nid)
             
     def _createBranch(self, path):
-        """navigates the nexus tree starting in the current <entry> and finishing in <entry>/path.
-        It creates the groups if they do not exist, using the class info in self.instrDict
-        If successful, path is left open"""
-        groups=path.split('/')
-        self.fd.openpath("/%s:NXentry" % self.entryname)
-        relpath="" #the current path relative to <entry>, to use as a key for instrDict
-        for g in groups:
+        """
+        Navigates the nexus tree starting in / and finishing in path. 
+        
+        If path does not start with `/<something>:NXentry`, the current entry is
+        prepended to it.
+        
+        This method creates the groups if they do not exist. If the
+        path is given using `name:nxclass` notation, the given nxclass is used.
+        Otherwise, the class name is obtained from self.instrDict values (and if
+        not found, it defaults to NXcollection). If successful, path is left
+        open
+        """
+        m = self._nxentryInPath.match(path)
+        if m is None:
+            self._createBranch("/%s:NXentry"%self.entryname) #if at all, it will recurse just once 
+#            self.fd.openpath("/%s:NXentry" % self.entryname)
+        else:
+            self.fd.openpath("/")
+        
+        relpath="" 
+        for g in path.split('/'):
             if len(g) == 0:
                 continue
             relpath = relpath + "/"+ g
-            try:
-                group_type = self.instrDict[relpath].klass
-            except:
-                group_type = 'NXcollection'
+            if ':' in g:
+                g,group_type = g.split(':')
+            else:
+                try:
+                    group_type = self.instrDict[relpath].klass
+                except:
+                    group_type = 'NXcollection'
             try:
                 self.fd.opengroup(g, group_type)
             except:
                 self.fd.makegroup(g, group_type)
-                self.fd.opengroup(g, group_type)    
-
+                self.fd.opengroup(g, group_type)
+                
 
 class NXscan_FileRecorder(BaseNAPI_FileRecorder):
     """saves data to a nexus file that follows the NXscan application definition
@@ -588,7 +642,39 @@ class NXscan_FileRecorder(BaseNAPI_FileRecorder):
         if not self.overwrite and os.path.exists(self.filename): nxfilemode='rw'
         self.fd = nxs.open(self.filename, nxfilemode)
         self.entryname = "entry%d" % serialno
-        self.fd.makegroup(self.entryname,"NXentry") 
+        try:
+            self.fd.makegroup(self.entryname,"NXentry")
+        except NeXusError:
+            entrynames = self.fd.getentries().keys()
+            
+            #===================================================================
+            ##Warn and abort
+            if self.entryname in entrynames:
+                raise RuntimeError(('"%s" already exists in %s. To prevent data corruption the macro will be aborted.\n'%(self.entryname, self.filename)+
+                                    'This is likely caused by a wrong ScanID\n'+
+                                    'Possible workarounds:\n'+
+                                    '  * first, try re-running this macro (the ScanID may be automatically corrected)\n'
+                                    '  * if not, try changing ScanID with senv, or...\n'+
+                                    '  * change the file name (%s will be in both files containing different data)\n'%self.entryname+
+                                    '\nPlease report this problem.'))
+            else:
+                raise              
+            #===================================================================
+            
+            #===================================================================
+            ## Warn and continue writing to another entry
+            #if self.entryname in entrynames:
+            #    i = 2
+            #    newname = "%s_%i"%(self.entryname,i)
+            #    while(newname in entrynames):
+            #        i +=1
+            #        newname = "%s_%i"%(self.entryname,i)
+            #    self.warning('"%s" already exists. Using "%s" instead. This may indicate a bug in %s',self.entryname, newname, self.macro.name)
+            #    self.macro.warning('"%s" already exists. Using "%s" instead. \nThis may indicate a bug in %s. Please report it.',self.entryname, newname, self.macro.name)
+            #    self.entryname = newname
+            #    self.fd.makegroup(self.entryname,"NXentry")
+            #===================================================================
+            
         self.fd.opengroup(self.entryname,"NXentry") 
         
         
@@ -629,8 +715,7 @@ class NXscan_FileRecorder(BaseNAPI_FileRecorder):
         self.fd.closegroup()
         
         #prepare the "measurement" group
-        self.fd.makegroup("measurement","NXcollection")
-        self.fd.opengroup("measurement","NXcollection")
+        self._createBranch("measurement:NXcollection")
         if self.savemode==SaveModes.Record:
             #create extensible datasets
             for dd in self.datadesc:
@@ -649,12 +734,9 @@ class NXscan_FileRecorder(BaseNAPI_FileRecorder):
         self.fd.flush()
     
     def _createPreScanSnapshot(self, env):
-        measurementpath = "/%s:NXentry/measurement:NXcollection"%self.entryname
-        self.fd.openpath(measurementpath)
         #write the pre-scan snapshot in the "measurement:NXcollection/pre_scan_snapshot:NXcollection" group
         self.preScanSnapShot = env.get('preScanSnapShot',[])
-        self.fd.makegroup("pre_scan_snapshot","NXcollection")
-        self.fd.opengroup("pre_scan_snapshot","NXcollection")
+        self._createBranch('measurement:NXcollection/pre_scan_snapshot:NXcollection')
         links = {}
         for dd in self.preScanSnapShot: #desc is a ColumnDesc object
             label = self.sanitizeName(dd.label)
@@ -825,8 +907,56 @@ class NXscan_FileRecorder(BaseNAPI_FileRecorder):
                     self._nxln(src, dst)
                 except:
                     self.warning("cannot create link for '%s'. Skipping",axis)
-                
-
+                    
+    def _addCustomData(self, value, name, nxpath=None, dtype=None, **kwargs):
+        '''
+        apart from value and name, this recorder can use the following optional parameters:
+        
+        :param nxpath: (str) a nexus path (optionally using name:nxclass notation for
+                       the group names). See the rules for automatic nxclass
+                       resolution used by
+                       :meth:`NXscan_FileRecorder._createBranch`.
+                       If None given, it defaults to 
+                       nxpath='custom_data:NXcollection'
+                       
+        :param dtype: name of data type (it is inferred from value if not given)
+                       
+        '''           
+        if nxpath is None:
+            nxpath='custom_data:NXcollection'
+        if dtype is None:
+            if numpy.isscalar(value):
+                dtype = numpy.dtype(type(value)).name
+                if numpy.issubdtype(dtype,str):
+                    dtype='char'
+                if dtype == 'bool':
+                    value, dtype = int(value), 'int8' 
+            else:
+                value = numpy.array(value)
+                dtype = value.dtype.name
+            
+        if dtype not in self.supported_dtypes and dtype != 'char':
+            self.warning("cannot write '%s'. Reason: unsupported data type",name)
+            return
+        #open the file if necessary 
+        fileWasClosed = self.fd is None or not self.fd.isopen
+        if fileWasClosed:
+            if not self.overwrite and os.path.exists(self.filename): nxfilemode='rw'
+            import nxs
+            self.fd = nxs.open(self.filename, nxfilemode)
+        #write the data
+        self._createBranch(nxpath)
+        try:
+            self._writeData(name, value, dtype)
+        except ValueError, e:
+            msg = "Error writing %s. Reason: %s"%(name, str(e))
+            self.warning(msg)
+            self.macro.warning(msg)
+        #leave the file as it was
+        if fileWasClosed:
+            self.fd.close()
+        
+            
 class NXxas_FileRecorder(BaseNEXUS_FileRecorder):
     """saves data to a nexus file that follows the NXsas application definition
     
diff --git a/src/sardana/pool/pool.py b/src/sardana/pool/pool.py
index f477f6a..9e7ca66 100644
--- a/src/sardana/pool/pool.py
+++ b/src/sardana/pool/pool.py
@@ -35,8 +35,8 @@ __docformat__ = 'restructuredtext'
 import os.path
 import logging.handlers
 
-from taurus.core import AttributeNameValidator
-from taurus.core.util import CaselessDict
+from taurus.core.taurusvalidator import AttributeNameValidator
+from taurus.core.util.containers import CaselessDict
 
 from sardana import InvalidId, ElementType, TYPE_ACQUIRABLE_ELEMENTS, \
     TYPE_PSEUDO_ELEMENTS, TYPE_PHYSICAL_ELEMENTS, TYPE_MOVEABLE_ELEMENTS
@@ -705,4 +705,4 @@ class Pool(PoolContainer, PoolObject, SardanaElementManager, SardanaIDManager):
         graph = Graph()
         for moveable in moveable_elems_map.values():
             self._build_element_dependencies(moveable, graph)
-        return graph    
\ No newline at end of file
+        return graph    
diff --git a/src/sardana/pool/poolbasegroup.py b/src/sardana/pool/poolbasegroup.py
index bac0d4b..69326f4 100644
--- a/src/sardana/pool/poolbasegroup.py
+++ b/src/sardana/pool/poolbasegroup.py
@@ -30,7 +30,7 @@ __all__ = [ "PoolBaseGroup"]
 
 __docformat__ = 'restructuredtext'
 
-from taurus.core import AttributeNameValidator
+from taurus.core.taurusvalidator import AttributeNameValidator
 
 from sardana import State, ElementType, TYPE_PHYSICAL_ELEMENTS
 from poolexternal import PoolExternalObject
diff --git a/src/sardana/pool/poolmeasurementgroup.py b/src/sardana/pool/poolmeasurementgroup.py
index 670ae32..87bdf54 100644
--- a/src/sardana/pool/poolmeasurementgroup.py
+++ b/src/sardana/pool/poolmeasurementgroup.py
@@ -30,7 +30,7 @@ __all__ = [ "PoolMeasurementGroup" ]
 
 __docformat__ = 'restructuredtext'
 
-from taurus.core import AttributeNameValidator
+from taurus.core.taurusvalidator import AttributeNameValidator
 from taurus.core.tango.sardana import PlotType, Normalization
 
 from sardana import State,ElementType, \
diff --git a/src/sardana/pool/poolpseudomotor.py b/src/sardana/pool/poolpseudomotor.py
index 481e4ec..f12b318 100644
--- a/src/sardana/pool/poolpseudomotor.py
+++ b/src/sardana/pool/poolpseudomotor.py
@@ -51,8 +51,22 @@ class Position(SardanaAttribute):
     def __init__(self, *args, **kwargs):
         self._exc_info = None
         super(Position, self).__init__(*args, **kwargs)
-        for position_attr in self.obj.get_physical_position_attribute_iterator():
-            position_attr.add_listener(self.on_change)
+
+        # 130226: We found a bug https://sourceforge.net/p/sardana/tickets/2/ that makes the Pool segfault with some pseudomotor configuration:
+        # It can be reproduced by:
+        # 4 physical motors: m1 m2 m3 m4
+        # 2 slits: s1g,s1o = f(m1,m2) and s2g,s2o = f(m3,m4)
+        # The pool will not be able to start if we create a third slit with s3g,s3o = f(s1g, s2g)
+        # self.obj.get_physical_position_attribute_iterator() raises a KeyError exception
+        # so we will flag the Position object as no_listeners for later configuration
+        # We should still investigate the root of the problem when ordering the creation of elements
+        self._listeners_configured = False
+        try:
+            for position_attr in self.obj.get_physical_position_attribute_iterator():
+                position_attr.add_listener(self.on_change)
+            self._listeners_configured = True
+        except KeyError:
+            pass
     
     def _in_error(self):
         for position_attr in self.obj.get_physical_position_attribute_iterator():
@@ -210,9 +224,9 @@ class PoolPseudoMotor(PoolBaseGroup, PoolElement):
         self._drift_correction = kwargs.pop('drift_correction', None)
         user_elements = kwargs.pop('user_elements')
         kwargs['elem_type'] = ElementType.PseudoMotor
+        PoolElement.__init__(self, **kwargs)
         PoolBaseGroup.__init__(self, user_elements=user_elements,
                                pool=kwargs['pool'])
-        PoolElement.__init__(self, **kwargs)
         self._position = Position(self, listeners=self.on_change)
 
     # --------------------------------------------------------------------------
@@ -430,6 +444,13 @@ class PoolPseudoMotor(PoolBaseGroup, PoolElement):
 
     _STD_STATUS = "{name} is {state}\n{ctrl_status}"
     def calculate_state_info(self, status_info=None):
+
+        # Refer to Position.__init__ method for an explanation on this 'hack'
+        if not self._position._listeners_configured:
+            for position_attr in self.get_physical_position_attribute_iterator():
+                position_attr.add_listener(self._position.on_change)
+            self._position._listeners_configured = True
+        
         if status_info is None:
             status_info = self._state, self._status
         state, status = status_info
diff --git a/src/sardana/release.py b/src/sardana/release.py
index c86c6dd..7c94185 100644
--- a/src/sardana/release.py
+++ b/src/sardana/release.py
@@ -26,11 +26,6 @@
 
 __docformat__ = "restructuredtext"
 
-__svn_revision = '$Rev::         $'
-try:
-    __svn_revision = int(__svn_revision[7:14])
-except:
-    __svn_revision = 0
 
 """
 Release data for the taurus project. It contains the following members:
@@ -54,10 +49,8 @@ name = 'sardana'
 #: the new substring. We have to avoid using either dashes or underscores,
 #: because bdist_rpm does not accept dashes (an RPM) convention, and
 #: bdist_deb does not accept underscores (a Debian convention).
-version_info = (1, 2, 0, 'dev', __svn_revision)
+version_info = (1, 2, 0, 'rc', 0)
 version = '.'.join(map(str, version_info[:3]))
-
-#: svn revision number
 revision = str(version_info[4])
 
 description = "Sardana is a generic program for control applications in large and small installations"
@@ -69,7 +62,8 @@ and the standard basis of collaborations in control."""
 
 license = 'LGPL'
 
-authors = {'Tiago'          : ('Tiago Coutinho','tcoutinho at cells.es') }
+authors = {'Tiago'          : ('Tiago Coutinho','tiago.coutinho at esrf.fr'),
+           'Pascual-Izarra' : ('Carlos Pascual-Izarra','cpascual at cells.es') }
 
 url = 'http://packages.python.org/sardana'
 
diff --git a/src/sardana/requirements.py b/src/sardana/requirements.py
index de182d2..527255a 100644
--- a/src/sardana/requirements.py
+++ b/src/sardana/requirements.py
@@ -35,7 +35,7 @@ __requires__ = {
 #     module        minimum
     "Python"      : (2,6,0),
     "PyTango"     : (7,2,3),
-    "taurus.core" : (3,0,0),
+    "taurus.core" : (3,1,0),
 }
 
 def check_requirements(exec_name=None):
@@ -93,7 +93,7 @@ def check_requirements(exec_name=None):
         sys.exit(-1)
     
     try:
-        from taurus.core.util import etree
+        from lxml import etree
     except:
         print "Could not find any suitable XML library"
         sys.exit(-1)
diff --git a/src/sardana/spock/magic.py b/src/sardana/spock/magic.py
index f67b21f..5953ee2 100644
--- a/src/sardana/spock/magic.py
+++ b/src/sardana/spock/magic.py
@@ -45,9 +45,24 @@ def expconf(self, parameter_s=''):
     except:
         print "Error importing ExpDescriptionEditor "\
               "(hint: is taurus extra_sardana installed?)"
+        return
     doorname = get_door().name()
-    w = ExpDescriptionEditor(door=doorname)
-    w.show()
+        
+    #===========================================================================
+    ## ugly hack to avoid ipython/qt thread problems #e.g. see
+    ## https://sourceforge.net/p/sardana/tickets/10/ 
+    ## this hack does not allow inter-process communication and leaves the 
+    ## widget open after closing spock 
+    ## @todo: investigate cause of segfaults when using launching qt widgets from ipython
+    # 
+    #w = ExpDescriptionEditor(door=doorname)
+    #w.show() #launching it like this, produces the problem of https://sourceforge.net/p/sardana/tickets/10/
+    import subprocess
+    import sys
+    fname = sys.modules[ExpDescriptionEditor.__module__].__file__
+    args = ['python', fname, doorname]
+    subprocess.Popen(args)
+    #===========================================================================
 
 
 def showscan(self, parameter_s=''):
diff --git a/src/sardana/spock/release.py b/src/sardana/spock/release.py
index 5ffc26c..3a765da 100644
--- a/src/sardana/spock/release.py
+++ b/src/sardana/spock/release.py
@@ -52,7 +52,8 @@ replacement for the interactive Python interpreter with extra functionality.
 
 license = 'GNU'
 
-authors = {'Tiago' : ('Tiago Coutinho','tcoutinho at cells.es') }
+authors = {'Tiago'          : ('Tiago Coutinho','tiago.coutinho at esrf.fr'),
+           'Pascual-Izarra' : ('Carlos Pascual-Izarra','cpascual at cells.es') }
 
 url = ''
 
diff --git a/src/sardana/spock/spockms.py b/src/sardana/spock/spockms.py
index b0cd8ad..755ef12 100644
--- a/src/sardana/spock/spockms.py
+++ b/src/sardana/spock/spockms.py
@@ -63,10 +63,17 @@ class GUIViewer(BaseGUIViewer):
 
     def show_scan(self, scan_nb=None, scan_history_info=None, directory_map=None):
         if scan_nb is None and scan_history_info is None:
-            import taurus.qt.qtgui.plot
-            w = taurus.qt.qtgui.plot.TaurusTrend()
-            w.model = "scan://" + self._door.getNormalName()
-            w.show()
+            #===================================================================
+            ##Hack to avoid ipython-qt issues. See similar workaround in expconf magic command
+            ## @todo: do this in a better way
+            #import taurus.qt.qtgui.plot
+            #w = taurus.qt.qtgui.plot.TaurusTrend()
+            #w.model = "scan://" + self._door.getNormalName()
+            #w.show()
+            import subprocess
+            args = ['taurustrend','scan://%s'%self._door.getNormalName()]
+            subprocess.Popen(args)
+            #===================================================================
             return
 
         scan_dir, scan_file = None, None
diff --git a/src/sardana/tango/core/util.py b/src/sardana/tango/core/util.py
index 6bda8b8..ea630f7 100644
--- a/src/sardana/tango/core/util.py
+++ b/src/sardana/tango/core/util.py
@@ -785,7 +785,12 @@ def prepare_logging(options, args, tango_args, start_time=None, log_messages=Non
             _, ds_name = os.path.split(args[0])
             ds_name, _ = os.path.splitext(ds_name)
             ds_instance = args[-1].lower()
-            path = os.path.join(os.sep, "tmp", "tango", ds_name, ds_instance)
+            import getpass
+            try:
+                tangodir = 'tango-%s'%getpass.getuser() #include the user name to avoid permission errors
+            except:
+                tangodir = 'tango'%getpass.getuser()
+            path = os.path.join(os.sep, "tmp", tangodir, ds_name, ds_instance)
             log_file_name = os.path.join(path, 'log.txt')
         else:
             log_file_name = options.log_file_name
diff --git a/src/sardana/tango/macroserver/Door.py b/src/sardana/tango/macroserver/Door.py
index 8885c0e..218eb1a 100644
--- a/src/sardana/tango/macroserver/Door.py
+++ b/src/sardana/tango/macroserver/Door.py
@@ -37,7 +37,8 @@ from PyTango import Util, DevFailed, Except, DevVoid, DevShort, DevLong, \
 
 import taurus
 import taurus.core.util
-from taurus.core.util import etree, CodecFactory, DebugIt
+from lxml import etree
+from taurus.core.util import CodecFactory, DebugIt
 
 from sardana import State, InvalidId, SardanaServer
 from sardana.sardanaattribute import SardanaAttribute
diff --git a/src/sardana/tango/macroserver/MacroServer.py b/src/sardana/tango/macroserver/MacroServer.py
index 4d8b974..d0db368 100644
--- a/src/sardana/tango/macroserver/MacroServer.py
+++ b/src/sardana/tango/macroserver/MacroServer.py
@@ -98,6 +98,7 @@ class MacroServer(SardanaDevice):
             import tempfile
             env_db = os.path.join(tempfile.mkdtemp(),
                                   MacroServerClass.DefaultEnvRelDir)
+            env_db = self._calculate_name(env_db)
             db = Util.instance().get_database()
             db.put_device_property(self.get_name(), dict(EnvironmentDb=env_db))
             self.EnvironmentDb = env_db
diff --git a/src/sardana/tango/pool/Motor.py b/src/sardana/tango/pool/Motor.py
index e051421..f769621 100644
--- a/src/sardana/tango/pool/Motor.py
+++ b/src/sardana/tango/pool/Motor.py
@@ -547,7 +547,6 @@ with this value is sent to clients using events.
     def read_Limit_switches(self, attr):
         motor = self.motor
         use_cache = motor.is_in_operation() and not self.Force_HW_Read
-        self.info("read_Limit_switches(%s)", use_cache)
         limit_switches = motor.get_limit_switches(cache=use_cache)
         self.set_attribute(attr, value=limit_switches.value, priority=0,
                            timestamp=limit_switches.timestamp)
diff --git a/src/sardana/util/motion/motion.py b/src/sardana/util/motion/motion.py
index f2f4047..489c8ee 100644
--- a/src/sardana/util/motion/motion.py
+++ b/src/sardana/util/motion/motion.py
@@ -70,10 +70,24 @@ class MotionPath(object):
     #: time the motion will take
     duration = -1
 
-    def __init__(self, motor, initial_user_pos, final_user_pos):
+    def __init__(self, motor, 
+                       initial_user_pos, 
+                       final_user_pos, 
+                       active_time=None):
+        """MotionPath constructor - creates and calculates 
+        motion path parameters.
+        :param initial_user_pos: position at which constant vel 
+                                 should be reached
+        :param final_user_pos: position at which deceleration should 
+                               start
+        :param active_time: if passed, will fix the constant velocity 
+                            (abs(final_user_pos - initial_user_pos)/active_time)
+                            otherwise motor constant velocity
+                            will be selected as high as possible"""
         self.motor = motor
         self.initial_user_pos = initial_user_pos
         self.final_user_pos = final_user_pos
+        self.active_time = active_time
         self._calculateMotionPath()
 
     def setInitialUserPos(self, initial_user_pos):
@@ -93,6 +107,24 @@ class MotionPath(object):
         final_pos = final_user_pos * motor.step_per_unit
 
         displacement = abs(final_pos - initial_pos)
+                    
+        # in this case active_time forces that the user range
+        # correspond to the constant velocity
+        # and 
+        if self.active_time != None:
+            velocity = displacement / self.active_time
+            self.motor.setMaxVelocity(velocity)
+            sign = final_pos > initial_pos and 1 or -1
+            accel_time = motor.getAccelerationTime()
+            decel_time = motor.getDecelerationTime()
+            base_vel = motor.getMinVelocity()
+            accel_displacement = accel_time * 0.5 * (velocity + base_vel)
+            decel_displacement = decel_time * 0.5 * (velocity + base_vel)
+            initial_pos -= sign * accel_displacement
+            final_pos += sign * decel_displacement
+            displacement = abs(final_pos - initial_pos)            
+            self.initial_user_pos = initial_pos
+            self.final_user_pos = final_pos
 
         if displacement == 0:
             positive_displacement = False
@@ -201,12 +233,12 @@ class MotionPath(object):
         self.small_motion = small_motion
 
         self.accel = accel
-        self.decel = decel        
-        
+        self.decel = decel
+
         self.displacement_reach_max_vel = displacement_reach_max_vel
         self.displacement_reach_min_vel = displacement_reach_min_vel
-        self.max_vel = max_vel
-        self.min_vel = min_vel
+        self.max_vel = abs(max_vel) #velocity must be a positive value
+        self.min_vel = abs(min_vel)
         self.max_vel_pos = max_vel_pos
         self.at_max_vel_displacement = at_max_vel_displacement
         self.max_vel_time = max_vel_time
@@ -511,8 +543,10 @@ class Motor(BaseMotor):
 
         self.min_vel = vi
 
+        #TODO: consult this solution with others
         if self.max_vel < self.min_vel:
-            self.max_vel = self.min_vel
+            pass
+            #self.max_vel = self.min_vel (original version)
 
         # force recalculation of accelerations
         if self.accel_time >= 0:
@@ -530,9 +564,12 @@ class Motor(BaseMotor):
             raise Exception("Maximum velocity must be > 0")
 
         self.max_vel = vf
-
+        
+        #TODO: consult this solution with others
         if self.min_vel > self.max_vel:
-            self.min_vel = self.max_vel
+            pass
+            #self.min_vel = self.max_vel #accel set to zero (original version)
+            #self.setMinVelocity(0) another solution could be to set it to 0
 
         # force recalculation of accelerations
         if self.accel_time >= 0:
@@ -556,6 +593,7 @@ class Motor(BaseMotor):
             self.accel = float('inf')
 
         self.__recalculate_acc_constants()
+       
 
     def getAccelerationTime(self):
         return self.accel_time
diff --git a/src/sardana/util/tree.py b/src/sardana/util/tree.py
new file mode 100644
index 0000000..3fdb33f
--- /dev/null
+++ b/src/sardana/util/tree.py
@@ -0,0 +1,28 @@
+class BaseNode:
+    """BaseNode, stores reference to data."""
+    
+    def __init__(self, data):
+        self.data = data
+
+class BranchNode(BaseNode):
+    """BranchNode, apart of reference to data, stores a list of 
+    children Nodes."""
+    def __init__(self, data):
+        BaseNode.__init__(self, data)
+        self.children = []
+
+    def addChild(self, child):
+        self.children.append(child)
+
+class LeafNode(BaseNode):
+    """LeafMode, just stores reference to data."""
+    def __init__(self, data):
+        BaseNode.__init__(self, data)
+
+class Tree:
+    """Base tree class, stores reference to root Node object"""
+    def __init__(self, root):
+        self._root = root
+        
+    def root(self):
+        return self._root
\ No newline at end of file

-- 
Packaging for sardana



More information about the debian-science-commits mailing list