[GRASS-SVN] r55594 - in grass/trunk/gui/wxpython: . core gmodeler gui_core lmgr modules psmap scripts

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Apr 2 12:17:47 PDT 2013


Author: annakrat
Date: 2013-04-02 12:17:47 -0700 (Tue, 02 Apr 2013)
New Revision: 55594

Added:
   grass/trunk/gui/wxpython/core/menutree.py
   grass/trunk/gui/wxpython/core/treemodel.py
   grass/trunk/gui/wxpython/gui_core/treeview.py
Removed:
   grass/trunk/gui/wxpython/core/menudata.py
   grass/trunk/gui/wxpython/core/modulesdata.py
Modified:
   grass/trunk/gui/wxpython/Makefile
   grass/trunk/gui/wxpython/gmodeler/dialogs.py
   grass/trunk/gui/wxpython/gmodeler/frame.py
   grass/trunk/gui/wxpython/gmodeler/menudata.py
   grass/trunk/gui/wxpython/gui_core/goutput.py
   grass/trunk/gui/wxpython/gui_core/menu.py
   grass/trunk/gui/wxpython/gui_core/prompt.py
   grass/trunk/gui/wxpython/gui_core/query.py
   grass/trunk/gui/wxpython/gui_core/widgets.py
   grass/trunk/gui/wxpython/lmgr/frame.py
   grass/trunk/gui/wxpython/lmgr/menudata.py
   grass/trunk/gui/wxpython/modules/extensions.py
   grass/trunk/gui/wxpython/psmap/frame.py
   grass/trunk/gui/wxpython/psmap/menudata.py
   grass/trunk/gui/wxpython/scripts/vkrige.py
   grass/trunk/gui/wxpython/wxpythonlib.dox
Log:
wxGUI: refactoring tree structures (menu, search tree, extensions, query)

Modified: grass/trunk/gui/wxpython/Makefile
===================================================================
--- grass/trunk/gui/wxpython/Makefile	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/Makefile	2013-04-02 19:17:47 UTC (rev 55594)
@@ -26,9 +26,10 @@
 $(ETCDIR)/%: % | $(PYDSTDIRS) $(DSTDIRS)
 	$(INSTALL_DATA) $< $@
 
-menustrings.py: core/menudata.py $(ETCDIR)/xml/menudata.xml $(ETCDIR)/xml/menudata_modeler.xml 
+menustrings.py: core/menutree.py $(ETCDIR)/xml/menudata.xml $(ETCDIR)/xml/menudata_modeler.xml $(ETCDIR)/xml/menudata_psmap.xml
 	$(call run_grass,$(PYTHON) $< > $@)
 	$(call run_grass,$(PYTHON) $< "modeler" >> $@)
+	$(call run_grass,$(PYTHON) $< "psmap" >> $@)
 
 $(PYDSTDIRS): %: | $(ETCDIR)
 	$(MKDIR) $@

