ソースを参照

wxGUI: refactoring tree structures (menu, search tree, extensions, query)

git-svn-id: https://svn.osgeo.org/grass/grass/trunk@55594 15284696-431f-4ddb-bdfa-cd5b030d7da7
Anna Petrášová 12 年 前
コミット
d4cd822c37

+ 2 - 1
gui/wxpython/Makefile

@@ -26,9 +26,10 @@ default: $(DSTFILES)
 $(ETCDIR)/%: % | $(PYDSTDIRS) $(DSTDIRS)
 $(ETCDIR)/%: % | $(PYDSTDIRS) $(DSTDIRS)
 	$(INSTALL_DATA) $< $@
 	$(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) $< > $@)
 	$(call run_grass,$(PYTHON) $< "modeler" >> $@)
 	$(call run_grass,$(PYTHON) $< "modeler" >> $@)
+	$(call run_grass,$(PYTHON) $< "psmap" >> $@)
 
 
 $(PYDSTDIRS): %: | $(ETCDIR)
 $(PYDSTDIRS): %: | $(ETCDIR)
 	$(MKDIR) $@
 	$(MKDIR) $@

+ 0 - 233
gui/wxpython/core/menudata.py

@@ -1,233 +0,0 @@
-"""!
-@package core.menudata
-
-@brief Complex list for menu entries for wxGUI
-
-Classes:
- - menudata::MenuData
-
-Usage:
-@code
-python menudata.py [action] [manager|modeler]
-@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.
-
-@author Michael Barton (Arizona State University)
-@author Yann Chemin <yann.chemin gmail.com>
-@author Martin Landa <landa.martin gmail.com>
-@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)

+ 233 - 0
gui/wxpython/core/menutree.py

@@ -0,0 +1,233 @@
+"""!
+@package core.menutree
+
+@brief Creates tree structure for wxGUI menus (former menudata.py)
+
+Classes:
+ - menutree::MenuTreeModelBuilder
+
+Usage:
+@code
+python menutree.py [action] [menu]
+@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.
+
+@author Glynn Clements (menudata.py)
+@author Martin Landa <landa.martin gmail.com> (menudata.py)
+@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)

+ 0 - 189
gui/wxpython/core/modulesdata.py

@@ -1,189 +0,0 @@
-"""!
-@package core.modulesdata
-
-@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.
-
-@author Martin Landa <landa.martin gmail.com>
-@author Vaclav Petras <wenzeslaus gmail.com>
-@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()

+ 209 - 0
gui/wxpython/core/treemodel.py

@@ -0,0 +1,209 @@
+"""!
+@package core.treemodel
+
+@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.
+
+@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()

+ 9 - 31
gui/wxpython/gmodeler/dialogs.py

@@ -31,7 +31,6 @@ import wx.lib.mixins.listctrl as listmix
 
 
 from core                 import globalvar
 from core                 import globalvar
 from core                 import utils
 from core                 import utils
-from core.modulesdata     import ModulesData
 from gui_core.widgets     import SearchModuleWidget, SimpleValidator
 from gui_core.widgets     import SearchModuleWidget, SimpleValidator
 from core.gcmd            import GError, EncodeString
 from core.gcmd            import GError, EncodeString
 from gui_core.dialogs     import SimpleDialog, MapLayersDialogForModeler
 from gui_core.dialogs     import SimpleDialog, MapLayersDialogForModeler
@@ -39,6 +38,7 @@ from gui_core.prompt      import GPromptSTC
 from gui_core.forms       import CmdPanel
 from gui_core.forms       import CmdPanel
 from gui_core.gselect     import Select, ElementSelect
 from gui_core.gselect     import Select, ElementSelect
 from gmodeler.model       import *
 from gmodeler.model       import *
+from lmgr.menudata        import LayerManagerMenuData
 
 
 from grass.script import task as gtask
 from grass.script import task as gtask
 
 
@@ -138,7 +138,7 @@ class ModelDataDialog(SimpleDialog):
             self.Destroy()
             self.Destroy()
 
 
 class ModelSearchDialog(wx.Dialog):
 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):
                  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
         """!Graphical modeler module search window
         """!Graphical modeler module search window
         
         
@@ -149,7 +149,7 @@ class ModelSearchDialog(wx.Dialog):
         """
         """
         self.parent = parent
         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.SetName("ModelerDialog")
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         
         
@@ -158,12 +158,14 @@ class ModelSearchDialog(wx.Dialog):
         
         
         self.cmdBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
         self.cmdBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
                                    label=" %s " % _("Command"))
                                    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.cmd_prompt.promptRunCmd.connect(self.OnCommand)
         self.search = SearchModuleWidget(parent = self.panel,
         self.search = SearchModuleWidget(parent = self.panel,
-                                         modulesData = modulesData,
+                                         model = menuModel.GetModel(),
                                          showTip = True)
                                          showTip = True)
         self.search.moduleSelected.connect(lambda name:
         self.search.moduleSelected.connect(lambda name:
                                            self.cmd_prompt.SetTextAndFocus(name + ' '))
                                            self.cmd_prompt.SetTextAndFocus(name + ' '))
@@ -172,10 +174,7 @@ class ModelSearchDialog(wx.Dialog):
         self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
         self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
         self.btnOk     = wx.Button(self.panel, wx.ID_OK)
         self.btnOk     = wx.Button(self.panel, wx.ID_OK)
         self.btnOk.SetDefault()
         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.OnOk, self.btnOk)
         self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
         self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
         
         
@@ -256,31 +255,10 @@ class ModelSearchDialog(wx.Dialog):
         """Cancel pressed, close window"""
         """Cancel pressed, close window"""
         self.Hide()
         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):
     def Reset(self):
         """!Reset dialog"""
         """!Reset dialog"""
         self.search.Reset()
         self.search.Reset()
         self.cmd_prompt.OnCmdErase(None)
         self.cmd_prompt.OnCmdErase(None)