Deleted: grass/trunk/gui/wxpython/core/menudata.py
===================================================================
--- grass/trunk/gui/wxpython/core/menudata.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/core/menudata.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -1,233 +0,0 @@
-"""!
- at package core.menudata
-
- at brief Complex list for menu entries for wxGUI
-
-Classes:
- - menudata::MenuData
-
-Usage:
- at code
-python menudata.py [action] [manager|modeler]
- at endcode
-
-where <i>action</i>:
- - strings (default)
- - tree
- - commands
- - dump
-
-(C) 2007-2011 by the GRASS Development Team
-
-This program is free software under the GNU General Public License
-(>=v2). Read the file COPYING that comes with GRASS for details.
-
- at author Michael Barton (Arizona State University)
- at author Yann Chemin <yann.chemin gmail.com>
- at author Martin Landa <landa.martin gmail.com>
- at author Glynn Clements
-"""
-
-import os
-import sys
-import pprint
-try:
-    import xml.etree.ElementTree   as etree
-except ImportError:
-    import elementtree.ElementTree as etree # Python <= 2.4
-
-import wx
-
-if not os.getenv("GISBASE"):
-    sys.exit("GRASS is not running. Exiting...")
-
-class MenuData:
-    """!Abstract menu data class"""
-    def __init__(self, filename):
-	self.tree = etree.parse(filename)
-
-    def _getMenuItem(self, mi):
-        """!Get menu item
-
-        @param mi menu item instance
-        """
-	if mi.tag == 'separator':
-	    return ('', '', '', '', '')
-	elif mi.tag == 'menuitem':
-	    label    = _(mi.find('label').text)
-	    help     = _(mi.find('help').text)
-	    handler  = mi.find('handler').text
-	    gcmd     = mi.find('command')  # optional
-            keywords = mi.find('keywords') # optional
-            shortcut = mi.find('shortcut') # optional
-            wxId     = mi.find('id')       # optional
-	    if gcmd != None:
-		gcmd = gcmd.text
-	    else:
-		gcmd = ""
-            if keywords != None:
-                keywords = keywords.text
-            else:
-                keywords = ""
-            if shortcut != None:
-                shortcut = shortcut.text
-            else:
-                shortcut = ""
-            if wxId != None:
-                wxId = eval('wx.' + wxId.text)
-            else:
-                wxId = wx.ID_ANY
-	    return (label, help, handler, gcmd, keywords, shortcut, wxId)
-	elif mi.tag == 'menu':
-	    return self._getMenu(mi)
-	else:
-	    raise Exception(_("Unknow tag"))
-
-    def _getMenu(self, m):
-        """!Get menu
-
-        @param m menu
-
-        @return label, menu items
-        """
-	label = _(m.find('label').text)
-	items = m.find('items')
-	return (label, tuple(map(self._getMenuItem, items)))
-    
-    def _getMenuBar(self, mb):
-        """!Get menu bar
-
-        @param mb menu bar instance
-        
-        @return menu items
-        """
-	return tuple(map(self._getMenu, mb.findall('menu')))
-
-    def _getMenuData(self, md):
-        """!Get menu data
-
-        @param md menu data instace
-        
-        @return menu data
-        """
-	return list(map(self._getMenuBar, md.findall('menubar')))
-
-    def GetMenu(self):
-        """!Get menu
-
-        @return menu data
-        """
-	return self._getMenuData(self.tree.getroot())
-
-    def PrintStrings(self, fh):
-        """!Print menu strings to file (used for localization)
-
-        @param fh file descriptor"""
-        className = str(self.__class__).split('.', 1)[1]
-	fh.write('menustrings_%s = [\n' % className)
-	for node in self.tree.getiterator():
-	    if node.tag in ['label', 'help']:
-		fh.write('     _(%r),\n' % node.text)
-	fh.write('    \'\']\n')
-
-    def PrintTree(self, fh):
-        """!Print menu tree to file
-
-        @param fh file descriptor"""
-        level = 0
-        for eachMenuData in self.GetMenu():
-            for label, items in eachMenuData:
-                fh.write('- %s\n' % label.replace('&', ''))
-                self._PrintTreeItems(fh, level + 1, items)
-        
-    def _PrintTreeItems(self, fh, level, menuData):
-        """!Print menu tree items to file (used by PrintTree)
-
-        @param fh file descriptor
-        @param level menu level
-        @param menuData menu data to print out"""
-        for eachItem in menuData:
-            if len(eachItem) == 2:
-                if eachItem[0]:
-                    fh.write('%s - %s\n' % (' ' * level, eachItem[0]))
-                self._PrintTreeItems(fh, level + 1, eachItem[1])
-            else:
-                if eachItem[0]:
-                    fh.write('%s - %s\n' % (' ' * level, eachItem[0]))
-    
-    def PrintCommands(self, fh, itemSep = ' | ', menuSep = ' > '):
-        """!Print commands list (command | menu item > menu item)
-
-        @param fh file descriptor
-        """
-        level = 0
-        for eachMenuData in self.GetMenu():
-            for label, items in eachMenuData:
-                menuItems = [label, ]
-                self._PrintCommandsItems(fh, level + 1, items,
-                                         menuItems, itemSep, menuSep)
-        
-    def _PrintCommandsItems(self, fh, level, menuData,
-                             menuItems, itemSep, menuSep):
-        """!Print commands item (used by PrintCommands)
-
-        @param fh file descriptor
-        @param menuItems list of menu items
-        """
-        for eachItem in menuData:
-            if len(eachItem) == 2:
-                if eachItem[0]:
-                    try:
-                        menuItems[level] = eachItem[0]
-                    except IndexError:
-                        menuItems.append(eachItem[0])
-                self._PrintCommandsItems(fh, level + 1, eachItem[1],
-                                          menuItems, itemSep, menuSep)
-            else:
-                try:
-                    del menuItems[level]
-                except IndexError:
-                    pass
-                
-                if eachItem[3]:
-                    fh.write('%s%s' % (eachItem[3], itemSep))
-                    fh.write(menuSep.join(map(lambda x: x.replace('&', ''), menuItems)))
-                    fh.write('%s%s' % (menuSep, eachItem[0]))
-                    fh.write('\n')
-
-if __name__ == "__main__":
-    import os
-    import sys
-    
-    # i18N
-    import gettext
-    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
-
-    action = 'strings'
-    menu   = 'manager'
-    
-    for arg in sys.argv:
-        if arg in ('strings', 'tree', 'commands', 'dump'):
-            action =  arg
-        elif arg in ('manager', 'modeler'):
-            menu = arg
-    
-    sys.path.append(os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython"))
-    
-    if menu == 'manager':
-        from lmgr.menudata     import LayerManagerMenuData
-        data = LayerManagerMenuData()
-    else:
-        from gmodeler.menudata import ModelerMenuData
-        data = ModelerMenuData()
-    
-    if action == 'strings':
-        data.PrintStrings(sys.stdout)
-    elif action == 'tree':
-        data.PrintTree(sys.stdout)
-    elif action == 'commands':
-        data.PrintCommands(sys.stdout)
-    elif action == 'dump':
-	pprint.pprint(data.GetMenu(), stream = sys.stdout, indent = 2)
-    
-    sys.exit(0)

Added: grass/trunk/gui/wxpython/core/menutree.py
===================================================================
--- grass/trunk/gui/wxpython/core/menutree.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/menutree.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -0,0 +1,233 @@
+"""!
+ at package core.menutree
+
+ at brief Creates tree structure for wxGUI menus (former menudata.py)
+
+Classes:
+ - menutree::MenuTreeModelBuilder
+
+Usage:
+ at code
+python menutree.py [action] [menu]
+ at endcode
+
+where <i>action</i>:
+ - strings (default, used for translations)
+ - tree (simple tree structure)
+ - commands (command names and their place in tree)
+ - dump (tree structure with stored data)
+
+and <i>menu</i>:
+ - manager (Layer Manager)
+ - modeler (Graphical Modeler)
+ - psmap (Cartographic Composer)
+
+(C) 2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Glynn Clements (menudata.py)
+ at author Martin Landa <landa.martin gmail.com> (menudata.py)
+ at author Anna Petrasova <kratochanna gmail.com>
+"""
+
+import os
+import sys
+import copy
+try:
+    import xml.etree.ElementTree   as etree
+except ImportError:
+    import elementtree.ElementTree as etree # Python <= 2.4
+
+import wx
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+from core.treemodel import TreeModel, ModuleNode
+from core.settings import UserSettings
+
+if not os.getenv("GISBASE"):
+    sys.exit("GRASS is not running. Exiting...")
+
+class MenuTreeModelBuilder:
+    """!Abstract menu data class"""
+    def __init__(self, filename):
+
+        self.menustyle = UserSettings.Get(group = 'appearance',
+                                          key = 'menustyle',
+                                          subkey = 'selection')
+
+        xmlTree = etree.parse(filename)
+        self.model = TreeModel(ModuleNode)
+        self._createModel(xmlTree)
+
+    def _createModel(self, xmlTree):
+        root = xmlTree.getroot()
+        menubar = root.findall('menubar')[0]
+        menus = menubar.findall('menu')
+        for m in menus:
+            self._createMenu(m, self.model.root)
+
+    def _createMenu(self, menu, node):
+        label = _(menu.find('label').text)
+        items = menu.find('items')
+        node = self.model.AppendNode(parent=node, label=label)
+        for item in items:
+            self._createItem(item, node)
+
+    def _createItem(self, item, node):
+        if item.tag == 'separator':
+            data = dict(label='', description='', handler='',
+                        command='', keywords='', shortcut='', wxId='')
+            self.model.AppendNode(parent=node, label='', data=data)
+        elif item.tag == 'menuitem':
+            label    = _(item.find('label').text)
+            desc     = _(item.find('help').text)
+            handler  = item.find('handler').text
+            gcmd     = item.find('command')  # optional
+            keywords = item.find('keywords') # optional
+            shortcut = item.find('shortcut') # optional
+            wxId     = item.find('id')       # optional
+            if gcmd != None:
+                gcmd = gcmd.text
+            else:
+                gcmd = ""
+            if keywords != None:
+                keywords = keywords.text
+            else:
+                keywords = ""
+            if shortcut != None:
+                shortcut = shortcut.text
+            else:
+                shortcut = ""
+            if wxId != None:
+                wxId = eval('wx.' + wxId.text)
+            else:
+                wxId = wx.ID_ANY
+            if gcmd:
+                if self.menustyle == 1:
+                    label += '   [' + gcmd + ']'
+                elif self.menustyle == 2:
+                    label = '      [' + gcmd + ']'
+            data = dict(label=label, description=desc, handler=handler,
+                        command=gcmd, keywords=keywords, shortcut=shortcut, wxId=wxId)
+            self.model.AppendNode(parent=node, label=label, data=data)
+        elif item.tag == 'menu':
+            self._createMenu(item, node)
+        else:
+            raise Exception(_("Unknow tag %s") % item.tag)
+
+    def GetModel(self, separators=False):
+        """Returns copy of model with or without separators
+        (for menu or for search tree).
+        """
+        if separators:
+            return copy.deepcopy(self.model)
+        else:
+            model = copy.deepcopy(self.model)
+            removeSeparators(model)
+            return model
+
+    def PrintTree(self, fh):
+        for child in self.model.root.children:
+            printTree(node=child, fh=fh)
+
+    def PrintStrings(self, fh):
+        """!Print menu strings to file (used for localization)
+
+        @param fh file descriptor
+        """
+        className = str(self.__class__).split('.', 1)[1]
+        fh.write('menustrings_%s = [\n' % className)
+        for child in self.model.root.children:
+            printStrings(child, fh)
+        fh.write('    \'\']\n')
+
+    def PrintCommands(self, fh):
+        printCommands(self.model.root, fh, itemSep=' | ', menuSep=' > ')
+
+def removeSeparators(model, node=None):
+    if not node:
+        node = model.root
+    if node.label:
+        for child in reversed(node.children):
+            removeSeparators(model, child)
+    else:
+        model.RemoveNode(node)
+
+def printTree(node, fh, indent=0):
+    if not node.label:
+        return
+    text = '%s- %s\n' % (' ' * indent, node.label.replace('&', ''))
+    fh.write(text)
+    for child in node.children:
+        printTree(node=child, fh=fh, indent=indent + 2)
+
+def printStrings(node, fh):
+    if node.label:
+        fh.write('     _(%r),\n' % str(node.label))
+    if node.data:
+        if 'description' in node.data and node.data['description']:
+            fh.write('     _(%r),\n' % str(node.data['description']))
+    for child in node.children:
+        printStrings(node=child, fh=fh)
+
+def printCommands(node, fh, itemSep, menuSep):
+
+    def collectParents(node, parents):
+        parent = node.parent
+        if parent.parent:
+            parents.insert(0, node.parent)
+            collectParents(node.parent, parents)
+
+    data = node.data
+    if data and 'command' in data and data['command']:
+        fh.write('%s%s' % (data['command'], itemSep))
+        parents = [node]
+        collectParents(node, parents)
+        labels = [parent.label.replace('&', '') for parent in parents]
+        fh.write(menuSep.join(labels))
+        fh.write('\n')
+
+    for child in node.children:
+        printCommands(child, fh, itemSep, menuSep)
+
+
+if __name__ == "__main__":
+    # i18N
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
+
+    action = 'strings'
+    menu   = 'manager'
+
+    for arg in sys.argv:
+        if arg in ('strings', 'tree', 'commands', 'dump'):
+            action =  arg
+        elif arg in ('manager', 'modeler', 'psmap'):
+            menu = arg
+
+    sys.path.append(os.path.join(os.getenv("GISBASE"), "etc", "gui", "wxpython"))
+
+
+    if menu == 'manager':
+        from lmgr.menudata     import LayerManagerMenuData
+        menudata = LayerManagerMenuData()
+    elif menu == 'modeler':
+        from gmodeler.menudata import ModelerMenuData
+        menudata = ModelerMenuData()
+    elif menu == 'psmap':
+        from psmap.menudata import PsMapMenuData
+        menudata = PsMapMenuData()
+
+    if action == 'strings':
+        menudata.PrintStrings(sys.stdout)
+    elif action == 'tree':
+        menudata.PrintTree(sys.stdout)
+    elif action == 'commands':
+        menudata.PrintCommands(sys.stdout)
+    elif action == 'dump':
+        print menudata.model
+
+    sys.exit(0)

Deleted: grass/trunk/gui/wxpython/core/modulesdata.py
===================================================================
--- grass/trunk/gui/wxpython/core/modulesdata.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/core/modulesdata.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -1,189 +0,0 @@
-"""!
- at package core.modulesdata
-
- at brief Provides information about available modules
-
-Classes:
- - modules::modulesdata
-
-(C) 2009-2012 by the GRASS Development Team
-
-This program is free software under the GNU General Public License
-(>=v2). Read the file COPYING that comes with GRASS for details.
-
- at author Martin Landa <landa.martin gmail.com>
- at author Vaclav Petras <wenzeslaus gmail.com>
- at author Anna Kratochvilova <kratochanna gmail.com>
-"""
-
-import sys
-import os
-
-if __name__ == '__main__':
-    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
-
-from core import globalvar
-from lmgr.menudata import LayerManagerMenuData
-
-
-class ModulesData(object):
-    """!Holds information about modules.
-
-    @todo add doctest
-    @todo analyze what exactly this class is doing
-    @todo split this class into two classes
-
-    @see modules::extensions::ExtensionModulesData
-    """
-    def __init__(self, modulesDesc = None):
-
-        if modulesDesc is not None:
-            self.moduleDesc = modulesDesc
-        else:
-            self.moduleDesc = LayerManagerMenuData().GetModules()
-
-        self.moduleDict = self.GetDictOfModules()
-
-    def GetCommandDesc(self, cmd):
-        """!Gets the description for a given module (command).
-
-        If the given module is not available, an empty string is returned.
-        
-        \code
-        print data.GetCommandDesc('r.info')
-        Outputs basic information about a raster map.
-        \endcode
-        """
-        if cmd in self.moduleDesc:
-            return self.moduleDesc[cmd]['description']
-
-        return ''
-
-    def GetCommandItems(self):
-        """!Gets list of available modules (commands).
-
-        The list contains available module names.
-
-        \code
-        print data.GetCommandItems()[0:4]
-        ['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate']
-        \endcode
-        """
-        items = list()
-
-        mList = self.moduleDict
-
-        prefixes = mList.keys()
-        prefixes.sort()
-
-        for prefix in prefixes:
-            for command in mList[prefix]:
-                name = prefix + '.' + command
-                if name not in items:
-                    items.append(name)
-
-        items.sort()
-
-        return items
-
-    def GetDictOfModules(self):
-        """!Gets modules as a dictionary optimized for autocomplete.
-
-        \code
-        print data.GetDictOfModules()['r'][0:4]
-        print data.GetDictOfModules()['r.li'][0:4]
-        r: ['basins.fill', 'bitpattern', 'blend', 'buffer']
-        r.li: ['cwed', 'dominance', 'edgedensity', 'mpa']
-        \endcode
-        """
-        result = dict()
-        for module in globalvar.grassCmd:
-            try:
-                group, name = module.split('.', 1)
-            except ValueError:
-                continue  # TODO
-
-            if group not in result:
-                result[group] = list()
-            result[group].append(name)
-
-            # for better auto-completion:
-            # not only result['r']={...,'colors.out',...}
-            # but also result['r.colors']={'out',...}
-            for i in range(len(name.split('.')) - 1):
-                group = '.'.join([group, name.split('.', 1)[0]])
-                name = name.split('.', 1)[1]
-                if group not in result:
-                    result[group] = list()
-                result[group].append(name)
-
-        # sort list of names
-        for group in result.keys():
-            result[group].sort()
-
-        return result
-
-    def FindModules(self, text, findIn):
-        """!Finds modules according to given text.
-
-        @param text string to search
-        @param findIn where to search for text
-        (allowed values are 'description', 'keywords' and 'command')
-        """
-        modules = dict()
-        iFound = 0
-        for module, data in self.moduleDesc.iteritems():
-            found = False
-            if findIn == 'description':
-                if text in data['description']:
-                    found = True
-            elif findIn == 'keywords':
-                if text in ','.join(data['keywords']):
-                    found = True
-            elif findIn == 'command':
-                if module[:len(text)] == text:
-                    found = True
-            else:
-                raise ValueError("Parameter findIn is not valid")
-
-            if found:
-                try:
-                    group, name = module.split('.')
-                except ValueError:
-                    continue # TODO                
-                iFound += 1
-                if group not in modules:
-                    modules[group] = list()
-                modules[group].append(name)
-        return modules, iFound
-
-    def SetFilter(self, data = None):
-        """!Sets filter modules
-
-        If @p data is not specified, module dictionary is derived
-        from an internal data structures.
-        
-        @todo Document this method.
-
-        @param data data dict
-        """
-        if data:
-            self.moduleDict = data
-        else:
-            self.moduleDict = self.GetDictOfModules()
-
-
-def test():
-    data = ModulesData()
-    module = 'r.info'
-    print '%s:' % module, data.GetCommandDesc(module)
-    print '[0:5]:', data.GetCommandItems()[0:5]
-
-    modules = data.GetDictOfModules()
-    print 'r:', modules['r'][0:4]
-    print 'r.li:', modules['r.li'][0:4]
-    
-
-
-if __name__ == '__main__':
-    test()
\ No newline at end of file

Added: grass/trunk/gui/wxpython/core/treemodel.py
===================================================================
--- grass/trunk/gui/wxpython/core/treemodel.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/treemodel.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -0,0 +1,209 @@
+"""!
+ at package core.treemodel
+
+ at brief tree structure model (used for menu, search tree)
+
+Classes:
+ - treemodel::TreeModel
+ - treemodel::DictNode
+ - treemodel::ModuleNode
+
+(C) 2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Anna Petrasova <kratochanna gmail.com>
+"""
+
+
+class TreeModel(object):
+    """!Class represents a tree structure with hidden root.
+    
+    TreeModel is used together with TreeView class to display results in GUI.
+    The functionality is not complete, only needed methods are implemented.
+    If needed, the functionality can be extended.
+    
+    >>> tree = TreeModel(DictNode)
+    >>> root = tree.root
+    >>> n1 = tree.AppendNode(parent=root, label='node1')
+    >>> n2 = tree.AppendNode(parent=root, label='node2')
+    >>> n11 = tree.AppendNode(parent=n1, label='node11', data={'xxx': 1})
+    >>> n111 = tree.AppendNode(parent=n11, label='node111', data={'xxx': 4})
+    >>> n12 = tree.AppendNode(parent=n1, label='node12', data={'xxx': 2})
+    >>> n21 = tree.AppendNode(parent=n2, label='node21', data={'xxx': 1})
+    >>> [node.label for node in tree.SearchNodes(key='xxx', value=1)]
+    ['node11', 'node21']
+    >>> [node.label for node in tree.SearchNodes(key='xxx', value=5)]
+    []
+    >>> tree.GetIndexOfNode(n111)
+    [0, 0, 0]
+    >>> tree.GetNodeByIndex((0,1)).label
+    'node12'
+    >>> print tree
+    node1
+      node11
+        * xxx : 1
+        node111
+          * xxx : 4
+      node12
+        * xxx : 2
+    node2
+      node21
+        * xxx : 1
+    """
+    def __init__(self, nodeClass):
+        """!Constructor creates root node.
+
+        @param nodeClass class which is used for creating nodes
+        """
+        self._root = nodeClass('root')
+        self.nodeClass = nodeClass
+
+    @property
+    def root(self):
+        return self._root
+
+    def AppendNode(self, parent, label, data=None):
+        """!Create node and append it to parent node.
+        
+        @param parent parent node of the new node
+        @param label node label
+        @param data optional node data
+        
+        @return new node
+        """
+        node = self.nodeClass(label=label, data=data)
+        parent.children.append(node)
+        node.parent = parent
+        return node
+
+    def SearchNodes(self, **kwargs):
+        """!Search nodes according to specified attributes."""
+        nodes = []
+        self._searchNodes(node=self.root, foundNodes=nodes, **kwargs)
+        return nodes
+        
+    def _searchNodes(self, node, foundNodes, **kwargs):
+        """!Helper method for searching nodes."""
+        if node.match(**kwargs):
+            foundNodes.append(node)
+        for child in node.children:
+            self._searchNodes(node=child, foundNodes=foundNodes, **kwargs)
+
+    def GetNodeByIndex(self, index):
+        """!Method used for communication between view (VirtualTree) and model.
+
+        @param index index of node, as defined in VirtualTree doc
+        (e.g. root ~ [], second node of a first node ~ [0, 1])
+        """
+        if len(index) == 0:
+            return self.root
+        return self._getNode(self.root, index)
+        
+    def GetIndexOfNode(self, node):
+        """!Method used for communication between view (VirtualTree) and model."""
+        index = []
+        return self._getIndex(node, index)
+        
+        
+    def _getIndex(self, node, index):
+        if node.parent:
+            index.insert(0, node.parent.children.index(node))
+            return self._getIndex(node.parent, index)
+        return index
+        
+        
+    def GetChildrenByIndex(self, index):
+        """!Method used for communication between view (VirtualTree) and model."""
+        if len(index) == 0:
+            return self.root.children
+        node = self._getNode(self.root, index)
+        return node.children
+        
+    def _getNode(self, node, index):
+        if len(index) == 1:
+            return node.children[index[0]]
+        else:
+            return self._getNode(node.children[index[0]], index[1:])
+
+    def RemoveNode(self, node):
+        """!Removes node."""
+        if node.parent:
+            node.parent.children.remove(node)
+
+    def __str__(self):
+        """!Print tree."""
+        text = []
+        for child in self.root.children:
+            child.nprint(text)
+        return "\n".join(text)
+
+
+class DictNode(object):
+    """!Node which has data in a form of dictionary."""
+    def __init__(self, label, data=None):
+        """!Create node.
+
+        @param label node label (displayed in GUI)
+        @param data data as dictionary or None
+        """
+
+        self.label = label
+        if data == None:
+            self.data = dict()
+        else:
+            self.data = data
+        self._children = []
+        self.parent = None
+
+    @property
+    def children(self):
+        return self._children
+
+    def nprint(self, text, indent=0):
+        text.append(indent * ' ' + self.label)
+        if self.data:
+            for key, value in self.data.iteritems():
+                text.append("%(indent)s* %(key)s : %(value)s" % {'indent': (indent + 2) * ' ',
+                                                                 'key': key,
+                                                                 'value': value})
+
+        if self.children:
+            for child in self.children:
+                child.nprint(text, indent + 2)
+
+    def match(self, key, value):
+        """!Method used for searching according to given parameters.
+
+        @param value dictionary value to be matched
+        @param key data dictionary key
+        """
+        if key in self.data and self.data[key] == value:
+            return True
+        return False
+
+
+class ModuleNode(DictNode):
+    """!Node representing module."""
+    def __init__(self, label, data=None):
+        super(ModuleNode, self).__init__(label=label, data=data)
+
+    def match(self, key, value):
+        """!Method used for searching according to command,
+        keywords or description."""
+        if not self.data:
+            return False
+        if key in ('command', 'keywords', 'description'):
+            return len(self.data[key]) and value in self.data[key]
+        
+        return False
+            
+        
+def main():
+    import doctest
+    doctest.testmod()
+
+
+if __name__ == '__main__':
+    main()

Modified: grass/trunk/gui/wxpython/gmodeler/dialogs.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/dialogs.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gmodeler/dialogs.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -31,7 +31,6 @@
 
 from core                 import globalvar
 from core                 import utils
-from core.modulesdata     import ModulesData
 from gui_core.widgets     import SearchModuleWidget, SimpleValidator
 from core.gcmd            import GError, EncodeString
 from gui_core.dialogs     import SimpleDialog, MapLayersDialogForModeler
@@ -39,6 +38,7 @@
 from gui_core.forms       import CmdPanel
 from gui_core.gselect     import Select, ElementSelect
 from gmodeler.model       import *
+from lmgr.menudata        import LayerManagerMenuData
 
 from grass.script import task as gtask
 
@@ -138,7 +138,7 @@
             self.Destroy()
 
 class ModelSearchDialog(wx.Dialog):
-    def __init__(self, parent, id = wx.ID_ANY, title = _("Add new GRASS module to the model"),
+    def __init__(self, parent, title = _("Add new GRASS module to the model"),
                  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
         """!Graphical modeler module search window
         
@@ -149,7 +149,7 @@
         """
         self.parent = parent
         
-        wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = title, **kwargs)
         self.SetName("ModelerDialog")
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         
@@ -158,12 +158,14 @@
         
         self.cmdBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
                                    label=" %s " % _("Command"))
-
-        modulesData = ModulesData()
-        self.cmd_prompt = GPromptSTC(parent = self, modulesData = modulesData, updateCmdHistory = False)
+        
+        # menu data for search widget and prompt
+        menuModel = LayerManagerMenuData()
+        
+        self.cmd_prompt = GPromptSTC(parent = self, menuModel = menuModel.GetModel(), updateCmdHistory = False)
         self.cmd_prompt.promptRunCmd.connect(self.OnCommand)
         self.search = SearchModuleWidget(parent = self.panel,
-                                         modulesData = modulesData,
+                                         model = menuModel.GetModel(),
                                          showTip = True)
         self.search.moduleSelected.connect(lambda name:
                                            self.cmd_prompt.SetTextAndFocus(name + ' '))
@@ -172,10 +174,7 @@
         self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
         self.btnOk     = wx.Button(self.panel, wx.ID_OK)
         self.btnOk.SetDefault()
-        self.btnOk.Enable(False)
 
-        self.cmd_prompt.Bind(wx.EVT_KEY_UP, self.OnText)
-        self.search.searchChoice.Bind(wx.EVT_CHOICE, self.OnText)
         self.Bind(wx.EVT_BUTTON, self.OnOk, self.btnOk)
         self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
         
@@ -256,31 +255,10 @@
         """Cancel pressed, close window"""
         self.Hide()
         
-    def OnText(self, event):
-        """!Text in prompt changed"""
-        if self.cmd_prompt.AutoCompActive():
-            event.Skip()
-            return
-        
-        if isinstance(event, wx.KeyEvent):
-            entry = self.cmd_prompt.GetTextLeft()
-        elif isinstance(event, wx.stc.StyledTextEvent):
-            entry = event.GetText()
-        else:
-            entry = event.GetString()
-        
-        if entry:
-            self.btnOk.Enable()
-        else:
-            self.btnOk.Enable(False)
-            
-        event.Skip()
-        
     def Reset(self):
         """!Reset dialog"""
         self.search.Reset()
         self.cmd_prompt.OnCmdErase(None)
-        self.btnOk.Enable(False)
         self.cmd_prompt.SetFocus()
 
 class ModelRelationDialog(wx.Dialog):

Modified: grass/trunk/gui/wxpython/gmodeler/frame.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/frame.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gmodeler/frame.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -42,7 +42,6 @@
 from gui_core.dialogs     import GetImageHandlers
 from gui_core.preferences import PreferencesBaseDialog
 from core.settings        import UserSettings
-from core.menudata        import MenuData
 from gui_core.menu        import Menu
 from gmodeler.menudata    import ModelerMenuData
 from gui_core.forms       import GUI
@@ -82,8 +81,7 @@
         self.SetName("Modeler")
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         
-        self.menubar = Menu(parent = self, data = ModelerMenuData())
-        
+        self.menubar = Menu(parent = self, model = ModelerMenuData().GetModel())        
         self.SetMenuBar(self.menubar)
         
         self.toolbar = ModelerToolbar(parent = self)

Modified: grass/trunk/gui/wxpython/gmodeler/menudata.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/menudata.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gmodeler/menudata.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -16,14 +16,13 @@
 
 import os
 
-from core                 import globalvar
-from core.menudata        import MenuData
+from core import globalvar
+from core.menutree  import MenuTreeModelBuilder
 
-class ModelerMenuData(MenuData):
+class ModelerMenuData(MenuTreeModelBuilder):
     def __init__(self, filename = None):
         if not filename:
-            gisbase = os.getenv('GISBASE')
-	    filename = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_modeler.xml')
+            filename = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_modeler.xml')
         
-        MenuData.__init__(self, filename)
+        MenuTreeModelBuilder.__init__(self, filename)
 

Modified: grass/trunk/gui/wxpython/gui_core/goutput.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/goutput.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gui_core/goutput.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -38,7 +38,6 @@
 from gui_core.prompt import GPromptSTC
 from core.settings   import UserSettings
 from gui_core.widgets import SearchModuleWidget
-from core.modulesdata import ModulesData
 
 
 GC_EMPTY = 0
@@ -54,19 +53,19 @@
 class GConsoleWindow(wx.SplitterWindow):
     """!Create and manage output console for commands run by GUI.
     """
-    def __init__(self, parent, gconsole, margin = False,
+    def __init__(self, parent, gconsole, menuModel = None, margin = False,
                  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
                  gcstyle = GC_EMPTY,
                  **kwargs):
         """!
         @param parent gui parent
+        @param gconsole console logic
+        @param menuModel tree model of modules (from menu)
         @param margin use margin in output pane (GStc)
         @param style wx.SplitterWindow style
         @param gcstyle GConsole style
         (GC_EMPTY, GC_PROMPT to show command prompt,
         GC_SEARCH to show search widget)
-        @param ignoredCmdPattern regular expression specifying commads
-        to be ignored (e.g. @c '^d\..*' for display commands)
         """
         wx.SplitterWindow.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs)
         self.SetName("GConsole")
@@ -77,6 +76,7 @@
         # initialize variables
         self.parent = parent # GMFrame | CmdPanel | ?
         self._gconsole = gconsole
+        self._menuModel = menuModel
 
         self._gcstyle = gcstyle
         self.lineWidth       = 80
@@ -115,13 +115,10 @@
                                wrap = None)
         self.cmdOutput.Bind(stc.EVT_STC_CHANGE, self.OnStcChanged)
 