-        self.btnOk.Enable(False)
         self.cmd_prompt.SetFocus()
         self.cmd_prompt.SetFocus()
 
 
 class ModelRelationDialog(wx.Dialog):
 class ModelRelationDialog(wx.Dialog):

+ 1 - 3
gui/wxpython/gmodeler/frame.py

@@ -42,7 +42,6 @@ from core.gcmd            import GMessage, GException, GWarning, GError, RunComm
 from gui_core.dialogs     import GetImageHandlers
 from gui_core.dialogs     import GetImageHandlers
 from gui_core.preferences import PreferencesBaseDialog
 from gui_core.preferences import PreferencesBaseDialog
 from core.settings        import UserSettings
 from core.settings        import UserSettings
-from core.menudata        import MenuData
 from gui_core.menu        import Menu
 from gui_core.menu        import Menu
 from gmodeler.menudata    import ModelerMenuData
 from gmodeler.menudata    import ModelerMenuData
 from gui_core.forms       import GUI
 from gui_core.forms       import GUI
@@ -82,8 +81,7 @@ class ModelFrame(wx.Frame):
         self.SetName("Modeler")
         self.SetName("Modeler")
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         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.SetMenuBar(self.menubar)
         
         
         self.toolbar = ModelerToolbar(parent = self)
         self.toolbar = ModelerToolbar(parent = self)

+ 5 - 6
gui/wxpython/gmodeler/menudata.py

@@ -16,14 +16,13 @@ This program is free software under the GNU General Public License
 
 
 import os
 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):
     def __init__(self, filename = None):
         if not filename:
         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)
 
 

+ 11 - 11
gui/wxpython/gui_core/goutput.py

@@ -38,7 +38,6 @@ from core.gconsole   import GConsole, \
 from gui_core.prompt import GPromptSTC
 from gui_core.prompt import GPromptSTC
 from core.settings   import UserSettings
 from core.settings   import UserSettings
 from gui_core.widgets import SearchModuleWidget
 from gui_core.widgets import SearchModuleWidget
-from core.modulesdata import ModulesData
 
 
 
 
 GC_EMPTY = 0
 GC_EMPTY = 0
@@ -54,19 +53,19 @@ gGcContentChanged, EVT_GC_CONTENT_CHANGED = NewEvent()
 class GConsoleWindow(wx.SplitterWindow):
 class GConsoleWindow(wx.SplitterWindow):
     """!Create and manage output console for commands run by GUI.
     """!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,
                  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
                  gcstyle = GC_EMPTY,
                  gcstyle = GC_EMPTY,
                  **kwargs):
                  **kwargs):
         """!
         """!
         @param parent gui parent
         @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 margin use margin in output pane (GStc)
         @param style wx.SplitterWindow style
         @param style wx.SplitterWindow style
         @param gcstyle GConsole style
         @param gcstyle GConsole style
         (GC_EMPTY, GC_PROMPT to show command prompt,
         (GC_EMPTY, GC_PROMPT to show command prompt,
         GC_SEARCH to show search widget)
         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)
         wx.SplitterWindow.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs)
         self.SetName("GConsole")
         self.SetName("GConsole")
@@ -77,6 +76,7 @@ class GConsoleWindow(wx.SplitterWindow):
         # initialize variables
         # initialize variables
         self.parent = parent # GMFrame | CmdPanel | ?
         self.parent = parent # GMFrame | CmdPanel | ?
         self._gconsole = gconsole
         self._gconsole = gconsole
+        self._menuModel = menuModel
 
 
         self._gcstyle = gcstyle
         self._gcstyle = gcstyle
         self.lineWidth       = 80
         self.lineWidth       = 80
@@ -115,13 +115,10 @@ class GConsoleWindow(wx.SplitterWindow):
                                wrap = None)
                                wrap = None)
         self.cmdOutput.Bind(stc.EVT_STC_CHANGE, self.OnStcChanged)
         self.cmdOutput.Bind(stc.EVT_STC_CHANGE, self.OnStcChanged)
 
 
-        # information about available modules
-        modulesData = ModulesData()
-
         # search & command prompt
         # search & command prompt
         # move to the if below
         # move to the if below
         # search depends on cmd prompt
         # 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.cmdPrompt.promptRunCmd.connect(lambda cmd:
                                             self._gconsole.RunCmd(command=cmd))
                                             self._gconsole.RunCmd(command=cmd))
         self.cmdPrompt.showNotification.connect(self.showNotification)
         self.cmdPrompt.showNotification.connect(self.showNotification)
@@ -137,7 +134,7 @@ class GConsoleWindow(wx.SplitterWindow):
                                                  label = self.infoCollapseLabelExp,
                                                  label = self.infoCollapseLabelExp,
                                                  style = wx.CP_DEFAULT_STYLE |
                                                  style = wx.CP_DEFAULT_STYLE |
                                                  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
                                                  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.searchPane.Collapse(True)
             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane) 
             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane) 
             self.search.moduleSelected.connect(lambda name:
             self.search.moduleSelected.connect(lambda name:
@@ -256,12 +253,12 @@ class GConsoleWindow(wx.SplitterWindow):
         self.SetAutoLayout(True)
         self.SetAutoLayout(True)
         self.Layout()
         self.Layout()
 
 
-    def MakeSearchPaneContent(self, pane, modulesData):
+    def MakeSearchPaneContent(self, pane, model):
         """!Create search pane"""
         """!Create search pane"""
         border = wx.BoxSizer(wx.VERTICAL)
         border = wx.BoxSizer(wx.VERTICAL)
         
         
         self.search = SearchModuleWidget(parent = pane,
         self.search = SearchModuleWidget(parent = pane,
-                                         modulesData = modulesData)
+                                         model = model)
 
 
         self.search.showNotification.connect(self.showNotification)
         self.search.showNotification.connect(self.showNotification)
 
 
@@ -722,8 +719,11 @@ class GConsoleFrame(wx.Frame):
 
 
         panel = wx.Panel(self, id = wx.ID_ANY)
         panel = wx.Panel(self, id = wx.ID_ANY)
         
         
+        from lmgr.menudata import LayerManagerMenuData
+        menuTreeBuilder = LayerManagerMenuData()
         self.gconsole = GConsole(guiparent=self)
         self.gconsole = GConsole(guiparent=self)
         self.goutput = GConsoleWindow(parent = panel, gconsole = self.gconsole,
         self.goutput = GConsoleWindow(parent = panel, gconsole = self.gconsole,
+                                      menuModel=menuTreeBuilder.GetModel(),
                                       gcstyle = GC_SEARCH | GC_PROMPT)
                                       gcstyle = GC_SEARCH | GC_PROMPT)
 
 
         mainSizer = wx.BoxSizer(wx.VERTICAL)
         mainSizer = wx.BoxSizer(wx.VERTICAL)

+ 79 - 192
gui/wxpython/gui_core/menu.py

@@ -6,7 +6,6 @@
 Classes:
 Classes:
  - menu::Menu
  - menu::Menu
  - menu::SearchModuleWindow
  - menu::SearchModuleWindow
- - menu::MenuTree
 
 
 (C) 2010-2012 by the GRASS Development Team
 (C) 2010-2012 by the GRASS Development Team
 
 
@@ -24,44 +23,39 @@ import wx
 
 
 from core              import globalvar
 from core              import globalvar
 from core              import utils
 from core              import utils
-from core.modulesdata  import ModulesData
 from core.gcmd         import EncodeString
 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):
 class Menu(wx.MenuBar):
-    def __init__(self, parent, data):
+    def __init__(self, parent, model):
         """!Creates menubar"""
         """!Creates menubar"""
         wx.MenuBar.__init__(self)
         wx.MenuBar.__init__(self)
-        self.parent   = parent
-        self.menudata = data
-        self.menucmd  = dict()
-        
-        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)
+        self.parent = parent
+        self.model = model
+        self.menucmd = dict()
+
+        for child in self.model.root.children:
+            self.Append(self._createMenu(child), child.label)
         
         
-    def _createMenu(self, menuData):
+    def _createMenu(self, node):
         """!Creates menu"""
         """!Creates menu"""
         menu = wx.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)
                 menu.AppendMenu(wx.ID_ANY, label, subMenu)
             else:
             else:
-                self._createMenuItem(menu, self.menustyle, *eachItem)
+                self._createMenuItem(menu, **child.data)
         
         
         self.parent.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight)
         self.parent.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight)
         
         
         return menu
         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):
                         shortcut = '', wxId = wx.ID_ANY, kind = wx.ITEM_NORMAL):
         """!Creates menu items
         """!Creates menu items
         There are three menu styles (menu item text styles).
         There are three menu styles (menu item text styles).
@@ -71,42 +65,37 @@ class Menu(wx.MenuBar):
             menu.AppendSeparator()
             menu.AppendSeparator()
             return
             return
         
         
-        if gcmd:
-            helpString = gcmd + ' -- ' + help
-            if menustyle == 1:
-                label += '   [' + gcmd + ']'
-            elif menustyle == 2:
-                label = '      [' + gcmd + ']'
+        if command:
+            helpString = command + ' -- ' + description
         else:
         else:
-            helpString = help
+            helpString = description
         
         
         if shortcut:
         if shortcut:
             label += '\t' + shortcut
             label += '\t' + shortcut
         
         
         menuItem = menu.Append(wxId, label, helpString, kind)
         menuItem = menu.Append(wxId, label, helpString, kind)
         
         
-        self.menucmd[menuItem.GetId()] = gcmd
+        self.menucmd[menuItem.GetId()] = command
         
         
-        if gcmd: 
+        if command: 
             try: 
             try: 
-                cmd = utils.split(str(gcmd)) 
+                cmd = utils.split(str(command)) 
             except UnicodeError: 
             except UnicodeError: 
-                cmd = utils.split(EncodeString((gcmd))) 
+                cmd = utils.split(EncodeString((command))) 
             if cmd and cmd[0] not in globalvar.grassCmd: 
             if cmd and cmd[0] not in globalvar.grassCmd: 
                 menuItem.Enable(False)
                 menuItem.Enable(False)
-        
+
         rhandler = eval('self.parent.' + handler)
         rhandler = eval('self.parent.' + handler)
-        
         self.parent.Bind(wx.EVT_MENU, rhandler, menuItem)
         self.parent.Bind(wx.EVT_MENU, rhandler, menuItem)
-
+        
     def GetData(self):
     def GetData(self):
         """!Get menu data"""
         """!Get menu data"""
-        return self.menudata
+        return self.model
     
     
     def GetCmd(self):
     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
         return self.menucmd
         
         
@@ -125,63 +114,62 @@ class Menu(wx.MenuBar):
         event.Skip()
         event.Skip()
 
 
 class SearchModuleWindow(wx.Panel):
 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.parent = parent # LayerManager
         
         
+        self.showNotification = Signal('SearchModuleWindow.showNotification')
         wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
         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
         # 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
         # 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
         # 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
         # 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)
-
-        # 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._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))
 
 
         self._layout()
         self._layout()
         
         