-        # information about available modules
-        modulesData = ModulesData()
-
         # search & command prompt
         # move to the if below
         # search depends on cmd prompt
-        self.cmdPrompt = GPromptSTC(parent = self, modulesData = modulesData)
+        self.cmdPrompt = GPromptSTC(parent=self, menuModel=self._menuModel)
         self.cmdPrompt.promptRunCmd.connect(lambda cmd:
                                             self._gconsole.RunCmd(command=cmd))
         self.cmdPrompt.showNotification.connect(self.showNotification)
@@ -137,7 +134,7 @@
                                                  label = self.infoCollapseLabelExp,
                                                  style = wx.CP_DEFAULT_STYLE |
                                                  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
-            self.MakeSearchPaneContent(self.searchPane.GetPane(), modulesData)
+            self.MakeSearchPaneContent(self.searchPane.GetPane(), self._menuModel)
             self.searchPane.Collapse(True)
             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane) 
             self.search.moduleSelected.connect(lambda name:
@@ -256,12 +253,12 @@
         self.SetAutoLayout(True)
         self.Layout()
 
-    def MakeSearchPaneContent(self, pane, modulesData):
+    def MakeSearchPaneContent(self, pane, model):
         """!Create search pane"""
         border = wx.BoxSizer(wx.VERTICAL)
         
         self.search = SearchModuleWidget(parent = pane,
-                                         modulesData = modulesData)
+                                         model = model)
 
         self.search.showNotification.connect(self.showNotification)
 
@@ -722,8 +719,11 @@
 
         panel = wx.Panel(self, id = wx.ID_ANY)
         
+        from lmgr.menudata import LayerManagerMenuData
+        menuTreeBuilder = LayerManagerMenuData()
         self.gconsole = GConsole(guiparent=self)
         self.goutput = GConsoleWindow(parent = panel, gconsole = self.gconsole,
+                                      menuModel=menuTreeBuilder.GetModel(),
                                       gcstyle = GC_SEARCH | GC_PROMPT)
 
         mainSizer = wx.BoxSizer(wx.VERTICAL)