-        self.search.SetFocus()
+        self._search.SetFocus()
         
         
     def _layout(self):
     def _layout(self):
         """!Do dialog layout"""
         """!Do dialog layout"""
         sizer = wx.BoxSizer(wx.VERTICAL)
         sizer = wx.BoxSizer(wx.VERTICAL)
         
         
         # body
         # 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)
                       flag = wx.EXPAND)
         
         
         # buttons
         # buttons
         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
         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,
         sizer.Add(item = dataSizer, proportion = 1,
                   flag = wx.EXPAND | wx.ALL, border = 5)
                   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)
                   flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
         
         
         sizer.Add(item = btnSizer, proportion = 0,
         sizer.Add(item = btnSizer, proportion = 0,
@@ -196,66 +184,35 @@ class SearchModuleWindow(wx.Panel):
         self.SetAutoLayout(True)        
         self.SetAutoLayout(True)        
         self.Layout()
         self.Layout()
         
         
-    def OnCloseWindow(self, event):
-        """!Close window"""
-        self.Destroy()
-        
-    def OnRun(self, event):
-        """!Run selected command"""
-        if not self.tree.GetSelected():
-            return # should not happen
+    def Run(self, module=None):
+        """!Run selected command.
         
         
-        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:
         if not data:
             return
             return
 
 
         handler = 'self.parent.' + data['handler'].lstrip('self.')
         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:
         else:
-            eval(handler)(None)
+            eval(handler)(event=None)
 
 
     def OnKeyUp(self, event):
     def OnKeyUp(self, event):
-        if event.GetKeyCode() == wx.WXK_RETURN:
-            if event.ControlDown():
-                self.OnRun(event)
-            else:
-                self.OnShowItem(event)
-        
-    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
+        """!Key or key combination pressed"""
+        if event.ControlDown() and event.GetKeyCode() == wx.WXK_RETURN:
+            self.Run()
         
         
-        data = self.tree.GetPyData(item)
-        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)
+    def OnItemSelected(self, node):
+        """!Item selected"""      
+        data = node.data
         if not data or 'command' not in data:
         if not data or 'command' not in data:
             return
             return
         
         
@@ -264,74 +221,4 @@ class SearchModuleWindow(wx.Panel):
         else:
         else:
             label = data['description']
             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)

+ 15 - 30
gui/wxpython/gui_core/prompt.py

@@ -42,14 +42,14 @@ class GPrompt(object):
 
 
     See subclass GPromptPopUp and GPromptSTC.
     See subclass GPromptPopUp and GPromptSTC.
     """
     """
-    def __init__(self, parent, modulesData, updateCmdHistory):
+    def __init__(self, parent, menuModel, updateCmdHistory):
         self.parent = parent                 # GConsole
         self.parent = parent                 # GConsole
         self.panel  = self.parent.GetPanel()
         self.panel  = self.parent.GetPanel()
 
 
         self.promptRunCmd = Signal('GPrompt.promptRunCmd')
         self.promptRunCmd = Signal('GPrompt.promptRunCmd')
 
 
         # probably only subclasses need this
         # probably only subclasses need this
-        self.modulesData = modulesData
+        self._menuModel = menuModel
 
 
         self.mapList    = self._getListOfMaps()
         self.mapList    = self._getListOfMaps()
         self.mapsetList = utils.ListOfMapsets()
         self.mapsetList = utils.ListOfMapsets()
@@ -121,29 +121,6 @@ class GPrompt(object):
         self.OnCmdErase(None)
         self.OnCmdErase(None)
         self.ShowStatusText('')
         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):
     def GetCommands(self):
         """!Get list of launched commands"""
         """!Get list of launched commands"""
         return self.commands
         return self.commands
@@ -155,9 +132,9 @@ class GPrompt(object):
 
 
 class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
 class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
     """!Styled wxGUI prompt with autocomplete and calltips"""    
     """!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, 
         GPrompt.__init__(self, parent = parent, 
-                         modulesData = modulesData, updateCmdHistory = updateCmdHistory)
+                         menuModel = menuModel, updateCmdHistory = updateCmdHistory)
         wx.stc.StyledTextCtrl.__init__(self, self.panel, id = wx.ID_ANY)
         wx.stc.StyledTextCtrl.__init__(self, self.panel, id = wx.ID_ANY)
         
         
         #
         #
@@ -218,7 +195,10 @@ class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
         if self.toComplete['entity'] == 'command':
         if self.toComplete['entity'] == 'command':
             item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()] 
             item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()] 
             try:
             try:
-                desc = self.modulesData.GetCommandDesc(item)
+                nodes = self._menuModel.SearchNodes(key='command', value=item)
+                desc = ''
+                if nodes:
+                    desc = nodes[0].data['description']
             except KeyError:
             except KeyError:
                 desc = '' 
                 desc = '' 
             self.ShowStatusText(desc)
             self.ShowStatusText(desc)
@@ -406,9 +386,14 @@ class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
             self.InsertText(pos, '.')
             self.InsertText(pos, '.')
             self.CharRight()
             self.CharRight()
             self.toComplete = self.EntityToComplete()
             self.toComplete = self.EntityToComplete()
+            if self.toComplete is None:
+                return
             try:
             try:
                 if self.toComplete['entity'] == 'command': 
                 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):
             except (KeyError, TypeError):
                 return
                 return
             self.ShowList()
             self.ShowList()
@@ -559,7 +544,7 @@ class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
             
             
             try:
             try:
                 txt = self.cmdbuffer[self.cmdindex]
                 txt = self.cmdbuffer[self.cmdindex]
-            except:
+            except KeyError:
                 txt = ''
                 txt = ''
             
             
             # clear current line and insert command history    
             # clear current line and insert command history    

+ 63 - 49
gui/wxpython/gui_core/query.py

@@ -13,9 +13,16 @@ This program is free software under the GNU General Public License
 
 
 @author Anna Kratochvilova <kratochanna gmail.com>
 @author Anna Kratochvilova <kratochanna gmail.com>
 """
 """
-
+import os
+import sys
+import random
 import wx
 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):
 class QueryDialog(wx.Dialog):
     def __init__(self, parent, data = None):
     def __init__(self, parent, data = None):
@@ -27,20 +34,17 @@ class QueryDialog(wx.Dialog):
 
 
         self.panel = wx.Panel(self, id = wx.ID_ANY)
         self.panel = wx.Panel(self, id = wx.ID_ANY)
         self.mainSizer = wx.BoxSizer(wx.VERTICAL)
         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(0, 220)
         self.tree.SetColumnWidth(1, 400)
         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)
         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 = wx.Button(self.panel, id = wx.ID_CLOSE)
         close.Bind(wx.EVT_BUTTON, lambda event: self.Close())
         close.Bind(wx.EVT_BUTTON, lambda event: self.Close())
@@ -59,46 +63,15 @@ class QueryDialog(wx.Dialog):
         # for Windows
         # for Windows
         self.SendSizeEvent()
         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):
     def SetData(self, data):
+        state = self.tree.GetExpansionState()
         self.data = data
         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):
     def Copy(self, event):
-        text = self._print()
+        text = printResults(self._model, self._colNames[1])
         if wx.TheClipboard.Open():
         if wx.TheClipboard.Open():
             do = wx.TextDataObject()
             do = wx.TextDataObject()
             do.SetText(text)
             do.SetText(text)
@@ -110,6 +83,47 @@ class QueryDialog(wx.Dialog):
         event.Skip()
         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):
 def PrepareQueryResults(coordinates, result):
     """!Prepare query results as a Query dialog input.
     """!Prepare query results as a Query dialog input.
 
 
@@ -133,9 +147,9 @@ def PrepareQueryResults(coordinates, result):
             data.append(part)
             data.append(part)
     return data
     return data
 
 
+
 def test():
 def test():
     app = wx.PySimpleApp()
     app = wx.PySimpleApp()
-    import pprint
     from grass.script import vector as gvect
     from grass.script import vector as gvect
     from grass.script import raster as grast
     from grass.script import raster as grast
     testdata1 = grast.raster_what(map = ('elevation_shade@PERMANENT','landclass96'),
     testdata1 = grast.raster_what(map = ('elevation_shade@PERMANENT','landclass96'),

+ 203 - 0
gui/wxpython/gui_core/treeview.py

@@ -0,0 +1,203 @@
+"""!
+@package gui_core.treeview
+
+@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.
+
+@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()

+ 71 - 143
gui/wxpython/gui_core/widgets.py

@@ -13,7 +13,6 @@ Classes:
  - widgets::BaseValidator
  - widgets::BaseValidator
  - widgets::IntegerValidator
  - widgets::IntegerValidator
  - widgets::FloatValidator
  - widgets::FloatValidator
- - widgets::ItemTree
  - widgets::GListCtrl
  - widgets::GListCtrl
  - widgets::SearchModuleWidget
  - widgets::SearchModuleWidget
  - widgets::ManageSettingsWidget
  - widgets::ManageSettingsWidget
@@ -55,8 +54,6 @@ from core        import globalvar
 from core.gcmd   import GMessage, GError
 from core.gcmd   import GMessage, GError
 from core.debug  import Debug
 from core.debug  import Debug
 
 
-from wx.lib.newevent import NewEvent
-
 
 
 class NotebookController:
 class NotebookController:
     """!Provides handling of notebook page names.
     """!Provides handling of notebook page names.
@@ -642,85 +639,6 @@ class GenericValidator(wx.PyValidator):
         return True # Prevent wxDialog from complaining.
         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):
 class SingleSymbolPanel(wx.Panel):
     """!Panel for displaying one symbol.
     """!Panel for displaying one symbol.
     
     
@@ -846,68 +764,75 @@ class GListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCt
 
 
 
 
 class SearchModuleWidget(wx.Panel):
 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):
                  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.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',
         self._searchDict = { _('description') : 'description',
                              _('command') : 'command',
                              _('command') : 'command',
                              _('keywords') : 'keywords' }
                              _('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)"))
                                 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')]
         items = [_('description'), _('keywords'), _('command')]
         datas = ['description', 'keywords', 'command']
         datas = ['description', 'keywords', 'command']
         for item, data in zip(items, datas):
         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)
                                     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()
         self._layout()
 
 
     def _layout(self):
     def _layout(self):
         """!Do layout"""
         """!Do layout"""
-        sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
+        sizer = wx.StaticBoxSizer(self._box, wx.HORIZONTAL)
         gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
         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))
                       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))
                       flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
         row = 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))
                           flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
             row += 1
             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))
                           flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
             row += 1
             row += 1
 
 
@@ -918,62 +843,65 @@ class SearchModuleWidget(wx.Panel):
         self.SetSizer(sizer)
         self.SetSizer(sizer)
         sizer.Fit(self)
         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):
     def GetSelection(self):
         """!Get selected element"""
         """!Get selected element"""
-        selection = self.searchBy.GetStringSelection()
+        selection = self._searchBy.GetStringSelection()
 
 
         return self._searchDict[selection]
         return self._searchDict[selection]
 
 
     def SetSelection(self, i):
     def SetSelection(self, i):
         """!Set selection element"""
         """!Set selection element"""
-        self.searchBy.SetSelection(i)
+        self._searchBy.SetSelection(i)
 
 
     def OnSearchModule(self, event):
     def OnSearchModule(self, event):
         """!Search module by keywords or description"""
         """!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
-
-        if self.showTip:
-            self.searchTip.SetLabel(label)
+        label = _("%d modules match") % len(commands)
+        if self._showTip:
+            self._searchTip.SetLabel(label)
 
 
         self.showNotification.emit(message=label)
         self.showNotification.emit(message=label)
 
 
         event.Skip()
         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):
     def OnSelectModule(self, event):
         """!Module selected from choice, update command prompt"""
         """!Module selected from choice, update command prompt"""
-        cmd  = event.GetString().split(' ', 1)[0]
-
+        cmd  = self._searchChoice.GetStringSelection()
         self.moduleSelected.emit(name = cmd)
         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):
     def Reset(self):
         """!Reset widget"""
         """!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):
 class ManageSettingsWidget(wx.Panel):
     """!Widget which allows loading and saving settings into file."""
     """!Widget which allows loading and saving settings into file."""

+ 8 - 6
gui/wxpython/lmgr/frame.py

@@ -103,6 +103,7 @@ class GMFrame(wx.Frame):
         
         
         self._giface = LayerManagerGrassInterface(self)
         self._giface = LayerManagerGrassInterface(self)
         
         
+        self._menuTreeBuilder = LayerManagerMenuData()
         self._auimgr = wx.aui.AuiManager(self)
         self._auimgr = wx.aui.AuiManager(self)
         
         
         
         
@@ -232,7 +233,7 @@ class GMFrame(wx.Frame):
         
         
     def _createMenuBar(self):
     def _createMenuBar(self):
         """!Creates menu bar"""
         """!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.SetMenuBar(self.menubar)
         self.menucmd = self.menubar.GetCmd()
         self.menucmd = self.menubar.GetCmd()
         
         