Modified: grass/trunk/gui/wxpython/gui_core/menu.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/menu.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gui_core/menu.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -6,7 +6,6 @@
 Classes:
  - menu::Menu
  - menu::SearchModuleWindow
- - menu::MenuTree
 
 (C) 2010-2012 by the GRASS Development Team
 
@@ -24,44 +23,39 @@
 
 from core              import globalvar
 from core              import utils
-from core.modulesdata  import ModulesData
 from core.gcmd         import EncodeString
-from core.settings     import UserSettings
-from gui_core.widgets  import ItemTree, SearchModuleWidget
-from lmgr.menudata     import LayerManagerMenuData
+from gui_core.widgets  import SearchModuleWidget
+from gui_core.treeview import CTreeView
 
+from grass.pydispatch.signal import Signal
+
 class Menu(wx.MenuBar):
-    def __init__(self, parent, data):
+    def __init__(self, parent, model):
         """!Creates menubar"""
         wx.MenuBar.__init__(self)
-        self.parent   = parent
-        self.menudata = data
-        self.menucmd  = dict()
+        self.parent = parent
+        self.model = model
+        self.menucmd = dict()
+
+        for child in self.model.root.children:
+            self.Append(self._createMenu(child), child.label)
         
-        self.menustyle = UserSettings.Get(group = 'appearance', key = 'menustyle', subkey = 'selection')
-        
-        for eachMenuData in self.menudata.GetMenu():
-            for eachHeading in eachMenuData:
-                menuLabel = eachHeading[0]
-                menuItems = eachHeading[1]
-                self.Append(self._createMenu(menuItems), menuLabel)
-        
-    def _createMenu(self, menuData):
+    def _createMenu(self, node):
         """!Creates menu"""
         menu = wx.Menu()
-        for eachItem in menuData:
-            if len(eachItem) == 2:
-                label = eachItem[0]
-                subMenu = self._createMenu(eachItem[1])
+        for child in node.children:
+            if child.children:
+                label = child.label
+                subMenu = self._createMenu(child)
                 menu.AppendMenu(wx.ID_ANY, label, subMenu)
             else:
-                self._createMenuItem(menu, self.menustyle, *eachItem)
+                self._createMenuItem(menu, **child.data)
         
         self.parent.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight)
         
         return menu
 
-    def _createMenuItem(self, menu, menustyle, label, help, handler, gcmd, keywords,
+    def _createMenuItem(self, menu, label, description, handler, command, keywords,
                         shortcut = '', wxId = wx.ID_ANY, kind = wx.ITEM_NORMAL):
         """!Creates menu items
         There are three menu styles (menu item text styles).
@@ -71,42 +65,37 @@
             menu.AppendSeparator()
             return
         
-        if gcmd:
-            helpString = gcmd + ' -- ' + help
-            if menustyle == 1:
-                label += '   [' + gcmd + ']'
-            elif menustyle == 2:
-                label = '      [' + gcmd + ']'
+        if command:
+            helpString = command + ' -- ' + description
         else:
-            helpString = help
+            helpString = description
         
         if shortcut:
             label += '\t' + shortcut
         
         menuItem = menu.Append(wxId, label, helpString, kind)
         
-        self.menucmd[menuItem.GetId()] = gcmd
+        self.menucmd[menuItem.GetId()] = command
         
-        if gcmd: 
+        if command: 
             try: 
-                cmd = utils.split(str(gcmd)) 
+                cmd = utils.split(str(command)) 
             except UnicodeError: 
-                cmd = utils.split(EncodeString((gcmd))) 
+                cmd = utils.split(EncodeString((command))) 
             if cmd and cmd[0] not in globalvar.grassCmd: 
                 menuItem.Enable(False)
-        
+
         rhandler = eval('self.parent.' + handler)
-        
         self.parent.Bind(wx.EVT_MENU, rhandler, menuItem)
-
+        
     def GetData(self):
         """!Get menu data"""
-        return self.menudata
+        return self.model
     
     def GetCmd(self):
-        """!Get list of commands
+        """!Get dictionary of commands (key is id)
 
-        @return list of commands
+        @return dictionary of commands
         """
         return self.menucmd
         
@@ -125,63 +114,62 @@
         event.Skip()
 
 class SearchModuleWindow(wx.Panel):
-    """!Show menu tree"""
-    def __init__(self, parent, id = wx.ID_ANY, **kwargs):
+    """!Menu tree and search widget for searching modules.
+    
+        Signal:
+            showNotification - attribute 'message'
+    """
+    def __init__(self, parent, model, id = wx.ID_ANY, **kwargs):
         self.parent = parent # LayerManager
         
+        self.showNotification = Signal('SearchModuleWindow.showNotification')
         wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
         
-        self.dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                    label = " %s " % _("Menu tree (double-click or Ctrl-Enter to run command)"))
         # tree
-        menuData = LayerManagerMenuData()
-        self.tree = MenuTree(parent = self, data = menuData)
-        self.tree.Load()
+        self._tree = CTreeView(model=model, parent=self)
 
+        self._dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                     label = " %s " % _("Menu tree (double-click or Ctrl-Enter to run command)"))
+
         # search widget
-        self.search = SearchModuleWidget(parent = self,
-                                         modulesData = ModulesData(menuData.GetModules()),
-                                         showChoice = False)
+        self._search = SearchModuleWidget(parent=self,
+                                          model=model,
+                                          showChoice=False)
+        self._search.showSearchResult.connect(lambda result: self._tree.Select(result))
+        self._search.showNotification.connect(self.showNotification)
         
         # buttons
-        self.btnRun   = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
-        self.btnRun.SetToolTipString(_("Run selected command from the menu tree"))
-        self.btnRun.Enable(False)
+        self._btnRun = wx.Button(self, id=wx.ID_OK, label=_("&Run"))
+        self._btnRun.SetToolTipString(_("Run selected command from the menu tree"))
         
         # bindings
-        self.btnRun.Bind(wx.EVT_BUTTON,            self.OnRun)
-        self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
-        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED,    self.OnItemSelected)
-        self.search.GetCtrl().Bind(wx.EVT_TEXT,    self.OnUpdateStatusBar)
-        self.search.GetCtrl().Bind(wx.EVT_KEY_UP,  self.OnKeyUp)
+        self._btnRun.Bind(wx.EVT_BUTTON, lambda evt: self.Run())
+        self.Bind(wx.EVT_KEY_UP,  self.OnKeyUp)
+        
+        self._tree.selectionChanged.connect(self.OnItemSelected)
+        self._tree.itemActivated.connect(lambda node: self.Run(node))
 
-        # because number of matched items differs
-        # from number of matched items in tree
-        # TODO: find the reason for this difference
-        # TODO: use this event for updating statusbar
-        # TODO: some showNotification usage?
-
         self._layout()
         
-        self.search.SetFocus()
+        self._search.SetFocus()
         
     def _layout(self):
         """!Do dialog layout"""
         sizer = wx.BoxSizer(wx.VERTICAL)
         
         # body