@@ -276,6 +277,7 @@ class GMFrame(wx.Frame):
         self._gconsole = GConsole(guiparent = self, giface = self._giface,
         self._gconsole = GConsole(guiparent = self, giface = self._giface,
                                   ignoredCmdPattern = '^d\..*|^r[3]?\.mapcalc$|^i.group')
                                   ignoredCmdPattern = '^d\..*|^r[3]?\.mapcalc$|^i.group')
         self.goutput = GConsoleWindow(parent = self, gconsole = self._gconsole,
         self.goutput = GConsoleWindow(parent = self, gconsole = self._gconsole,
+                                      menuModel=self._menuTreeBuilder.GetModel(),
                                       gcstyle = GC_SEARCH | GC_PROMPT)
                                       gcstyle = GC_SEARCH | GC_PROMPT)
         self.notebook.AddPage(page = self.goutput, text = _("Command console"), name = 'output')
         self.notebook.AddPage(page = self.goutput, text = _("Command console"), name = 'output')
 
 
@@ -313,7 +315,8 @@ class GMFrame(wx.Frame):
         
         
         # create 'search module' notebook page
         # create 'search module' notebook page
         if not UserSettings.Get(group = 'manager', key = 'hideTabs', subkey = 'search'):
         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')
             self.notebook.AddPage(page = self.search, text = _("Search module"), name = 'search')
         else:
         else:
             self.search = None
             self.search = None
@@ -691,12 +694,11 @@ class GMFrame(wx.Frame):
 
 
         Return command as a list"""
         Return command as a list"""
         layer = None
         layer = None
-        
         if event:
         if event:
             cmd = self.menucmd[event.GetId()]
             cmd = self.menucmd[event.GetId()]
         else:
         else:
             cmd = ''
             cmd = ''
-        
+
         try:
         try:
             cmdlist = cmd.split(' ')
             cmdlist = cmd.split(' ')
         except: # already list?
         except: # already list?
@@ -725,13 +727,13 @@ class GMFrame(wx.Frame):
 
 
     def RunMenuCmd(self, event = None, cmd = []):
     def RunMenuCmd(self, event = None, cmd = []):
         """!Run command selected from menu"""
         """!Run command selected from menu"""
-        if event:
+        if event:       
             cmd = self.GetMenuCmd(event)
             cmd = self.GetMenuCmd(event)
         self._gconsole.RunCmd(cmd)
         self._gconsole.RunCmd(cmd)
 
 
     def OnMenuCmd(self, event = None, cmd = []):
     def OnMenuCmd(self, event = None, cmd = []):
         """!Parse command selected from menu"""
         """!Parse command selected from menu"""
-        if event:
+        if event:       
             cmd = self.GetMenuCmd(event)
             cmd = self.GetMenuCmd(event)
         GUI(parent = self).ParseCommand(cmd)
         GUI(parent = self).ParseCommand(cmd)
         
         

+ 3 - 31
gui/wxpython/lmgr/menudata.py

@@ -16,41 +16,13 @@ This program is free software under the GNU General Public License
 """
 """
 
 
 import os
 import os
-import sys
 
 
 from core.globalvar import ETCWXDIR
 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):
     def __init__(self, filename = None):
         if not filename:
         if not filename:
-            global etcwxdir
             filename = os.path.join(ETCWXDIR, 'xml', 'menudata.xml')
             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)

+ 65 - 248
gui/wxpython/modules/extensions.py

@@ -5,7 +5,7 @@
 
 
 Classes:
 Classes:
  - extensions::InstallExtensionWindow
  - extensions::InstallExtensionWindow
- - extensions::ExtensionTree
+ - extensions::ExtensionTreeModelBuilder
  - extensions::UninstallExtensionWindow
  - extensions::UninstallExtensionWindow
  - extensions::CheckListExtension
  - extensions::CheckListExtension
 
 
@@ -15,115 +15,24 @@ This program is free software under the GNU General Public License
 (>=v2). Read the file COPYING that comes with GRASS for details.
 (>=v2). Read the file COPYING that comes with GRASS for details.
 
 
 @author Martin Landa <landa.martin gmail.com>
 @author Martin Landa <landa.martin gmail.com>
+@author Anna Petrasova <kratochanna gmail.com>
 """
 """
 
 
 import os
 import os
 import sys
 import sys
 
 
 import wx
 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 grass.script import task as gtask
 
 
 from core             import globalvar
 from core             import globalvar
 from core.gcmd        import GError, RunCommand
 from core.gcmd        import GError, RunCommand
 from core.utils       import SetAddOnPath
 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):
 class InstallExtensionWindow(wx.Frame):
     def __init__(self, parent, id = wx.ID_ANY,
     def __init__(self, parent, id = wx.ID_ANY,
                  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
                  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
@@ -142,13 +51,15 @@ class InstallExtensionWindow(wx.Frame):
         
         
         self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
         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)
                                          showChoice = False)
         self.search.SetSelection(0)
         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
         # show text in statusbar when notification appears
         self.search.showNotification.connect(lambda message: self.SetStatusText(message))
         self.search.showNotification.connect(lambda message: self.SetStatusText(message))
 
 
@@ -174,26 +85,26 @@ class InstallExtensionWindow(wx.Frame):
                 continue
                 continue
             self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
             self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
                                              label = desc)
                                              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.statusbar = self.CreateStatusBar(number = 1)
         
         
         self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
         self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
                                   label = _("&Fetch"))
                                   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.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
         self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
         self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
                                     label = _("&Install"))
                                     label = _("&Install"))
         self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
         self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
         self.btnInstall.Enable(False)
         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.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
         self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
         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)
         wx.CallAfter(self._fetch)
         
         
@@ -246,13 +157,11 @@ class InstallExtensionWindow(wx.Frame):
 
 
     def _getCmd(self):
     def _getCmd(self):
         item = self.tree.GetSelected()
         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)
             GError(_("Extension not defined"), parent = self)
             return
             return
+
+        name = item[0].data['command']
         
         
         flags = list()
         flags = list()
         for key in self.options.keys():
         for key in self.options.keys():
@@ -264,31 +173,6 @@ class InstallExtensionWindow(wx.Frame):
         
         
         return ['g.extension'] + flags + ['extension=' + name,
         return ['g.extension'] + flags + ['extension=' + name,
                                           'svnurl=' + self.repo.GetValue().strip()]
                                           '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):
     def OnFetch(self, event):
         """!Fetch list of available extensions"""
         """!Fetch list of available extensions"""
@@ -298,22 +182,22 @@ class InstallExtensionWindow(wx.Frame):
         """!Fetch list of available extensions"""
         """!Fetch list of available extensions"""
         wx.BeginBusyCursor()
         wx.BeginBusyCursor()
         self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
         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)
         self.SetStatusText("", 0)
         wx.EndBusyCursor()
         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:
         if data and 'command' in data:
-            self.OnInstall(event = None)
+            self.OnInstall(event=None)
         
         
     def OnInstall(self, event):
     def OnInstall(self, event):
         """!Install selected extension"""
         """!Install selected extension"""
         log = self.parent.GetLogWindow()
         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):
     def OnDone(self, cmd, returncode):
         if returncode == 0:
         if returncode == 0:
@@ -322,11 +206,9 @@ class InstallExtensionWindow(wx.Frame):
             
             
             globalvar.UpdateGRASSAddOnCommands()
             globalvar.UpdateGRASSAddOnCommands()
 
 
-    def OnItemSelected(self, event):
+    def OnItemSelected(self, node):
         """!Item selected"""
         """!Item selected"""
-        item = event.GetItem()
-        self.tree.itemSelected = item
-        data = self.tree.GetPyData(item)
+        data = node.data
         if data is None:
         if data is None:
             self.SetStatusText('', 0)
             self.SetStatusText('', 0)
             self.btnInstall.Enable(False)
             self.btnInstall.Enable(False)