-        dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
-        dataSizer.Add(item = self.tree, proportion =1,
+        dataSizer = wx.StaticBoxSizer(self._dataBox, wx.HORIZONTAL)
+        dataSizer.Add(item = self._tree, proportion =1,
                       flag = wx.EXPAND)
         
         # buttons
         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
-        btnSizer.Add(item = self.btnRun, proportion = 0)
+        btnSizer.Add(item = self._btnRun, proportion = 0)
         
         sizer.Add(item = dataSizer, proportion = 1,
                   flag = wx.EXPAND | wx.ALL, border = 5)
 
-        sizer.Add(item = self.search, proportion = 0,
+        sizer.Add(item = self._search, proportion = 0,
                   flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
         
         sizer.Add(item = btnSizer, proportion = 0,
@@ -196,142 +184,41 @@
         self.SetAutoLayout(True)        
         self.Layout()
         
-    def OnCloseWindow(self, event):
-        """!Close window"""
-        self.Destroy()
+    def Run(self, module=None):
+        """!Run selected command.
         
-    def OnRun(self, event):
-        """!Run selected command"""
-        if not self.tree.GetSelected():
-            return # should not happen
-        
-        data = self.tree.GetPyData(self.tree.GetSelected())
+        @param module module (represented by tree node)
+        """
+        if module is None:
+            if not self._tree.GetSelected():
+                return
+
+            module = self._tree.GetSelected()[0]
+        data = module.data
         if not data:
             return
 
         handler = 'self.parent.' + data['handler'].lstrip('self.')
-        if data['handler'] == 'self.OnXTerm':
-            wx.MessageBox(parent = self,
-                          message = _('You must run this command from the menu or command line',
-                                      'This command require an XTerm'),
-                          caption = _('Message'), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
-        elif data['command']:
-            eval(handler)(event = None, cmd = data['command'].split())
+
+        if data['command']:
+            eval(handler)(event=None, cmd=data['command'].split())
         else:
-            eval(handler)(None)
+            eval(handler)(event=None)
 
     def OnKeyUp(self, event):
-        if event.GetKeyCode() == wx.WXK_RETURN:
-            if event.ControlDown():
-                self.OnRun(event)
-            else:
-                self.OnShowItem(event)
+        """!Key or key combination pressed"""
+        if event.ControlDown() and event.GetKeyCode() == wx.WXK_RETURN:
+            self.Run()
         
-    def OnShowItem(self, event):
-        """!Show selected item"""
-        self.tree.OnShowItem(event)
-        if self.tree.GetSelected():
-            self.btnRun.Enable()
-        else:
-            self.btnRun.Enable(False)
-        
-    def OnItemActivated(self, event):
-        """!Item activated (double-click)"""
-        item = event.GetItem()
-        if not item or not item.IsOk():
-            return
-        
-        data = self.tree.GetPyData(item)
+    def OnItemSelected(self, node):
+        """!Item selected"""      
+        data = node.data
         if not data or 'command' not in data:
             return
         
-        self.tree.itemSelected = item
-        
-        self.OnRun(None)
-        
-    def OnItemSelected(self, event):
-        """!Item selected"""
-        item = event.GetItem()
-        if not item or not item.IsOk():
-            return
-        
-        data = self.tree.GetPyData(item)
-        if not data or 'command' not in data:
-            return
-        
         if data['command']:
             label = data['command'] + ' -- ' + data['description']
         else:
             label = data['description']
         
-        self.parent.SetStatusText(label, 0)
-        
-    def OnUpdateStatusBar(self, event):
-        """!Update statusbar text"""
-        element = self.search.GetSelection()
-        value = event.GetEventObject().GetValue()
-        self.tree.SearchItems(element = element, value = value)
-        
-        nItems = len(self.tree.itemsMarked)
-        if value:
-            self.parent.SetStatusText(_("%d modules match") % nItems, 0)
-        else:
-            self.parent.SetStatusText("", 0)
-        
-        event.Skip()
-        
-class MenuTree(ItemTree):
-    """!Menu tree class"""
-    def __init__(self, parent, data, **kwargs):
-        self.parent   = parent
-        self.menudata = data
-
-        super(MenuTree, self).__init__(parent, **kwargs)
-        
-        self.menustyle = UserSettings.Get(group = 'appearance', key = 'menustyle', subkey = 'selection')
-        
-    def Load(self, data = None):
-        """!Load menu data tree
-
-        @param data menu data (None to use self.menudata)
-        """
-        if not data:
-            data = self.menudata
-        
-        self.itemsMarked = [] # list of marked items
-        for eachMenuData in data.GetMenu():
-            for label, items in eachMenuData:
-                item = self.AppendItem(parentId = self.root,
-                                       text = label.replace('&', ''))
-                self.__AppendItems(item, items)
-        
-    def __AppendItems(self, item, data):
-        """!Append items into tree (used by Load()
-        
-        @param item tree item (parent)
-        @parent data menu data"""
-        for eachItem in data:
-            if len(eachItem) == 2:
-                if eachItem[0]:
-                    itemSub = self.AppendItem(parentId = item,
-                                    text = eachItem[0])
-                self.__AppendItems(itemSub, eachItem[1])
-            else:
-                if eachItem[0]:
-                    label = eachItem[0]
-                    if eachItem[3]:
-                        if self.menustyle == 1:
-                            label += ' [' + eachItem[3] + ']'
-                        elif self.menustyle == 2:
-                            label = '[' + eachItem[3] + ']'
-                    
-                    itemNew = self.AppendItem(parentId = item,
-                                              text = label)
-                    
-                    data = { 'item'        : eachItem[0],
-                             'description' : eachItem[1],
-                             'handler'  : eachItem[2],
-                             'command'  : eachItem[3],
-                             'keywords' : eachItem[4] }
-                    
-                    self.SetPyData(itemNew, data)
+        self.showNotification.emit(message=label)

Modified: grass/trunk/gui/wxpython/gui_core/prompt.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/prompt.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gui_core/prompt.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -42,14 +42,14 @@
 
     See subclass GPromptPopUp and GPromptSTC.
     """
-    def __init__(self, parent, modulesData, updateCmdHistory):
+    def __init__(self, parent, menuModel, updateCmdHistory):
         self.parent = parent                 # GConsole
         self.panel  = self.parent.GetPanel()
 
         self.promptRunCmd = Signal('GPrompt.promptRunCmd')
 
         # probably only subclasses need this
-        self.modulesData = modulesData
+        self._menuModel = menuModel
 
         self.mapList    = self._getListOfMaps()
         self.mapsetList = utils.ListOfMapsets()
@@ -121,29 +121,6 @@
         self.OnCmdErase(None)
         self.ShowStatusText('')
         
-    def GetPanel(self):
-        """!Get main widget panel"""
-        return self.panel
-
-    def GetInput(self):
-        """!Get main prompt widget"""
-        return self.input
-    
-    def SetFilter(self, data, module = True):
-        """!Set filter
-
-        @param data data dict
-        @param module True to filter modules, otherwise data
-        """
-        if module:
-            # TODO: remove this and module param
-            raise NotImplementedError("Replace by call to common ModulesData object (SetFilter with module=True)")
-        else:
-            if data:
-                self.dataList = data
-            else:
-                self.dataList = self._getListOfMaps()
-        
     def GetCommands(self):
         """!Get list of launched commands"""
         return self.commands
@@ -155,9 +132,9 @@
 
 class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
     """!Styled wxGUI prompt with autocomplete and calltips"""    
-    def __init__(self, parent, modulesData, updateCmdHistory = True, margin = False):
+    def __init__(self, parent, menuModel, updateCmdHistory = True, margin = False):
         GPrompt.__init__(self, parent = parent, 
-                         modulesData = modulesData, updateCmdHistory = updateCmdHistory)
+                         menuModel = menuModel, updateCmdHistory = updateCmdHistory)
         wx.stc.StyledTextCtrl.__init__(self, self.panel, id = wx.ID_ANY)
         
         #
@@ -218,7 +195,10 @@
         if self.toComplete['entity'] == 'command':
             item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()] 
             try:
-                desc = self.modulesData.GetCommandDesc(item)
+                nodes = self._menuModel.SearchNodes(key='command', value=item)
+                desc = ''
+                if nodes:
+                    desc = nodes[0].data['description']
             except KeyError:
                 desc = '' 
             self.ShowStatusText(desc)
@@ -406,9 +386,14 @@
             self.InsertText(pos, '.')
             self.CharRight()
             self.toComplete = self.EntityToComplete()
+            if self.toComplete is None:
+                return
             try:
                 if self.toComplete['entity'] == 'command': 
-                    self.autoCompList = self.modulesData.GetDictOfModules()[entry.strip()]
+                    for command in globalvar.grassCmd:
+                        if command.find(self.toComplete['cmd']) == 0:
+                            dotNumber = list(self.toComplete['cmd']).count('.') 
+                            self.autoCompList.append(command.split('.',dotNumber)[-1])
             except (KeyError, TypeError):
                 return
             self.ShowList()
@@ -559,7 +544,7 @@
             
             try:
                 txt = self.cmdbuffer[self.cmdindex]
-            except:
+            except KeyError:
                 txt = ''
             
             # clear current line and insert command history    

Modified: grass/trunk/gui/wxpython/gui_core/query.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/query.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gui_core/query.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -13,10 +13,17 @@
 
 @author Anna Kratochvilova <kratochanna gmail.com>
 """
-
+import os
+import sys
+import random
 import wx
-import wx.gizmos as gizmos
 
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+from gui_core.treeview import TreeListView
+from core.treemodel import TreeModel, DictNode
+
 class QueryDialog(wx.Dialog):
     def __init__(self, parent, data = None):
         wx.Dialog.__init__(self, parent, id = wx.ID_ANY,
@@ -27,20 +34,17 @@
 
         self.panel = wx.Panel(self, id = wx.ID_ANY)
         self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+        self._colNames = [_("Feature"), _("Value")]
+        self._model = QueryTreeBuilder(self.data, column=self._colNames[1])
+        self.tree = TreeListView(model=self._model, parent=self.panel,
+                                 columns=self._colNames,
+                                 style=wx.TR_DEFAULT_STYLE | 
+                                 wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE)
 
-        self.tree = gizmos.TreeListCtrl(self.panel, id = wx.ID_ANY,
-                                        style = wx.TR_DEFAULT_STYLE |
-                                        wx.TR_HIDE_ROOT)
-        
-        self.tree.AddColumn("Feature")
-        self.tree.AddColumn("Value")
-        self.tree.SetMainColumn(0)
         self.tree.SetColumnWidth(0, 220)
         self.tree.SetColumnWidth(1, 400)
-
+        self.tree.ExpandAll(self._model.root)
         self.mainSizer.Add(item = self.tree, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
-        if self.data:
-            self._load()
 
         close = wx.Button(self.panel, id = wx.ID_CLOSE)
         close.Bind(wx.EVT_BUTTON, lambda event: self.Close())
@@ -59,46 +63,15 @@
         # for Windows
         self.SendSizeEvent()
 
-    def _load(self):
-        self.tree.DeleteAllItems()
-        self.root = self.tree.AddRoot("The Root Item")
-        for part in self.data:
-            self._addItem(self.root, part)
-
-        self.tree.UnselectAll()
-        self.tree.ExpandAll(self.root)
-
-    def _print(self):
-        string = []
-        for part in self.data:
-            self._printItem(string, '', part)
-            string.append('')
-        return '\n'.join(string)
-
-    def _addItem(self, parent, data):
-        for k, v in data.iteritems():
-            if isinstance(v, dict):
-                item = self.tree.AppendItem(parent, text = k)
-                self.tree.SetItemText(item, '', 1)
-                self._addItem(item, v)
-            else:
-                item = self.tree.AppendItem(parent, text = k)
-                self.tree.SetItemText(item, str(v), 1)
-
-    def _printItem(self, string, indent, data):
-        for k, v in data.iteritems():
-            if isinstance(v, dict):
-                string.append(indent + k)
-                self._printItem(string, indent + '    ', v)
-            else:
-                string.append(indent + k + ': ' + str(v))
-
     def SetData(self, data):
+        state = self.tree.GetExpansionState()
         self.data = data
-        self._load()
+        self._model = QueryTreeBuilder(self.data, column=self._colNames[1])
+        self.tree.SetModel(self._model)
+        self.tree.SetExpansionState(state)
 
     def Copy(self, event):
-        text = self._print()
+        text = printResults(self._model, self._colNames[1])
         if wx.TheClipboard.Open():
             do = wx.TextDataObject()
             do.SetText(text)
@@ -110,6 +83,47 @@
         event.Skip()
 
 
+def QueryTreeBuilder(data, column):
+    """!Builds tree model from query results.
+    
+    @param data query results as a dictionary
+    @param column column name
+    
+    @returns tree model
+    """
+    def addNode(parent, data, model):
+        for k, v in data.iteritems():
+            if isinstance(v, dict):
+                node = model.AppendNode(parent=parent, label=k)
+                addNode(parent=node, data=v, model=model)
+            else:
+                node = model.AppendNode(parent=parent, label=k,
+                                        data={column: str(v)})
+
+    model = TreeModel(DictNode)
+    for part in data:
+        addNode(parent=model.root, data=part, model=model)
+
+    return model
+
+
+def printResults(model, valueCol):
+    """!Print all results to string.
+    
+    @param model results tree model
+    @param valueCol column name with value to be printed
+    """
+    def printTree(node, textList, valueCol, indent=0):
+        textList.append(indent*' ' + node.label + ': ' + node.data.get(valueCol, ''))
+        for child in node.children:
+            printTree(node=child, textList=textList, valueCol=valueCol, indent=indent + 2)
+    
+    textList=[]
+    for child in model.root.children:
+        printTree(node=child, textList=textList, valueCol=valueCol)
+    return '\n'.join(textList)
+
+
 def PrepareQueryResults(coordinates, result):
     """!Prepare query results as a Query dialog input.
 
@@ -133,9 +147,9 @@
             data.append(part)
     return data
 
+
 def test():
     app = wx.PySimpleApp()
-    import pprint
     from grass.script import vector as gvect
     from grass.script import raster as grast
     testdata1 = grast.raster_what(map = ('elevation_shade at PERMANENT','landclass96'),

Added: grass/trunk/gui/wxpython/gui_core/treeview.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/treeview.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/treeview.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -0,0 +1,203 @@
+"""!
+ at package gui_core.treeview
+
+ at brief tree view for dislaying tree model (used for search tree)
+
+Classes:
+ - treeview::TreeView
+
+(C) 2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Anna Petrasova <kratochanna gmail.com>
+"""
+
+import os
+import sys
+import wx
+from wx.lib.mixins.treemixin import VirtualTree, ExpansionState
+try:
+    import wx.lib.agw.customtreectrl as CT
+except ImportError:
+    import wx.lib.customtreectrl as CT
+import wx.gizmos as gizmos
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+from core.treemodel import TreeModel, DictNode
+
+from grass.pydispatch.signal import Signal
+
+
+class AbstractTreeViewMixin(VirtualTree):
+    """!Abstract tree view class for displaying tree model.
+
+    Concrete implementation must inherit both this mixin class and a wx tree widget.
+    More functionality and signals can be added if needed.
+
+    Signals:
+        selectionChanged - attribute 'node'
+        itemActivated - attribute 'node'
+    """
+    def __init__(self, model, parent, *args, **kw):
+        self._model = model
+        super(AbstractTreeViewMixin, self).__init__(parent=parent, *args, **kw)
+        self.RefreshItems()
+
+        self.selectionChanged = Signal('TreeView.selectionChanged')
+        self.itemActivated = Signal('TreeView.itemActivated')
+
+        self.Bind(wx.EVT_TREE_SEL_CHANGED, lambda evt:
+                                           self._emitSignal(evt.GetItem(), self.selectionChanged))
+        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, lambda evt:
+                                           self._emitSignal(evt.GetItem(), self.itemActivated))
+
+    def SetModel(self, model):
+        """!Set tree model and refresh.
+        
+        @param model tree model        
+        """
+        self._model = model
+        self.RefreshItems()
+
+    def OnGetItemText(self, index, column=0):
+        """!Overridden method necessary to communicate with tree model.
+
+        @param index index as explained in VirtualTree doc
+        @param column column index if applicable
+        """
+        node = self._model.GetNodeByIndex(index)
+        # remove & because of & needed in menu (&Files)
+        label = node.label.replace('&', '')
+        return label
+
+    def OnGetChildrenCount(self, index):
+        """!Overridden method necessary to communicate with tree model."""
+        return len(self._model.GetChildrenByIndex(index))
+
+    def GetSelected(self):
+        """!Get currently selected items.
+
+        @return list of nodes representing selected items (can be empty)
+        """
+        selected = []
+        for sel in self.GetSelections():
+            index = self.GetIndexOfItem(sel)
+            selected.append(self._model.GetNodeByIndex(index))
+        return selected
+
+    def Select(self, node, select=True):
+        """!Select items.
+
+        @param node node representing item
+        @param select True/False to select/deselect
+        """
+        index = self._model.GetIndexOfNode(node)
+        for i in range(len(index))[1:]:
+            item = self.GetItemByIndex(index[:i])
+            self.Expand(item)
+
+        item = self.GetItemByIndex(index)
+        self.SelectItem(item, select)
+
+    def _emitSignal(self, item, signal):
+        """!Helper method for emitting signals.
+
+        @param item tree item
+        @param signal signal to be emitted
+        """
+        if not item or not item.IsOk():
+            return
+        index = self.GetIndexOfItem(item)
+        node = self._model.GetNodeByIndex(index)
+        signal.emit(node = node)
+
+
+class TreeView(AbstractTreeViewMixin, wx.TreeCtrl):
+    """!Tree view class inheriting from wx.TreeCtrl"""
+    def __init__(self, model, parent, *args, **kw):
+        super(TreeView, self).__init__(parent=parent, model=model, *args, **kw)
+
+class CTreeView(AbstractTreeViewMixin, CT.CustomTreeCtrl):
+    """!Tree view class inheriting from wx.TreeCtrl"""
+    def __init__(self, model, parent, **kw):
+        if 'agwStyle' not in kw:
+            kw['agwStyle'] = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT |\
+                             CT.TR_HAS_BUTTONS | CT.TR_LINES_AT_ROOT | CT.TR_SINGLE
+        super(CTreeView, self).__init__(parent=parent, model=model, **kw)
+        
+class TreeListView(AbstractTreeViewMixin, ExpansionState, gizmos.TreeListCtrl):
+    def __init__(self, model, parent, columns, **kw):
+        self._columns = columns
+        super(TreeListView, self).__init__(parent=parent, model=model, **kw)
+        for column in columns:
+            self.AddColumn(column)
+        self.SetMainColumn(0)
+        # refresh again
+        self.RefreshItems()
+
+    def OnGetItemText(self, index, column=0):
+        """!Overridden method necessary to communicate with tree model.
+
+        @param index index as explained in VirtualTree doc
+        @param column column index if applicable
+        """
+        node = self._model.GetNodeByIndex(index)
+        # remove & because of & needed in menu (&Files)
+        if column > 0:
+            return node.data.get(self._columns[column], '')
+        else:
+            label = node.label.replace('&', '')
+            return label
+
+
+class TreeFrame(wx.Frame):
+    """!Frame for testing purposes only."""
+    def __init__(self, model=None):
+        wx.Frame.__init__(self, None, title='Test tree')
+
+        panel = wx.Panel(self)
+#        self.tree = TreeListView(model=model, parent=panel, columns=['col1', 'xxx'])
+#        self.tree = TreeView(model=model, parent=panel)
+        self.tree = CTreeView(model=model, parent=panel)
+        self.tree.selectionChanged.connect(self.OnSelChanged)
+        self.tree.itemActivated.connect(self.OnItemActivated)
+        self.tree.SetMinSize((150, 300))
+
+        szr = wx.BoxSizer(wx.VERTICAL)
+        szr.Add(self.tree, 1, wx.ALIGN_CENTER)
+        panel.SetSizerAndFit(szr)
+        szr.SetSizeHints(self)
+
+    def OnSelChanged(self):
+        print 'selected items: ' + \
+              str([node.label for node in self.tree.GetSelected()])
+        
+    def OnItemActivated(self, node):
+        print 'activated: ' + node.label
+
+
+def main():
+    tree = TreeModel(DictNode)
+    root = tree.root
+    n1 = tree.AppendNode(parent=root, label='node1')
+    n2 = tree.AppendNode(parent=root, label='node2')
+    n3 = tree.AppendNode(parent=root, label='node3') # pylint: disable=W0612
+    n11 = tree.AppendNode(parent=n1, label='node11', data={'xxx': 'A'})
+    n12 = tree.AppendNode(parent=n1, label='node12', data={'xxx': 'B'}) # pylint: disable=W0612
+    n21 = tree.AppendNode(parent=n2, label='node21', data={'xxx': 'A'}) # pylint: disable=W0612
+    n111 = tree.AppendNode(parent=n11, label='node111', data={'xxx': 'A'}) # pylint: disable=W0612
+
+
+    app = wx.PySimpleApp()
+    frame = TreeFrame(model=tree)
+#    frame.tree.Select(n111)
+    frame.Show()
+    app.MainLoop()
+
+
+if __name__ == '__main__':
+    main()

Modified: grass/trunk/gui/wxpython/gui_core/widgets.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/widgets.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/gui_core/widgets.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -13,7 +13,6 @@
  - widgets::BaseValidator
  - widgets::IntegerValidator
  - widgets::FloatValidator
- - widgets::ItemTree
  - widgets::GListCtrl
  - widgets::SearchModuleWidget
  - widgets::ManageSettingsWidget
@@ -55,9 +54,7 @@
 from core.gcmd   import GMessage, GError
 from core.debug  import Debug
 
-from wx.lib.newevent import NewEvent
 
-
 class NotebookController:
     """!Provides handling of notebook page names.
 
@@ -642,85 +639,6 @@
         return True # Prevent wxDialog from complaining.
 
 
-class ItemTree(CT.CustomTreeCtrl):
-    def __init__(self, parent, id = wx.ID_ANY,
-                 ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
-                 CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
-        if globalvar.hasAgw:
-            super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs)
-        else:
-            super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs)
-        
-        self.root = self.AddRoot(_("Menu tree"))
-        self.itemsMarked = [] # list of marked items
-        self.itemSelected = None
-
-    def SearchItems(self, element, value):
-        """!Search item 
-
-        @param element element index (see self.searchBy)
-        @param value
-
-        @return list of found tree items
-        """
-        items = list()
-        if not value:
-            return items
-        
-        item = self.GetFirstChild(self.root)[0]
-        self._processItem(item, element, value, items)
-        
-        self.itemsMarked  = items
-        self.itemSelected = None
-        
-        return items
-    
-    def _processItem(self, item, element, value, listOfItems):
-        """!Search items (used by SearchItems)
-        
-        @param item reference item
-        @param listOfItems list of found items
-        """
-        while item and item.IsOk():
-            subItem = self.GetFirstChild(item)[0]
-            if subItem:
-                self._processItem(subItem, element, value, listOfItems)
-            data = self.GetPyData(item)
-            
-            if data and element in data and \
-                    value.lower() in data[element].lower():
-                listOfItems.append(item)
-            
-            item = self.GetNextSibling(item)
-            
-    def GetSelected(self):
-        """!Get selected item"""
-        return self.itemSelected
-
-    def OnShowItem(self, event):
-        """!Highlight first found item in menu tree"""
-        if len(self.itemsMarked) > 0:
-            if self.GetSelected():
-                self.ToggleItemSelection(self.GetSelected())
-                idx = self.itemsMarked.index(self.GetSelected()) + 1
-            else:
-                idx = 0
-            try:
-                self.ToggleItemSelection(self.itemsMarked[idx])
-                self.itemSelected = self.itemsMarked[idx]
-                self.EnsureVisible(self.itemsMarked[idx])
-            except IndexError:
-                self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
-                self.EnsureVisible(self.itemsMarked[0])
-                self.itemSelected = self.itemsMarked[0]
-        else:
-            for item in self.root.GetChildren():
-                self.Collapse(item)
-            itemSelected = self.GetSelection()
-            if itemSelected:
-                self.ToggleItemSelection(itemSelected)
-            self.itemSelected = None
-
 class SingleSymbolPanel(wx.Panel):
     """!Panel for displaying one symbol.
     
@@ -846,68 +764,75 @@
 
 
 class SearchModuleWidget(wx.Panel):
-    """!Search module widget (used in SearchModuleWindow)
+    """!Search module widget (used e.g. in SearchModuleWindow)
         
-    Signal moduleSelected - attribute 'name' is module name
+    Signals:
+        moduleSelected - attribute 'name' is module name
+        showSearchResult - attribute 'result' is a node (representing module)
+        showNotification - attribute 'message'
     """
-    def __init__(self, parent, modulesData, id = wx.ID_ANY,
+    def __init__(self, parent, model,
                  showChoice = True, showTip = False, **kwargs):
-        self.showTip = showTip
-        self.showChoice = showChoice
-        self.modulesData = modulesData
+        self._showTip = showTip
+        self._showChoice = showChoice
+        self._model = model
+        self._results = [] # list of found nodes
+        self._resultIndex = -1
         
         self.moduleSelected = Signal('SearchModuleWidget.moduleSelected')
+        self.showSearchResult = Signal('SearchModuleWidget.showSearchResult')
+        self.showNotification = Signal('SearchModuleWidget.showNotification')
 
-        wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
+        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY, **kwargs)
 
         self._searchDict = { _('description') : 'description',
                              _('command') : 'command',
                              _('keywords') : 'keywords' }
 
-        # signal which requests showing of a notification
-        self.showNotification = Signal('SearchModuleWidget.showNotification')
 
-        self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
+        self._box = wx.StaticBox(parent = self, id = wx.ID_ANY,
                                 label = " %s " % _("Find module - (press Enter for next match)"))
 
-        self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY)
+        self._searchBy = wx.Choice(parent = self, id = wx.ID_ANY)
         items = [_('description'), _('keywords'), _('command')]
         datas = ['description', 'keywords', 'command']
         for item, data in zip(items, datas):
-            self.searchBy.Append(item = item, clientData = data)
-        self.searchBy.SetSelection(0)
+            self._searchBy.Append(item = item, clientData = data)
+        self._searchBy.SetSelection(0)
+        self._searchBy.Bind(wx.EVT_CHOICE, self.OnSearchModule)
 
-        self.search = wx.SearchCtrl(parent = self, id = wx.ID_ANY,
+        self._search = wx.SearchCtrl(parent = self, id = wx.ID_ANY,
                                     size = (-1, 25), style = wx.TE_PROCESS_ENTER)
-        self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
+        self._search.Bind(wx.EVT_TEXT, self.OnSearchModule)
+        self._search.Bind(wx.EVT_KEY_UP,  self.OnKeyUp)
 
-        if self.showTip:
-            self.searchTip = StaticWrapText(parent = self, id = wx.ID_ANY,
-                                            size = (-1, 35))
+        if self._showTip:
+            self._searchTip = StaticWrapText(parent = self, id = wx.ID_ANY,
+                                             size = (-1, 35))
 
-        if self.showChoice:
-            self.searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
-            self.searchChoice.SetItems(self.modulesData.GetCommandItems())
-            self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
+        if self._showChoice:
+            self._searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
+            self._searchChoice.SetItems(self._searchModule(key='command', value=''))
+            self._searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
 
         self._layout()
 
     def _layout(self):
         """!Do layout"""
-        sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
+        sizer = wx.StaticBoxSizer(self._box, wx.HORIZONTAL)
         gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
         
-        gridSizer.Add(item = self.searchBy,
+        gridSizer.Add(item = self._searchBy,
                       flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
-        gridSizer.Add(item = self.search,
+        gridSizer.Add(item = self._search,
                       flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
         row = 1
-        if self.showChoice:
-            gridSizer.Add(item = self.searchChoice,
+        if self._showChoice:
+            gridSizer.Add(item = self._searchChoice,
                           flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
             row += 1
-        if self.showTip:
-            gridSizer.Add(item = self.searchTip,
+        if self._showTip:
+            gridSizer.Add(item = self._searchTip,
                           flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
             row += 1
 
@@ -918,62 +843,65 @@
         self.SetSizer(sizer)
         sizer.Fit(self)
 
-    def GetCtrl(self):
-        """!Get SearchCtrl widget"""
-        return self.search
+    def OnKeyUp(self, event):
+        """!Key or key combination pressed"""
+        if event.GetKeyCode() == wx.WXK_RETURN and not event.ControlDown():
+            if self._results:
+                self._resultIndex += 1
+                if self._resultIndex == len(self._results):
+                    self._resultIndex = 0
+                self.showSearchResult.emit(result=self._results[self._resultIndex])
+        event.Skip()
 
     def GetSelection(self):
         """!Get selected element"""
-        selection = self.searchBy.GetStringSelection()
+        selection = self._searchBy.GetStringSelection()
 
         return self._searchDict[selection]
 
     def SetSelection(self, i):
         """!Set selection element"""
-        self.searchBy.SetSelection(i)
+        self._searchBy.SetSelection(i)
 
     def OnSearchModule(self, event):
         """!Search module by keywords or description"""
+        commands = self._searchModule(key=self.GetSelection(), value=self._search.GetValue())
+        if self._showChoice:
+            self._searchChoice.SetItems(commands)
+            if commands:
+                self._searchChoice.SetSelection(0)
 
-        text = event.GetEventObject().GetValue()
-        if not text:
-            self.modulesData.SetFilter()
-            mList = self.modulesData.GetCommandItems()
-            if self.showChoice:
-                self.searchChoice.SetItems(mList)
-            label = _("%d modules found") % len(mList)
-        else:
-            findIn = self.searchBy.GetClientData(self.searchBy.GetSelection())
-            modules, nFound = self.modulesData.FindModules(text = text, findIn = findIn)
-            self.modulesData.SetFilter(modules)
-            if self.showChoice:
-                self.searchChoice.SetItems(self.modulesData.GetCommandItems())
-                self.searchChoice.SetSelection(0)
-            label = _("%d modules match") % nFound
+        label = _("%d modules match") % len(commands)
+        if self._showTip:
+            self._searchTip.SetLabel(label)
 
-        if self.showTip:
-            self.searchTip.SetLabel(label)
-
         self.showNotification.emit(message=label)
 
         event.Skip()
 
+    def _searchModule(self, key, value):
+        nodes = self._model.SearchNodes(key=key, value=value)
+        self._results = nodes
+        self._resultIndex = -1
+        return [node.data['command'] for node in nodes if node.data['command']]
+        
     def OnSelectModule(self, event):
         """!Module selected from choice, update command prompt"""
-        cmd  = event.GetString().split(' ', 1)[0]
-
+        cmd  = self._searchChoice.GetStringSelection()
         self.moduleSelected.emit(name = cmd)
 
-        desc = self.modulesData.GetCommandDesc(cmd)
-        if self.showTip:
-            self.searchTip.SetLabel(desc)
+        if self._showTip:
+            for module in self._results:
+                if cmd == module.data['command']:
+                    self._searchTip.SetLabel(module.data['description'])
+                    break
 
     def Reset(self):
         """!Reset widget"""
-        self.searchBy.SetSelection(0)
-        self.search.SetValue('')
-        if self.showTip:
-            self.searchTip.SetLabel('')
+        self._searchBy.SetSelection(0)
+        self._search.SetValue('')
+        if self._showTip:
+            self._searchTip.SetLabel('')
 
 class ManageSettingsWidget(wx.Panel):
     """!Widget which allows loading and saving settings into file."""

Modified: grass/trunk/gui/wxpython/lmgr/frame.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/frame.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/lmgr/frame.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -103,6 +103,7 @@
         
         self._giface = LayerManagerGrassInterface(self)
         
+        self._menuTreeBuilder = LayerManagerMenuData()
         self._auimgr = wx.aui.AuiManager(self)
         
         
@@ -232,7 +233,7 @@
         
     def _createMenuBar(self):
         """!Creates menu bar"""
-        self.menubar = Menu(parent = self, data = LayerManagerMenuData())
+        self.menubar = Menu(parent=self, model=self._menuTreeBuilder.GetModel(separators=True))
         self.SetMenuBar(self.menubar)
         self.menucmd = self.menubar.GetCmd()
         
@@ -276,6 +277,7 @@
         self._gconsole = GConsole(guiparent = self, giface = self._giface,
                                   ignoredCmdPattern = '^d\..*|^r[3]?\.mapcalc$|^i.group')
         self.goutput = GConsoleWindow(parent = self, gconsole = self._gconsole,
+                                      menuModel=self._menuTreeBuilder.GetModel(),
                                       gcstyle = GC_SEARCH | GC_PROMPT)
         self.notebook.AddPage(page = self.goutput, text = _("Command console"), name = 'output')
 
@@ -313,7 +315,8 @@
         
         # create 'search module' notebook page
         if not UserSettings.Get(group = 'manager', key = 'hideTabs', subkey = 'search'):
-            self.search = SearchModuleWindow(parent = self)
+            self.search = SearchModuleWindow(parent = self, model=self._menuTreeBuilder.GetModel())
+            self.search.showNotification.connect(lambda message: self.SetStatusText(message))
             self.notebook.AddPage(page = self.search, text = _("Search module"), name = 'search')
         else:
             self.search = None
@@ -691,12 +694,11 @@
 
         Return command as a list"""
         layer = None
-        
         if event:
             cmd = self.menucmd[event.GetId()]
         else:
             cmd = ''
-        
+
         try:
             cmdlist = cmd.split(' ')
         except: # already list?
@@ -725,13 +727,13 @@
 
     def RunMenuCmd(self, event = None, cmd = []):
         """!Run command selected from menu"""
-        if event:
+        if event:       
             cmd = self.GetMenuCmd(event)
         self._gconsole.RunCmd(cmd)
 
     def OnMenuCmd(self, event = None, cmd = []):
         """!Parse command selected from menu"""
-        if event:
+        if event:       
             cmd = self.GetMenuCmd(event)
         GUI(parent = self).ParseCommand(cmd)
         

Modified: grass/trunk/gui/wxpython/lmgr/menudata.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/menudata.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/lmgr/menudata.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -16,41 +16,13 @@
 """
 
 import os
-import sys
 
 from core.globalvar import ETCWXDIR
-from core.menudata  import MenuData
+from core.menutree  import MenuTreeModelBuilder
 
-class LayerManagerMenuData(MenuData):
+class LayerManagerMenuData(MenuTreeModelBuilder):
     def __init__(self, filename = None):
         if not filename:
-            global etcwxdir
             filename = os.path.join(ETCWXDIR, 'xml', 'menudata.xml')
         
-        MenuData.__init__(self, filename)
-        
-    def GetModules(self):
-        """!Create dictionary of modules used to search module by
-        keywords, description, etc."""
-        modules = dict()
-        
-        for node in self.tree.getiterator():
-            if node.tag == 'menuitem':
-                module = description = ''
-                keywords = []
-                for child in node.getchildren():
-                    if child.tag == 'help':
-                        description = child.text
-                    if child.tag == 'command':
-                        module = child.text
-                    if child.tag == 'keywords':
-                        if child.text:
-                            keywords = child.text.split(',')
-                    
-                if module:
-                    modules[module] = { 'description': description,
-                                        'keywords' : keywords }
-                    if len(keywords) < 1:
-                        print >> sys.stderr, "WARNING: Module <%s> has no keywords" % module
-                
-        return modules
+        MenuTreeModelBuilder.__init__(self, filename)

Modified: grass/trunk/gui/wxpython/modules/extensions.py
===================================================================
--- grass/trunk/gui/wxpython/modules/extensions.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/modules/extensions.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -5,7 +5,7 @@
 
 Classes:
  - extensions::InstallExtensionWindow
- - extensions::ExtensionTree
+ - extensions::ExtensionTreeModelBuilder
  - extensions::UninstallExtensionWindow
  - extensions::CheckListExtension
 
@@ -15,115 +15,24 @@
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 @author Martin Landa <landa.martin gmail.com>
+ at author Anna Petrasova <kratochanna gmail.com>
 """
 
 import os
 import sys
 
 import wx
-try:
-    import wx.lib.agw.customtreectrl as CT
-except ImportError:
-    import wx.lib.customtreectrl as CT
-import wx.lib.flatnotebook as FN
 
-import grass.script as grass
 from grass.script import task as gtask
 
 from core             import globalvar
 from core.gcmd        import GError, RunCommand
 from core.utils       import SetAddOnPath
-from gui_core.forms   import GUI
-from gui_core.widgets import ItemTree, GListCtrl, SearchModuleWidget
+from core.menutree    import TreeModel, ModuleNode
+from gui_core.widgets import GListCtrl, SearchModuleWidget
+from gui_core.treeview import CTreeView
 
 
-class ExtensionModulesData(object):
-    """!Holds information about modules.
-
-    @todo add some test
-    @todo this class has some common methods with core::modulesdata::ModulesData
-    """
-    def __init__(self, modulesDesc):
-
-        self.moduleDesc = modulesDesc
-        self.moduleDescOriginal = modulesDesc
-
-    def GetCommandDesc(self, cmd):
-        """!Gets the description for a given module (command).
-
-        If the given module is not available, an empty string is returned.
-        
-        \code
-        print data.GetCommandDesc('r.info')
-        Outputs basic information about a raster map.
-        \endcode
-        """
-        if cmd in self.moduleDesc:
-            return self.moduleDesc[cmd]['description']
-
-        return ''
-
-    def GetCommandItems(self):
-        """!Gets list of available modules (commands).
-
-        The list contains available module names.
-
-        \code
-        print data.GetCommandItems()[0:4]
-        ['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate']
-        \endcode
-        """
-        items = self.moduleDesc.keys()
-        items.sort()
-
-        return items
-
-    def FindModules(self, text, findIn):
-        """!Finds modules according to given text.
-
-        @param text string to search
-        @param findIn where to search for text
-        (allowed values are 'description', 'keywords' and 'command')
-        """
-        modules = dict()
-        iFound = 0
-        for module, data in self.moduleDescOriginal.iteritems():
-            found = False
-            if findIn == 'description':
-                if text in data['description']:
-                    found = True
-            elif findIn == 'keywords':
-                if text in data['keywords']:
-                    found = True
-            elif findIn == 'command':
-                if module[:len(text)] == text:
-                    found = True
-            else:
-                raise ValueError("Parameter findIn is not valid")
-
-            if found:
-                iFound += 1
-                modules[module] = data
-        return modules, iFound
-
-    def SetFilter(self, data = None):
-        """!Sets filter modules
-
-        If @p data is not specified, module dictionary is derived
-        from an internal data structures.
-        
-        @todo Document this method.
-
-        @param data data dict
-        """
-        if data:
-            self.moduleDesc = data
-        else:
-            self.moduleDesc = self.moduleDescOriginal
-
-    def SetData(self, data):
-        self.moduleDesc = self.moduleDescOriginal = data
-
 class InstallExtensionWindow(wx.Frame):
     def __init__(self, parent, id = wx.ID_ANY,
                  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
@@ -142,13 +51,15 @@
         
         self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
         
-        self.tree   = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
+        # modelBuilder loads data into tree model
+        self.modelBuilder = ExtensionTreeModelBuilder()
+        # tree view displays model data
+        self.tree = CTreeView(parent=self.panel, model=self.modelBuilder.GetModel())
         
-        self.modulesData = ExtensionModulesData(modulesDesc = self.tree.GetModules())
-        self.search = SearchModuleWidget(parent = self.panel, modulesData = self.modulesData,
+        self.search = SearchModuleWidget(parent=self.panel, model=self.modelBuilder.GetModel(),
                                          showChoice = False)
         self.search.SetSelection(0)
-        self.search.moduleSelected.connect(lambda name: self.OnShowItem(None))
+        self.search.showSearchResult.connect(lambda result: self.tree.Select(result))
         # show text in statusbar when notification appears
         self.search.showNotification.connect(lambda message: self.SetStatusText(message))
 
@@ -174,26 +85,26 @@
                 continue
             self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
                                              label = desc)
-        self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
-                                                                'http://svn.osgeo.org/grass/grass-addons/grass7'))
+        defaultUrl = 'http://svn.osgeo.org/grass/grass-addons/grass7'
+        self.repo.SetValue(task.get_param(value = 'svnurl').get('default', defaultUrl))
         
         self.statusbar = self.CreateStatusBar(number = 1)
         
         self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
                                   label = _("&Fetch"))
-        self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
+        self.btnFetch.SetToolTipString(_("Fetch list of available modules "
+                                         "from GRASS Addons SVN repository"))
         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
         self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
                                     label = _("&Install"))
         self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
         self.btnInstall.Enable(False)
         
-        self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
+        self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
         self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
         self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
-        self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
-        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED,    self.OnItemSelected)
-        self.search.Bind(wx.EVT_TEXT_ENTER,        self.OnShowItem)
+        self.tree.selectionChanged.connect(self.OnItemSelected)
+        self.tree.itemActivated.connect(self.OnItemActivated)
 
         wx.CallAfter(self._fetch)
         
@@ -246,13 +157,11 @@
 
     def _getCmd(self):
         item = self.tree.GetSelected()
-        if not item or not item.IsOk():
-            return ['g.extension']
-        
-        name = self.tree.GetItemText(item)
-        if not name:
+        if not item or 'command' not in item[0].data:
             GError(_("Extension not defined"), parent = self)
             return
+
+        name = item[0].data['command']
         
         flags = list()
         for key in self.options.keys():
@@ -264,32 +173,7 @@
         
         return ['g.extension'] + flags + ['extension=' + name,
                                           'svnurl=' + self.repo.GetValue().strip()]
-    
-    def OnUpdateStatusBar(self, event):
-        """!Update statusbar text
 
-        @todo This method is a dead code. Is it useful?
-        """
-        element = self.search.GetSelection()
-        if not self.tree.IsLoaded():
-            self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
-            return
-        
-        self.tree.SearchItems(element = element,
-                              value = event.GetEventObject().GetValue())
-
-        nItems = len(self.tree.itemsMarked)
-        if event.GetString():
-            self.SetStatusText(_("%d items match") % nItems, 0)
-        else:
-            self.SetStatusText("", 0)
-        
-        event.Skip()
-    
-    def OnCloseWindow(self, event):
-        """!Close window"""
-        self.Destroy()
-
     def OnFetch(self, event):
         """!Fetch list of available extensions"""
         self._fetch()
@@ -298,22 +182,22 @@
         """!Fetch list of available extensions"""
         wx.BeginBusyCursor()
         self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
-        self.tree.Load(url = self.repo.GetValue().strip())
-        modulesDesc = self.tree.GetModules()
-        self.modulesData.SetData(modulesDesc)
+        self.modelBuilder.Load(url = self.repo.GetValue().strip())
+        self.tree.RefreshItems()
         self.SetStatusText("", 0)
         wx.EndBusyCursor()
 
-    def OnItemActivated(self, event):
-        item = event.GetItem()
-        data = self.tree.GetPyData(item)
+    def OnItemActivated(self, node):
+        data = node.data
         if data and 'command' in data:
-            self.OnInstall(event = None)
+            self.OnInstall(event=None)
         
     def OnInstall(self, event):
         """!Install selected extension"""
         log = self.parent.GetLogWindow()
-        log.RunCmd(self._getCmd(), onDone = self.OnDone)
+        cmd = self._getCmd()
+        if cmd:
+            log.RunCmd(cmd, onDone = self.OnDone)
         
     def OnDone(self, cmd, returncode):
         if returncode == 0:
@@ -322,11 +206,9 @@
             
             globalvar.UpdateGRASSAddOnCommands()
 
-    def OnItemSelected(self, event):
+    def OnItemSelected(self, node):
         """!Item selected"""
-        item = event.GetItem()
-        self.tree.itemSelected = item
-        data = self.tree.GetPyData(item)
+        data = node.data
         if data is None:
             self.SetStatusText('', 0)
             self.btnInstall.Enable(False)
@@ -334,36 +216,28 @@
             self.SetStatusText(data.get('description', ''), 0)
             self.btnInstall.Enable(True)
 
-    def OnShowItem(self, event):
-        """!Show selected item"""
-        self.tree.OnShowItem(event)
-        if self.tree.GetSelected():
-            self.btnInstall.Enable()
-        else:
-            self.btnInstall.Enable(False)
 
-class ExtensionTree(ItemTree):
-    """!List of available extensions"""
-    def __init__(self, parent, log, id = wx.ID_ANY,
-                 ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
-                 CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
-                 **kwargs):
-        self.parent = parent # GMFrame
-        self.log    = log
-        
-        super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
-        
-        self._initTree()
-        
-    def _initTree(self):
+class ExtensionTreeModelBuilder:
+    """!Tree model of available extensions."""
+    def __init__(self):
+        self.mainNodes = dict()
+        self.model = TreeModel(ModuleNode)
         for prefix in ('display', 'database',
                        'general', 'imagery',
                        'misc', 'postscript', 'paint',
                        'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
-            self.AppendItem(parentId = self.root,
-                            text = prefix)
-        self._loaded = False
+            node = self.model.AppendNode(parent=self.model.root, label=prefix)
+            self.mainNodes[prefix] = node
         
+    def GetModel(self):
+        return self.model
+
+    def _emptyTree(self):
+        """!Remove modules from tree keeping the main structure"""
+        for node in self.mainNodes.values():
+            for child in reversed(node.children):
+                self.model.RemoveNode(child)
+        
     def _expandPrefix(self, c):
         name = { 'd'  : 'display',
                  'db' : 'database',
@@ -384,22 +258,9 @@
         
         return c
     
-    def _findItem(self, text):
-        """!Find item"""
-        item = self.GetFirstChild(self.root)[0]
-        while item and item.IsOk():
-            if text == self.GetItemText(item):
-                return item
-            
-            item = self.GetNextSibling(item)
-        
-        return None
-    
     def Load(self, url, full = True):
         """!Load list of extensions"""
-        self.DeleteAllItems()
-        self.root = self.AddRoot(_("Menu tree"))
-        self._initTree()
+        self._emptyTree()
         
         if full:
             flags = 'g'
@@ -411,7 +272,7 @@
         if not ret:
             return
         
-        mdict = dict()
+        currentNode = None
         for line in ret.splitlines():
             if full:
                 try:
@@ -419,74 +280,36 @@
                 except ValueError:
                     key = 'name'
                     value = line
-                
+
                 if key == 'name':
                     try:
                         prefix, name = value.split('.', 1)
                     except ValueError:
                         prefix = ''
                         name = value
-                    if prefix not in mdict:
-                        mdict[prefix] = dict()
-                    mdict[prefix][name] = dict()
-                    mdict[prefix][name]['command'] = value
+                    mainNode = self.mainNodes[self._expandPrefix(prefix)]
+                    currentNode = self.model.AppendNode(parent=mainNode, label=value)
+                    currentNode.data = {'command': value}
                 else:
-                    mdict[prefix][name][key] = value
+                    if currentNode is not None:
+                        currentNode.data[key] = value
             else:
                 try:
                     prefix, name = line.strip().split('.', 1)
-                except:
+                except ValueError:
                     prefix = ''
                     name = line.strip()
                 
                 if self._expandPrefix(prefix) == prefix:
                     prefix = ''
-                    
-                if prefix not in mdict:
-                    mdict[prefix] = dict()
-                    
-                mdict[prefix][name] = { 'command' : prefix + '.' + name }
-        
-        for prefix in mdict.keys():
-            prefixName = self._expandPrefix(prefix)
-            item = self._findItem(prefixName)
-            names = mdict[prefix].keys()
-            names.sort()
-            for name in names:
-                if prefix:
-                    text = prefix + '.' + name
-                else:
-                    text = name
-                new = self.AppendItem(parentId = item,
-                                      text = text)
-                data = dict()
-                for key in mdict[prefix][name].keys():
-                    data[key] = mdict[prefix][name][key]
-                
-                self.SetPyData(new, data)
-        
-        self._loaded = True
+                module = prefix + '.' + name
+                mainNode = self.mainNodes[self._expandPrefix(prefix)]
+                currentNode = self.model.AppendNode(parent=mainNode, label=module)
+                currentNode.data = {'command': module,
+                                    'keywords': '',
+                                    'description': ''}        
 
-    def IsLoaded(self):
-        """Check if items are loaded"""
-        return self._loaded
 
-    def GetModules(self):
-        modules = {}
-        root = self.GetRootItem()
-        child, cookie = self.GetFirstChild(root)
-        while child and child.IsOk():
-            if self.ItemHasChildren(child):
-                subChild, subCookie = self.GetFirstChild(child)
-                while subChild and subChild.IsOk():
-                    name = self.GetItemText(subChild)
-                    data = self.GetPyData(subChild)
-                    modules[name] = data
-                    subChild, subCookie = self.GetNextChild(child, subCookie)
-            child, cookie = self.GetNextChild(root, cookie)
-
-        return modules
-
 class UninstallExtensionWindow(wx.Frame):
     def __init__(self, parent, id = wx.ID_ANY,
                  title = _("Uninstall GRASS Addons extensions"), **kwargs):
@@ -509,7 +332,7 @@
         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
         
         self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
-        self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
+        self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
         
         self._layout()
         
@@ -536,13 +359,8 @@
         
         self.Layout()
 
-    def OnCloseWindow(self, event):
-        """!Close window"""
-        self.Destroy()
-
     def OnUninstall(self, event):
         """!Uninstall selected extensions"""
-        log = self.parent.GetLogWindow()
         eList = self.extList.GetExtensions()
         if not eList:
             GError(_("No extension selected for removal. "
@@ -568,8 +386,7 @@
         
         # update prompt
         globalvar.UpdateGRASSAddOnCommands(eList)
-        log = self.parent.GetLogWindow()
-        log.GetPrompt().SetFilter(None)
+
         
 class CheckListExtension(GListCtrl):
     """!List of mapset/owner/group"""

Modified: grass/trunk/gui/wxpython/psmap/frame.py
===================================================================
--- grass/trunk/gui/wxpython/psmap/frame.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/psmap/frame.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -59,7 +59,7 @@
         wx.Frame.__init__(self, parent = parent, id = id, title = title, name = "PsMap", **kwargs)
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         #menubar
-        self.menubar = Menu(parent = self, data = PsMapMenuData())
+        self.menubar = Menu(parent = self, model = PsMapMenuData().GetModel())
         self.SetMenuBar(self.menubar)
         #toolbar
 

Modified: grass/trunk/gui/wxpython/psmap/menudata.py
===================================================================
--- grass/trunk/gui/wxpython/psmap/menudata.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/psmap/menudata.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -16,10 +16,10 @@
 
 import os
 
-from core                 import globalvar
-from core.menudata        import MenuData
+from core import globalvar
+from core.menutree import MenuTreeModelBuilder
 
-class PsMapMenuData(MenuData):
+class PsMapMenuData(MenuTreeModelBuilder):
     def __init__(self, path = None):
         """!Menu for Cartographic Composer (psmap.py)
         
@@ -28,4 +28,4 @@
         if not path:
             path = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_psmap.xml')
         
-        MenuData.__init__(self, path)
+        MenuTreeModelBuilder.__init__(self, path)

Modified: grass/trunk/gui/wxpython/scripts/vkrige.py
===================================================================
--- grass/trunk/gui/wxpython/scripts/vkrige.py	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/scripts/vkrige.py	2013-04-02 19:17:47 UTC (rev 55594)
@@ -100,8 +100,7 @@
         
         ## Command output. From menuform module, cmdPanel class
         self._gconsole = gconsole.GConsole(guiparent = self)
-        self.goutput = goutput.GConsoleWindow(parent = self, gconsole = self._gconsole, margin = False,
-                                              gcstyle = goutput.GC_SEARCH | goutput.GC_PROMPT)
+        self.goutput = goutput.GConsoleWindow(parent = self, gconsole = self._gconsole, margin = False)
         self.goutputId = self.RPackagesBook.GetPageCount()
         self.outpage = self.RPackagesBook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
         self._gconsole.Bind(gconsole.EVT_CMD_RUN,

Modified: grass/trunk/gui/wxpython/wxpythonlib.dox
===================================================================
--- grass/trunk/gui/wxpython/wxpythonlib.dox	2013-04-02 18:18:59 UTC (rev 55593)
+++ grass/trunk/gui/wxpython/wxpythonlib.dox	2013-04-02 19:17:47 UTC (rev 55594)
@@ -71,13 +71,12 @@
  - goutput::GStdout
  - goutput::GStderr
  - goutput::GConsole
-- core::menudata
- - menudata::MenuData
+- core::menutree
+ - menutree::MenuTreeModelBuilder
 - core::modulesdata
  - modulesdata::ModulesData
 - core::render
  - render::Layer
- - render::Layer
  - render::MapLayer
  - render::Overlay
  - render::Map
@@ -152,7 +151,6 @@
 - gui_core::menu
  - menu::Menu
  - menu::SearchModuleWindow
- - menu::MenuTree
 - gui_core::preferences
  - preferences::PreferencesBaseDialog
  - preferences::PreferencesDialog
@@ -161,8 +159,12 @@
 - gui_core::prompt
  - prompt::GPrompt
  - prompt::GPromptSTC
+- gui_core::query
+ - query::QueryDialog
 - gui_core::toolbars
  - toolbars::BaseToolbar
+- gui_core::treeview
+ - treeview::TreeView
 - gui_core::widgets
  - widgets::ScrolledPanel
  - widgets::NTCValidator
@@ -175,7 +177,6 @@
  - widgets::IntegerValidator
  - widgets::FloatValidator
  - widgets::NTCValidator
- - widgets::ItemTree
  - widgets::SearchModuleWidget
 
 \subsection lmgr Layer Manager



More information about the grass-commit mailing list