@@ -334,35 +216,27 @@ class InstallExtensionWindow(wx.Frame):
             self.SetStatusText(data.get('description', ''), 0)
             self.SetStatusText(data.get('description', ''), 0)
             self.btnInstall.Enable(True)
             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',
         for prefix in ('display', 'database',
                        'general', 'imagery',
                        'general', 'imagery',
                        'misc', 'postscript', 'paint',
                        'misc', 'postscript', 'paint',
                        'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
                        '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):
     def _expandPrefix(self, c):
         name = { 'd'  : 'display',
         name = { 'd'  : 'display',
@@ -384,22 +258,9 @@ class ExtensionTree(ItemTree):
         
         
         return c
         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):
     def Load(self, url, full = True):
         """!Load list of extensions"""
         """!Load list of extensions"""
-        self.DeleteAllItems()
-        self.root = self.AddRoot(_("Menu tree"))
-        self._initTree()
+        self._emptyTree()
         
         
         if full:
         if full:
             flags = 'g'
             flags = 'g'
@@ -411,7 +272,7 @@ class ExtensionTree(ItemTree):
         if not ret:
         if not ret:
             return
             return
         
         
-        mdict = dict()
+        currentNode = None
         for line in ret.splitlines():
         for line in ret.splitlines():
             if full:
             if full:
                 try:
                 try:
@@ -419,73 +280,35 @@ class ExtensionTree(ItemTree):
                 except ValueError:
                 except ValueError:
                     key = 'name'
                     key = 'name'
                     value = line
                     value = line
-                
+
                 if key == 'name':
                 if key == 'name':
                     try:
                     try:
                         prefix, name = value.split('.', 1)
                         prefix, name = value.split('.', 1)
                     except ValueError:
                     except ValueError:
                         prefix = ''
                         prefix = ''
                         name = value
                         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:
                 else:
-                    mdict[prefix][name][key] = value
+                    if currentNode is not None:
+                        currentNode.data[key] = value
             else:
             else:
                 try:
                 try:
                     prefix, name = line.strip().split('.', 1)
                     prefix, name = line.strip().split('.', 1)
-                except:
+                except ValueError:
                     prefix = ''
                     prefix = ''
                     name = line.strip()
                     name = line.strip()
                 
                 
                 if self._expandPrefix(prefix) == prefix:
                 if self._expandPrefix(prefix) == 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
-
-    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
+                module = prefix + '.' + name
+                mainNode = self.mainNodes[self._expandPrefix(prefix)]
+                currentNode = self.model.AppendNode(parent=mainNode, label=module)
+                currentNode.data = {'command': module,
+                                    'keywords': '',
+                                    'description': ''}        
+
 
 
 class UninstallExtensionWindow(wx.Frame):
 class UninstallExtensionWindow(wx.Frame):
     def __init__(self, parent, id = wx.ID_ANY,
     def __init__(self, parent, id = wx.ID_ANY,
@@ -509,7 +332,7 @@ class UninstallExtensionWindow(wx.Frame):
         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
         
         
         self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
         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()
         self._layout()
         
         
@@ -536,13 +359,8 @@ class UninstallExtensionWindow(wx.Frame):
         
         
         self.Layout()
         self.Layout()
 
 
-    def OnCloseWindow(self, event):
-        """!Close window"""
-        self.Destroy()
-
     def OnUninstall(self, event):
     def OnUninstall(self, event):
         """!Uninstall selected extensions"""
         """!Uninstall selected extensions"""
-        log = self.parent.GetLogWindow()
         eList = self.extList.GetExtensions()
         eList = self.extList.GetExtensions()
         if not eList:
         if not eList:
             GError(_("No extension selected for removal. "
             GError(_("No extension selected for removal. "
@@ -568,8 +386,7 @@ class UninstallExtensionWindow(wx.Frame):
         
         
         # update prompt
         # update prompt
         globalvar.UpdateGRASSAddOnCommands(eList)
         globalvar.UpdateGRASSAddOnCommands(eList)
-        log = self.parent.GetLogWindow()
-        log.GetPrompt().SetFilter(None)
+
         
         
 class CheckListExtension(GListCtrl):
 class CheckListExtension(GListCtrl):
     """!List of mapset/owner/group"""
     """!List of mapset/owner/group"""

+ 1 - 1
gui/wxpython/psmap/frame.py

@@ -59,7 +59,7 @@ class PsMapFrame(wx.Frame):
         wx.Frame.__init__(self, parent = parent, id = id, title = title, name = "PsMap", **kwargs)
         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))
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         #menubar
         #menubar
-        self.menubar = Menu(parent = self, data = PsMapMenuData())
+        self.menubar = Menu(parent = self, model = PsMapMenuData().GetModel())
         self.SetMenuBar(self.menubar)
         self.SetMenuBar(self.menubar)
         #toolbar
         #toolbar
 
 

+ 4 - 4
gui/wxpython/psmap/menudata.py

@@ -16,10 +16,10 @@ This program is free software under the GNU General Public License
 
 
 import os
 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):
     def __init__(self, path = None):
         """!Menu for Cartographic Composer (psmap.py)
         """!Menu for Cartographic Composer (psmap.py)
         
         
@@ -28,4 +28,4 @@ class PsMapMenuData(MenuData):
         if not path:
         if not path:
             path = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_psmap.xml')
             path = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_psmap.xml')
         
         
-        MenuData.__init__(self, path)
+        MenuTreeModelBuilder.__init__(self, path)

+ 1 - 2
gui/wxpython/scripts/vkrige.py

@@ -100,8 +100,7 @@ class KrigingPanel(wx.Panel):
         
         
         ## Command output. From menuform module, cmdPanel class
         ## Command output. From menuform module, cmdPanel class
         self._gconsole = gconsole.GConsole(guiparent = self)
         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.goutputId = self.RPackagesBook.GetPageCount()
         self.outpage = self.RPackagesBook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
         self.outpage = self.RPackagesBook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
         self._gconsole.Bind(gconsole.EVT_CMD_RUN,
         self._gconsole.Bind(gconsole.EVT_CMD_RUN,

+ 6 - 5
gui/wxpython/wxpythonlib.dox

@@ -71,13 +71,12 @@ available in GRASS 5 and GRASS 6.
  - goutput::GStdout
  - goutput::GStdout
  - goutput::GStderr
  - goutput::GStderr
  - goutput::GConsole
  - goutput::GConsole
-- core::menudata
- - menudata::MenuData
+- core::menutree
+ - menutree::MenuTreeModelBuilder
 - core::modulesdata
 - core::modulesdata
  - modulesdata::ModulesData
  - modulesdata::ModulesData
 - core::render
 - core::render
  - render::Layer
  - render::Layer
- - render::Layer
  - render::MapLayer
  - render::MapLayer
  - render::Overlay
  - render::Overlay
  - render::Map
  - render::Map
@@ -152,7 +151,6 @@ available in GRASS 5 and GRASS 6.
 - gui_core::menu
 - gui_core::menu
  - menu::Menu
  - menu::Menu
  - menu::SearchModuleWindow
  - menu::SearchModuleWindow
- - menu::MenuTree
 - gui_core::preferences
 - gui_core::preferences
  - preferences::PreferencesBaseDialog
  - preferences::PreferencesBaseDialog
  - preferences::PreferencesDialog
  - preferences::PreferencesDialog
@@ -161,8 +159,12 @@ available in GRASS 5 and GRASS 6.
 - gui_core::prompt
 - gui_core::prompt
  - prompt::GPrompt
  - prompt::GPrompt
  - prompt::GPromptSTC
  - prompt::GPromptSTC
+- gui_core::query
+ - query::QueryDialog
 - gui_core::toolbars
 - gui_core::toolbars
  - toolbars::BaseToolbar
  - toolbars::BaseToolbar
+- gui_core::treeview
+ - treeview::TreeView
 - gui_core::widgets
 - gui_core::widgets
  - widgets::ScrolledPanel
  - widgets::ScrolledPanel
  - widgets::NTCValidator
  - widgets::NTCValidator
@@ -175,7 +177,6 @@ available in GRASS 5 and GRASS 6.
  - widgets::IntegerValidator
  - widgets::IntegerValidator
  - widgets::FloatValidator
  - widgets::FloatValidator
  - widgets::NTCValidator
  - widgets::NTCValidator
- - widgets::ItemTree
  - widgets::SearchModuleWidget
  - widgets::SearchModuleWidget
 
 
 \subsection lmgr Layer Manager
 \subsection lmgr Layer Manager