[GRASS-SVN] r49347 - in grass/trunk/gui/wxpython: . core dbm gcp gmodeler gui_core gui_modules icons lmgr location_wizard mapdisp modules nviz psmap scripts tools vdigit wxplot

svn_grass at osgeo.org svn_grass at osgeo.org
Thu Nov 24 06:46:55 EST 2011


Author: martinl
Date: 2011-11-24 03:46:55 -0800 (Thu, 24 Nov 2011)
New Revision: 49347

Added:
   grass/trunk/gui/wxpython/core/
   grass/trunk/gui/wxpython/core/debug.py
   grass/trunk/gui/wxpython/core/gcmd.py
   grass/trunk/gui/wxpython/core/globalvar.py
   grass/trunk/gui/wxpython/core/menudata.py
   grass/trunk/gui/wxpython/core/render.py
   grass/trunk/gui/wxpython/core/settings.py
   grass/trunk/gui/wxpython/core/units.py
   grass/trunk/gui/wxpython/core/utils.py
   grass/trunk/gui/wxpython/core/workspace.py
   grass/trunk/gui/wxpython/create__init__.py
   grass/trunk/gui/wxpython/dbm/
   grass/trunk/gui/wxpython/dbm/dialogs.py
   grass/trunk/gui/wxpython/dbm/manager.py
   grass/trunk/gui/wxpython/dbm/sqlbuilder.py
   grass/trunk/gui/wxpython/dbm/vinfo.py
   grass/trunk/gui/wxpython/gcp/
   grass/trunk/gui/wxpython/gcp/manager.py
   grass/trunk/gui/wxpython/gcp/mapdisp.py
   grass/trunk/gui/wxpython/gcp/toolbars.py
   grass/trunk/gui/wxpython/gmodeler/
   grass/trunk/gui/wxpython/gmodeler/dialogs.py
   grass/trunk/gui/wxpython/gmodeler/frame.py
   grass/trunk/gui/wxpython/gmodeler/model.py
   grass/trunk/gui/wxpython/gmodeler/model_file.py
   grass/trunk/gui/wxpython/gmodeler/preferences.py
   grass/trunk/gui/wxpython/gui_core/
   grass/trunk/gui/wxpython/gui_core/dialogs.py
   grass/trunk/gui/wxpython/gui_core/forms.py
   grass/trunk/gui/wxpython/gui_core/ghelp.py
   grass/trunk/gui/wxpython/gui_core/goutput.py
   grass/trunk/gui/wxpython/gui_core/gselect.py
   grass/trunk/gui/wxpython/gui_core/mapdisp.py
   grass/trunk/gui/wxpython/gui_core/mapwindow.py
   grass/trunk/gui/wxpython/gui_core/menu.py
   grass/trunk/gui/wxpython/gui_core/preferences.py
   grass/trunk/gui/wxpython/gui_core/prompt.py
   grass/trunk/gui/wxpython/gui_core/toolbars.py
   grass/trunk/gui/wxpython/gui_core/widgets.py
   grass/trunk/gui/wxpython/lmgr/
   grass/trunk/gui/wxpython/lmgr/layertree.py
   grass/trunk/gui/wxpython/lmgr/menudata.py
   grass/trunk/gui/wxpython/lmgr/pyshell.py
   grass/trunk/gui/wxpython/lmgr/toolbars.py
   grass/trunk/gui/wxpython/location_wizard/
   grass/trunk/gui/wxpython/location_wizard/base.py
   grass/trunk/gui/wxpython/location_wizard/dialogs.py
   grass/trunk/gui/wxpython/location_wizard/wizard.py
   grass/trunk/gui/wxpython/mapdisp/
   grass/trunk/gui/wxpython/mapdisp/frame.py
   grass/trunk/gui/wxpython/mapdisp/gprint.py
   grass/trunk/gui/wxpython/mapdisp/mapwindow.py
   grass/trunk/gui/wxpython/mapdisp/statusbar.py
   grass/trunk/gui/wxpython/mapdisp/toolbars.py
   grass/trunk/gui/wxpython/modules/
   grass/trunk/gui/wxpython/modules/colorrules.py
   grass/trunk/gui/wxpython/modules/histogram.py
   grass/trunk/gui/wxpython/modules/mcalc_builder.py
   grass/trunk/gui/wxpython/modules/ogc_services.py
   grass/trunk/gui/wxpython/modules/vclean.py
   grass/trunk/gui/wxpython/nviz/
   grass/trunk/gui/wxpython/nviz/animation.py
   grass/trunk/gui/wxpython/nviz/main.py
   grass/trunk/gui/wxpython/nviz/mapwindow.py
   grass/trunk/gui/wxpython/nviz/preferences.py
   grass/trunk/gui/wxpython/nviz/tools.py
   grass/trunk/gui/wxpython/nviz/workspace.py
   grass/trunk/gui/wxpython/nviz/wxnviz.py
   grass/trunk/gui/wxpython/psmap/
   grass/trunk/gui/wxpython/psmap/dialogs.py
   grass/trunk/gui/wxpython/psmap/frame.py
   grass/trunk/gui/wxpython/psmap/menudata.py
   grass/trunk/gui/wxpython/psmap/toolbars.py
   grass/trunk/gui/wxpython/states.txt
   grass/trunk/gui/wxpython/vdigit/
   grass/trunk/gui/wxpython/vdigit/dialogs.py
   grass/trunk/gui/wxpython/vdigit/main.py
   grass/trunk/gui/wxpython/vdigit/mapwindow.py
   grass/trunk/gui/wxpython/vdigit/preferences.py
   grass/trunk/gui/wxpython/vdigit/toolbars.py
   grass/trunk/gui/wxpython/vdigit/wxdigit.py
   grass/trunk/gui/wxpython/vdigit/wxdisplay.py
   grass/trunk/gui/wxpython/wxplot/
   grass/trunk/gui/wxpython/wxplot/base.py
   grass/trunk/gui/wxpython/wxplot/dialogs.py
   grass/trunk/gui/wxpython/wxplot/histogram.py
   grass/trunk/gui/wxpython/wxplot/profile.py
   grass/trunk/gui/wxpython/wxplot/scatter.py
Removed:
   grass/trunk/gui/wxpython/gui_modules/__init__.py
   grass/trunk/gui/wxpython/gui_modules/colorrules.py
   grass/trunk/gui/wxpython/gui_modules/dbm.py
   grass/trunk/gui/wxpython/gui_modules/dbm_base.py
   grass/trunk/gui/wxpython/gui_modules/dbm_dialogs.py
   grass/trunk/gui/wxpython/gui_modules/debug.py
   grass/trunk/gui/wxpython/gui_modules/disp_print.py
   grass/trunk/gui/wxpython/gui_modules/gcmd.py
   grass/trunk/gui/wxpython/gui_modules/gcpmanager.py
   grass/trunk/gui/wxpython/gui_modules/gcpmapdisp.py
   grass/trunk/gui/wxpython/gui_modules/gdialogs.py
   grass/trunk/gui/wxpython/gui_modules/ghelp.py
   grass/trunk/gui/wxpython/gui_modules/globalvar.py
   grass/trunk/gui/wxpython/gui_modules/gmodeler.py
   grass/trunk/gui/wxpython/gui_modules/goutput.py
   grass/trunk/gui/wxpython/gui_modules/gpyshell.py
   grass/trunk/gui/wxpython/gui_modules/gselect.py
   grass/trunk/gui/wxpython/gui_modules/histogram.py
   grass/trunk/gui/wxpython/gui_modules/layertree.py
   grass/trunk/gui/wxpython/gui_modules/location_wizard.py
   grass/trunk/gui/wxpython/gui_modules/mapdisp.py
   grass/trunk/gui/wxpython/gui_modules/mapdisp_statusbar.py
   grass/trunk/gui/wxpython/gui_modules/mapdisp_vdigit.py
   grass/trunk/gui/wxpython/gui_modules/mapdisp_window.py
   grass/trunk/gui/wxpython/gui_modules/mcalc_builder.py
   grass/trunk/gui/wxpython/gui_modules/menu.py
   grass/trunk/gui/wxpython/gui_modules/menudata.py
   grass/trunk/gui/wxpython/gui_modules/menuform.py
   grass/trunk/gui/wxpython/gui_modules/nviz.py
   grass/trunk/gui/wxpython/gui_modules/nviz_animation.py
   grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py
   grass/trunk/gui/wxpython/gui_modules/nviz_preferences.py
   grass/trunk/gui/wxpython/gui_modules/nviz_tools.py
   grass/trunk/gui/wxpython/gui_modules/ogc_services.py
   grass/trunk/gui/wxpython/gui_modules/preferences.py
   grass/trunk/gui/wxpython/gui_modules/prompt.py
   grass/trunk/gui/wxpython/gui_modules/psmap.py
   grass/trunk/gui/wxpython/gui_modules/psmap_dialogs.py
   grass/trunk/gui/wxpython/gui_modules/render.py
   grass/trunk/gui/wxpython/gui_modules/sqlbuilder.py
   grass/trunk/gui/wxpython/gui_modules/states.txt
   grass/trunk/gui/wxpython/gui_modules/toolbars.py
   grass/trunk/gui/wxpython/gui_modules/units.py
   grass/trunk/gui/wxpython/gui_modules/utils.py
   grass/trunk/gui/wxpython/gui_modules/vclean.py
   grass/trunk/gui/wxpython/gui_modules/vdigit.py
   grass/trunk/gui/wxpython/gui_modules/workspace.py
   grass/trunk/gui/wxpython/gui_modules/wxnviz.py
   grass/trunk/gui/wxpython/gui_modules/wxplot.py
   grass/trunk/gui/wxpython/gui_modules/wxplot_dialogs.py
   grass/trunk/gui/wxpython/gui_modules/wxvdigit.py
   grass/trunk/gui/wxpython/gui_modules/wxvdriver.py
Modified:
   grass/trunk/gui/wxpython/Makefile
   grass/trunk/gui/wxpython/icons/grass_icons.py
   grass/trunk/gui/wxpython/icons/icon.py
   grass/trunk/gui/wxpython/scripts/r.li.setup.py
   grass/trunk/gui/wxpython/scripts/vkrige.py
   grass/trunk/gui/wxpython/tools/update_menudata.py
   grass/trunk/gui/wxpython/wxgui.py
Log:
wxGUI major code reorganization

Modified: grass/trunk/gui/wxpython/Makefile
===================================================================
--- grass/trunk/gui/wxpython/Makefile	2011-11-24 09:13:37 UTC (rev 49346)
+++ grass/trunk/gui/wxpython/Makefile	2011-11-24 11:46:55 UTC (rev 49347)
@@ -9,21 +9,30 @@
 
 ETCDIR = $(ETC)/gui/wxpython
 
-SRCFILES := $(wildcard scripts/* compat/* gui_modules/* icons/*.* xml/*) gis_set.py gis_set_error.py wxgui.py README
+SRCFILES := $(wildcard compat/* icons/*.* scripts/* xml/*) \
+	$(wildcard core/* dbm/* gcp/* gmodeler/* gui_core/* lmgr/* \
+	mapdisp/* modules/* nviz/* psmap/* vdigit/* wxplot/* ) \
+	gis_set.py gis_set_error.py wxgui.py README
 DSTFILES := $(patsubst %,$(ETCDIR)/%,$(SRCFILES)) $(patsubst %.py,$(ETCDIR)/%.pyc,$(filter %.py,$(SRCFILES)))
 
-DSTDIRS := $(patsubst %,$(ETCDIR)/%,compat gui_modules icons scripts xml)
+PYDSTDIRS := $(patsubst %,$(ETCDIR)/%,core dbm gcp gmodeler gui_core lmgr \
+	mapdisp modules nviz psmap vdigit wxplot)
+DSTDIRS := $(patsubst %,$(ETCDIR)/%,compat icons scripts xml)
 
 default: $(DSTFILES) menustrings.py
 	$(MAKE) parsubdirs
 
-$(ETCDIR)/%: % | $(DSTDIRS)
+$(ETCDIR)/%: % | $(PYDSTDIRS) $(DSTDIRS)
 	$(INSTALL_DATA) $< $@
 
-menustrings.py: gui_modules/menudata.py $(ETCDIR)/xml/menudata.xml $(ETCDIR)/xml/menudata_modeler.xml 
+menustrings.py: core/menudata.py $(ETCDIR)/xml/menudata.xml $(ETCDIR)/xml/menudata_modeler.xml 
 	$(call run_grass,$(PYTHON) $< > $@)
 	$(call run_grass,$(PYTHON) $< "modeler" >> $@)
 
+$(PYDSTDIRS): %: | $(ETCDIR)
+	$(MKDIR) $@
+	$(call run_grass,$(PYTHON) create__init__.py $@)
+
 $(DSTDIRS): %: | $(ETCDIR)
 	$(MKDIR) $@
 

Copied: grass/trunk/gui/wxpython/core/debug.py (from rev 49282, grass/trunk/gui/wxpython/gui_modules/debug.py)
===================================================================
--- grass/trunk/gui/wxpython/core/debug.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/debug.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,75 @@
+"""!
+ at package core.debug
+
+ at brief wxGUI debugging
+
+Classes:
+ - DebugMsg
+
+ at code
+from core.debug import Debug
+Debug.msg (3, 'debug message')
+ at endcode
+         
+(C) 2007-2009, 2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+
+import grass.script as grass
+
+class DebugMsg:
+    """!wxGUI debugging
+
+    @code
+    g.gisenv set=WX_DEBUG=[0-5]
+    @endcode
+    """
+    def __init__(self):
+        # default level
+        self.debuglevel = 0
+        
+        self.SetLevel()
+
+    def SetLevel(self):
+        """!Initialize gui debug level
+        """
+        self.debuglevel = int(grass.gisenv().get('WX_DEBUG', 0))
+        
+    def msg(self, level, message, *args):
+        """!Print debug message
+
+        @param level debug level (0-5)
+        @param message message to be printed
+        @param *args formatting params
+        """
+        # self.SetLevel()
+        if self.debuglevel > 0 and level > 0 and level <= self.debuglevel:
+            if args:
+                sys.stderr.write("GUI D%d/%d: " % (level, self.debuglevel) + \
+                    message % args + os.linesep)
+            else:
+                sys.stderr.write("GUI D%d/%d: " % (level, self.debuglevel) + \
+                                     message + os.linesep)
+            sys.stderr.flush() # force flush (required for MS Windows)
+        
+    def GetLevel(self):
+        """!Return current GUI debug level"""
+        return self.debuglevel
+
+# Debug instance
+Debug = DebugMsg()
+
+# testing
+if __name__ == "__main__":
+    from core import cmd as gcmd
+    gcmd.RunCommand('g.gisenv',
+                    set = 'DEBUG=3')
+                
+    for level in range (4):
+        Debug.msg (level, "message level=%d" % level)

Copied: grass/trunk/gui/wxpython/core/gcmd.py (from rev 49282, grass/trunk/gui/wxpython/gui_modules/gcmd.py)
===================================================================
--- grass/trunk/gui/wxpython/core/gcmd.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/gcmd.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,673 @@
+"""!
+ at package core.gcmd
+
+ at brief wxGUI command interface
+
+Classes:
+ - GError
+ - GWarning
+ - GMessage
+ - GException
+ - Popen (from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554)
+ - Command
+ - CommandThread
+
+Functions:
+ - RunCommand
+
+(C) 2007-2008, 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+ at author Jachym Cepicky
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+import time
+import errno
+import signal
+import locale
+import traceback
+
+import wx
+
+try:
+    import subprocess
+except:
+    compatPath = os.path.join(globalvar.ETCWXDIR, "compat")
+    sys.path.append(compatPath)
+    import subprocess
+if subprocess.mswindows:
+    from win32file import ReadFile, WriteFile
+    from win32pipe import PeekNamedPipe
+    import msvcrt
+else:
+    import select
+    import fcntl
+from threading import Thread
+
+from grass.script import core as grass
+
+from core       import globalvar
+from core.debug import Debug
+
+def DecodeString(string):
+    """!Decode string using system encoding
+    
+    @param string string to be decoded
+    
+    @return decoded string
+    """
+    if not string:
+        return string
+    
+    enc = locale.getdefaultlocale()[1]
+    if enc:
+        Debug.msg(5, "DecodeString(): enc=%s" % enc)
+        return string.decode(enc)
+    
+    return string
+
+def EncodeString(string):
+    """!Return encoded string using system locales
+    
+    @param string string to be encoded
+    
+    @return encoded string
+    """
+    if not string:
+        return string
+    enc = locale.getdefaultlocale()[1]
+    if enc:
+        Debug.msg(5, "EncodeString(): enc=%s" % enc)
+        return string.encode(enc)
+    
+    return string
+
+class GError:
+    def __init__(self, message, parent = None, caption = None, showTraceback = True):
+        if not caption:
+            caption = _('Error')
+        style = wx.OK | wx.ICON_ERROR | wx.CENTRE
+        exc_type, exc_value, exc_traceback = sys.exc_info()
+        if exc_traceback:
+            exception = traceback.format_exc()
+            reason = exception.splitlines()[-1].split(':', 1)[-1].strip()
+        
+        if Debug.GetLevel() > 0 and exc_traceback:
+            sys.stderr.write(exception)
+        
+        if showTraceback and exc_traceback:
+            wx.MessageBox(parent = parent,
+                          message = message + '\n\n%s: %s\n\n%s' % \
+                              (_('Reason'),
+                               reason, exception),
+                          caption = caption,
+                          style = style)
+        else:
+            wx.MessageBox(parent = parent,
+                          message = message,
+                          caption = caption,
+                          style = style)
+
+class GWarning:
+    def __init__(self, message, parent = None):
+        caption = _('Warning')
+        style = wx.OK | wx.ICON_WARNING | wx.CENTRE
+        wx.MessageBox(parent = parent,
+                      message = message,
+                      caption = caption,
+                      style = style)
+        
+class GMessage:
+    def __init__(self, message, parent = None):
+        caption = _('Message')
+        style = wx.OK | wx.ICON_INFORMATION | wx.CENTRE
+        wx.MessageBox(parent = parent,
+                      message = message,
+                      caption = caption,
+                      style = style)
+
+class GException(Exception):
+    def __init__(self, value = ''):
+        self.value = value
+
+    def __str__(self):
+        return str(self.value)
+
+class Popen(subprocess.Popen):
+    """!Subclass subprocess.Popen"""
+    def __init__(self, args, **kwargs):
+        if subprocess.mswindows:
+            args = map(EncodeString, args)
+        
+        subprocess.Popen.__init__(self, args, **kwargs)
+        
+    def recv(self, maxsize = None):
+        return self._recv('stdout', maxsize)
+    
+    def recv_err(self, maxsize = None):
+        return self._recv('stderr', maxsize)
+
+    def send_recv(self, input = '', maxsize = None):
+        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
+
+    def get_conn_maxsize(self, which, maxsize):
+        if maxsize is None:
+            maxsize = 1024
+        elif maxsize < 1:
+            maxsize = 1
+        return getattr(self, which), maxsize
+    
+    def _close(self, which):
+        getattr(self, which).close()
+        setattr(self, which, None)
+
+    def kill(self):
+        """!Try to kill running process"""
+        if subprocess.mswindows:
+            import win32api
+            handle = win32api.OpenProcess(1, 0, self.pid)
+            return (0 != win32api.TerminateProcess(handle, 0))
+	else:
+            try:
+                os.kill(-self.pid, signal.SIGTERM) # kill whole group
+            except OSError:
+                pass
+
+    if subprocess.mswindows:
+        def send(self, input):
+            if not self.stdin:
+                return None
+
+            try:
+                x = msvcrt.get_osfhandle(self.stdin.fileno())
+                (errCode, written) = WriteFile(x, input)
+            except ValueError:
+                return self._close('stdin')
+            except (subprocess.pywintypes.error, Exception), why:
+                if why[0] in (109, errno.ESHUTDOWN):
+                    return self._close('stdin')
+                raise
+
+            return written
+
+        def _recv(self, which, maxsize):
+            conn, maxsize = self.get_conn_maxsize(which, maxsize)
+            if conn is None:
+                return None
+            
+            try:
+                x = msvcrt.get_osfhandle(conn.fileno())
+                (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
+                if maxsize < nAvail:
+                    nAvail = maxsize
+                if nAvail > 0:
+                    (errCode, read) = ReadFile(x, nAvail, None)
+            except ValueError:
+                return self._close(which)
+            except (subprocess.pywintypes.error, Exception), why:
+                if why[0] in (109, errno.ESHUTDOWN):
+                    return self._close(which)
+                raise
+            
+            if self.universal_newlines:
+                read = self._translate_newlines(read)
+            return read
+
+    else:
+        def send(self, input):
+            if not self.stdin:
+                return None
+
+            if not select.select([], [self.stdin], [], 0)[1]:
+                return 0
+
+            try:
+                written = os.write(self.stdin.fileno(), input)
+            except OSError, why:
+                if why[0] == errno.EPIPE: #broken pipe
+                    return self._close('stdin')
+                raise
+
+            return written
+
+        def _recv(self, which, maxsize):
+            conn, maxsize = self.get_conn_maxsize(which, maxsize)
+            if conn is None:
+                return None
+            
+            flags = fcntl.fcntl(conn, fcntl.F_GETFL)
+            if not conn.closed:
+                fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+            
+            try:
+                if not select.select([conn], [], [], 0)[0]:
+                    return ''
+                
+                r = conn.read(maxsize)
+                
+                if not r:
+                    return self._close(which)
+    
+                if self.universal_newlines:
+                    r = self._translate_newlines(r)
+                return r
+            finally:
+                if not conn.closed:
+                    fcntl.fcntl(conn, fcntl.F_SETFL, flags)
+
+message = "Other end disconnected!"
+
+def recv_some(p, t = .1, e = 1, tr = 5, stderr = 0):
+    if tr < 1:
+        tr = 1
+    x = time.time()+t
+    y = []
+    r = ''
+    pr = p.recv
+    if stderr:
+        pr = p.recv_err
+    while time.time() < x or r:
+        r = pr()
+        if r is None:
+            if e:
+                raise Exception(message)
+            else:
+                break
+        elif r:
+            y.append(r)
+        else:
+            time.sleep(max((x-time.time())/tr, 0))
+    return ''.join(y)
+    
+def send_all(p, data):
+    while len(data):
+        sent = p.send(data)
+        if sent is None:
+            raise Exception(message)
+        data = buffer(data, sent)
+
+class Command:
+    """!Run command in separate thread. Used for commands launched
+    on the background.
+
+    If stdout/err is redirected, write() method is required for the
+    given classes.
+
+    @code
+        cmd = Command(cmd=['d.rast', 'elevation.dem'], verbose=3, wait=True)
+
+        if cmd.returncode == None:
+            print 'RUNNING?'
+        elif cmd.returncode == 0:
+            print 'SUCCESS'
+        else:
+            print 'FAILURE (%d)' % cmd.returncode
+    @endcode
+    """
+    def __init__ (self, cmd, stdin = None,
+                  verbose = None, wait = True, rerr = False,
+                  stdout = None, stderr = None):
+        """
+        @param cmd     command given as list
+        @param stdin   standard input stream
+        @param verbose verbose level [0, 3] (--q, --v)
+        @param wait    wait for child execution terminated
+        @param rerr    error handling (when GException raised).
+        True for redirection to stderr, False for GUI dialog,
+        None for no operation (quiet mode)
+        @param stdout  redirect standard output or None
+        @param stderr  redirect standard error output or None
+        """
+        Debug.msg(1, "gcmd.Command(): %s" % ' '.join(cmd))
+        self.cmd = cmd
+        self.stderr = stderr
+	
+        #
+        # set verbosity level
+        #
+        verbose_orig = None
+        if ('--q' not in self.cmd and '--quiet' not in self.cmd) and \
+                ('--v' not in self.cmd and '--verbose' not in self.cmd):
+            if verbose is not None:
+                if verbose == 0:
+                    self.cmd.append('--quiet')
+                elif verbose == 3:
+                    self.cmd.append('--verbose')
+                else:
+                    verbose_orig = os.getenv("GRASS_VERBOSE")
+                    os.environ["GRASS_VERBOSE"] = str(verbose)
+
+        #
+        # create command thread
+        #
+        self.cmdThread = CommandThread(cmd, stdin,
+                                       stdout, stderr)
+        self.cmdThread.start()
+        
+        if wait:
+            self.cmdThread.join()
+            if self.cmdThread.module:
+                self.cmdThread.module.wait()
+                self.returncode = self.cmdThread.module.returncode
+            else:
+                self.returncode = 1
+        else:
+            self.cmdThread.join(0.5)
+            self.returncode = None
+
+        if self.returncode is not None:
+            Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=%d, alive=%s" % \
+                           (' '.join(cmd), wait, self.returncode, self.cmdThread.isAlive()))
+            if rerr is not None and self.returncode != 0:
+                if rerr is False: # GUI dialog
+                    raise GException("%s '%s'%s%s%s %s%s" % \
+                                         (_("Execution failed:"),
+                                          ' '.join(self.cmd),
+                                          os.linesep, os.linesep,
+                                          _("Details:"),
+                                          os.linesep,
+                                          _("Error: ") + self.__GetError()))
+                elif rerr == sys.stderr: # redirect message to sys
+                    stderr.write("Execution failed: '%s'" % (' '.join(self.cmd)))
+                    stderr.write("%sDetails:%s%s" % (os.linesep,
+                                                     _("Error: ") + self.__GetError(),
+                                                     os.linesep))
+            else:
+                pass # nop
+        else:
+            Debug.msg (3, "Command(): cmd='%s', wait=%s, returncode=?, alive=%s" % \
+                           (' '.join(cmd), wait, self.cmdThread.isAlive()))
+
+        if verbose_orig:
+            os.environ["GRASS_VERBOSE"] = verbose_orig
+        elif "GRASS_VERBOSE" in os.environ:
+            del os.environ["GRASS_VERBOSE"]
+            
+    def __ReadOutput(self, stream):
+        """!Read stream and return list of lines
+
+        @param stream stream to be read
+        """
+        lineList = []
+
+        if stream is None:
+            return lineList
+
+        while True:
+            line = stream.readline()
+            if not line:
+                break
+            line = line.replace('%s' % os.linesep, '').strip()
+            lineList.append(line)
+
+        return lineList
+                    
+    def __ReadErrOutput(self):
+        """!Read standard error output and return list of lines"""
+        return self.__ReadOutput(self.cmdThread.module.stderr)
+
+    def __ProcessStdErr(self):
+        """
+        Read messages/warnings/errors from stderr
+
+        @return list of (type, message)
+        """
+        if self.stderr is None:
+            lines = self.__ReadErrOutput()
+        else:
+            lines = self.cmdThread.error.strip('%s' % os.linesep). \
+                split('%s' % os.linesep)
+        
+        msg = []
+
+        type    = None
+        content = ""
+        for line in lines:
+            if len(line) == 0:
+                continue
+            if 'GRASS_' in line: # error or warning
+                if 'GRASS_INFO_WARNING' in line: # warning
+                    type = "WARNING"
+                elif 'GRASS_INFO_ERROR' in line: # error
+                    type = "ERROR"
+                elif 'GRASS_INFO_END': # end of message
+                    msg.append((type, content))
+                    type = None
+                    content = ""
+                
+                if type:
+                    content += line.split(':', 1)[1].strip()
+            else: # stderr
+                msg.append((None, line.strip()))
+
+        return msg
+
+    def __GetError(self):
+        """!Get error message or ''"""
+        if not self.cmdThread.module:
+            return _("Unable to exectute command: '%s'") % ' '.join(self.cmd)
+
+        for type, msg in self.__ProcessStdErr():
+            if type == 'ERROR':
+                enc = locale.getdefaultlocale()[1]
+                if enc:
+                    return unicode(msg, enc)
+                else:
+                    return msg
+        
+        return ''
+    
+class CommandThread(Thread):
+    """!Create separate thread for command. Used for commands launched
+    on the background."""
+    def __init__ (self, cmd, stdin = None,
+                  stdout = sys.stdout, stderr = sys.stderr):
+        """
+        @param cmd command (given as list)
+        @param stdin standard input stream 
+        @param stdout redirect standard output or None
+        @param stderr redirect standard error output or None
+        """
+        Thread.__init__(self)
+        
+        self.cmd    = cmd
+        self.stdin  = stdin
+        self.stdout = stdout
+        self.stderr = stderr
+        
+        self.module = None
+        self.error  = ''
+        
+        self._want_abort = False
+        self.aborted = False
+        
+        self.setDaemon(True)
+        
+        # set message formatting
+        self.message_format = os.getenv("GRASS_MESSAGE_FORMAT")
+        os.environ["GRASS_MESSAGE_FORMAT"] = "gui"
+        
+    def __del__(self):
+        if self.message_format:
+            os.environ["GRASS_MESSAGE_FORMAT"] = self.message_format
+        else:
+            del os.environ["GRASS_MESSAGE_FORMAT"]
+        
+    def run(self):
+        """!Run command"""
+        if len(self.cmd) == 0:
+            return
+
+        Debug.msg(1, "gcmd.CommandThread(): %s" % ' '.join(self.cmd))
+
+        self.startTime = time.time()
+        try:
+            self.module = Popen(self.cmd,
+                                stdin = subprocess.PIPE,
+                                stdout = subprocess.PIPE,
+                                stderr = subprocess.PIPE,
+                                shell = sys.platform == "win32")
+        except OSError, e:
+            self.error = str(e)
+            return 1
+        
+        if self.stdin: # read stdin if requested ...
+            self.module.stdin.write(self.stdin)
+            self.module.stdin.close()
+            
+        # redirect standard outputs...
+        self._redirect_stream()
+        
+    def _redirect_stream(self):
+        """!Redirect stream"""
+        if self.stdout:
+            # make module stdout/stderr non-blocking
+            out_fileno = self.module.stdout.fileno()
+	    if not subprocess.mswindows:
+                flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
+                fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+                
+        if self.stderr:
+            # make module stdout/stderr non-blocking
+            out_fileno = self.module.stderr.fileno()
+	    if not subprocess.mswindows:
+                flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
+                fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+        
+        # wait for the process to end, sucking in stuff until it does end
+        while self.module.poll() is None:
+            if self._want_abort: # abort running process
+                self.module.kill()
+                self.aborted = True
+                return 
+            if self.stdout:
+                line = recv_some(self.module, e = 0, stderr = 0)
+                self.stdout.write(line)
+            if self.stderr:
+                line = recv_some(self.module, e = 0, stderr = 1)
+                self.stderr.write(line)
+                if len(line) > 0:
+                    self.error = line
+
+        # get the last output
+        if self.stdout:
+            line = recv_some(self.module, e = 0, stderr = 0)
+            self.stdout.write(line)
+        if self.stderr:
+            line = recv_some(self.module, e = 0, stderr = 1)
+            self.stderr.write(line)
+            if len(line) > 0:
+                self.error = line
+            
+    def abort(self):
+        """!Abort running process, used by main thread to signal an abort"""
+        self._want_abort = True
+    
+def _formatMsg(text):
+    """!Format error messages for dialogs
+    """
+    message = ''
+    for line in text.splitlines():
+        if len(line) == 0:
+            continue
+        elif 'GRASS_INFO_MESSAGE' in line:
+            message += line.split(':', 1)[1].strip() + '\n'
+        elif 'GRASS_INFO_WARNING' in line:
+            message += line.split(':', 1)[1].strip() + '\n'
+        elif 'GRASS_INFO_ERROR' in line:
+            message += line.split(':', 1)[1].strip() + '\n'
+        elif 'GRASS_INFO_END' in line:
+            return message
+        else:
+            message += line.strip() + '\n'
+    
+    return message
+
+def RunCommand(prog, flags = "", overwrite = False, quiet = False, verbose = False,
+               parent = None, read = False, parse = None, stdin = None, getErrorMsg = False, **kwargs):
+    """!Run GRASS command
+
+    @param prog program to run
+    @param flags flags given as a string
+    @param overwrite, quiet, verbose flags
+    @param parent parent window for error messages
+    @param read fetch stdout
+    @param parse fn to parse stdout (e.g. grass.parse_key_val) or None
+    @param stdin stdin or None
+    @param getErrorMsg get error messages on failure
+    @param kwargs program parameters
+    
+    @return returncode (read == False and getErrorMsg == False)
+    @return returncode, messages (read == False and getErrorMsg == True)
+    @return stdout (read == True and getErrorMsg == False)
+    @return returncode, stdout, messages (read == True and getErrorMsg == True)
+    @return stdout, stderr
+    """
+    cmdString = ' '.join(grass.make_command(prog, flags, overwrite,
+                                            quiet, verbose, **kwargs))
+    
+    Debug.msg(1, "gcmd.RunCommand(): %s" % cmdString)
+    
+    kwargs['stderr'] = subprocess.PIPE
+    
+    if read:
+        kwargs['stdout'] = subprocess.PIPE
+    
+    if stdin:
+        kwargs['stdin'] = subprocess.PIPE
+
+    Debug.msg(2, "gcmd.RunCommand(): command started")
+    start = time.time()
+    
+    ps = grass.start_command(prog, flags, overwrite, quiet, verbose, **kwargs)
+    
+    if stdin:
+        ps.stdin.write(stdin)
+        ps.stdin.close()
+        ps.stdin = None
+    
+    Debug.msg(3, "gcmd.RunCommand(): decoding string")
+    stdout, stderr = map(DecodeString, ps.communicate())
+    
+    ret = ps.returncode
+    Debug.msg(1, "gcmd.RunCommand(): get return code %d (%.6f sec)" % \
+                  (ret, (time.time() - start)))
+    
+    Debug.msg(3, "gcmd.RunCommand(): print error")
+    if ret != 0 and parent:
+        Debug.msg(2, "gcmd.RunCommand(): error %s" % stderr)
+        if (stderr == None):
+            Debug.msg(2, "gcmd.RunCommand(): nothing to print ???")
+        else:
+            GError(parent = parent,
+                   message = stderr)
+    
+    Debug.msg(3, "gcmd.RunCommand(): print read error")
+    if not read:
+        if not getErrorMsg:
+            return ret
+        else:
+            return ret, _formatMsg(stderr)
+
+    if stdout:
+        Debug.msg(2, "gcmd.RunCommand(): return stdout\n'%s'" % stdout)
+    else:
+        Debug.msg(2, "gcmd.RunCommand(): return stdout = None")
+    
+    if parse:
+        stdout = parse(stdout)
+    
+    if not getErrorMsg:
+        return stdout
+    
+    Debug.msg(2, "gcmd.RunCommand(): return ret, stdout")
+    if read and getErrorMsg:
+        return ret, stdout, _formatMsg(stderr)
+    
+    Debug.msg(2, "gcmd.RunCommand(): return result")
+    return stdout, _formatMsg(stderr)

Copied: grass/trunk/gui/wxpython/core/globalvar.py (from rev 49282, grass/trunk/gui/wxpython/gui_modules/globalvar.py)
===================================================================
--- grass/trunk/gui/wxpython/core/globalvar.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/globalvar.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,177 @@
+"""!
+ at package core.globalvar
+
+ at brief Global variables used by wxGUI
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+import locale
+
+if not os.getenv("GISBASE"):
+    sys.exit("GRASS is not running. Exiting...")
+
+# path to python scripts
+ETCDIR = os.path.join(os.getenv("GISBASE"), "etc")
+ETCICONDIR = os.path.join(os.getenv("GISBASE"), "etc", "gui", "icons")
+ETCWXDIR = os.path.join(ETCDIR, "gui", "wxpython")
+ETCIMGDIR = os.path.join(ETCDIR, "gui", "images")
+
+sys.path.append(os.path.join(ETCDIR, "python"))
+import grass.script as grass
+
+def CheckWxVersion(version = [2, 8, 11, 0]):
+    """!Check wx version"""
+    ver = wx.version().split(' ')[0]
+    if map(int, ver.split('.')) < version:
+        return False
+    
+    return True
+
+def CheckForWx():
+    """!Try to import wx module and check its version"""
+    if 'wx' in sys.modules.keys():
+        return
+    
+    minVersion = [2, 8, 1, 1]
+    try:
+        try:
+            import wxversion
+        except ImportError, e:
+            raise ImportError(e)
+        # wxversion.select(str(minVersion[0]) + '.' + str(minVersion[1]))
+        wxversion.ensureMinimal(str(minVersion[0]) + '.' + str(minVersion[1]))
+        import wx
+        version = wx.version().split(' ')[0]
+        
+        if map(int, version.split('.')) < minVersion:
+            raise ValueError('Your wxPython version is %s.%s.%s.%s' % tuple(version.split('.')))
+
+    except ImportError, e:
+        print >> sys.stderr, 'ERROR: wxGUI requires wxPython. %s' % str(e)
+        sys.exit(1)
+    except (ValueError, wxversion.VersionError), e:
+        print >> sys.stderr, 'ERROR: wxGUI requires wxPython >= %d.%d.%d.%d. ' % tuple(minVersion) + \
+            '%s.' % (str(e))
+        sys.exit(1)
+    except locale.Error, e:
+        print >> sys.stderr, "Unable to set locale:", e
+        os.environ['LC_ALL'] = ''
+    
+if not os.getenv("GRASS_WXBUNDLED"):
+    CheckForWx()
+import wx
+import wx.lib.flatnotebook as FN
+
+"""
+Query layer (generated for example by selecting item in the Attribute Table Manager)
+Deleted automatically on re-render action
+"""
+# temporal query layer (removed on re-render action)
+QUERYLAYER = 'qlayer'
+
+"""!Style definition for FlatNotebook pages"""
+FNPageStyle = FN.FNB_VC8 | \
+    FN.FNB_BACKGROUND_GRADIENT | \
+    FN.FNB_NODRAG | \
+    FN.FNB_TABS_BORDER_SIMPLE 
+
+FNPageDStyle = FN.FNB_FANCY_TABS | \
+    FN.FNB_BOTTOM | \
+    FN.FNB_NO_NAV_BUTTONS | \
+    FN.FNB_NO_X_BUTTON
+
+FNPageColor = wx.Colour(125,200,175)
+
+"""!Dialog widget dimension"""
+DIALOG_SPIN_SIZE = (150, -1)
+DIALOG_COMBOBOX_SIZE = (300, -1)
+DIALOG_GSELECT_SIZE = (400, -1)
+DIALOG_TEXTCTRL_SIZE = (400, -1)
+DIALOG_LAYER_SIZE = (100, -1)
+DIALOG_COLOR_SIZE = (30, 30)
+
+MAP_WINDOW_SIZE = (725, 600)
+HIST_WINDOW_SIZE = (500, 350)
+GM_WINDOW_SIZE = (500, 600)
+
+
+"""!File name extension binaries/scripts"""
+if sys.platform == 'win32':
+    EXT_BIN = '.exe'
+    EXT_SCT = '.py'
+else:
+    EXT_BIN = ''
+    EXT_SCT = ''
+
+def GetGRASSCmds(bin = True, scripts = True, gui_scripts = True, addons = True):
+    """!Create list of available GRASS commands to use when parsing
+    string from the command line
+
+    @param bin True to include executable into list
+    @param scripts True to include scripts into list
+    @param gui_scripts True to include GUI scripts into list
+    """
+    gisbase = os.environ['GISBASE']
+    cmd = list()
+    
+    if bin:
+        for executable in os.listdir(os.path.join(gisbase, 'bin')):
+            ext = os.path.splitext(executable)[1]
+            if not EXT_BIN or \
+                    ext in (EXT_BIN, EXT_SCT):
+                cmd.append(executable)
+        
+        # add special call for setting vector colors
+        cmd.append('vcolors')
+    
+    if scripts:
+        cmd += os.listdir(os.path.join(gisbase, 'scripts'))
+    
+    if gui_scripts:
+        os.environ["PATH"] = os.getenv("PATH") + os.pathsep + os.path.join(gisbase, 'etc', 'gui', 'scripts')
+        cmd = cmd + os.listdir(os.path.join(gisbase, 'etc', 'gui', 'scripts'))
+    
+    if addons and os.getenv('GRASS_ADDON_PATH'):
+        path = os.getenv('GRASS_ADDON_PATH')
+        bpath = os.path.join(path, 'bin')
+        spath = os.path.join(path, 'scripts')
+        if os.path.exists(bpath) and os.path.isdir(bpath):
+            for executable in os.listdir(bpath):
+                ext = os.path.splitext(executable)[1]
+                if not EXT_BIN or \
+                        ext in (EXT_BIN, EXT_SCT):
+                    cmd.append(executable)
+        if os.path.exists(spath) and os.path.isdir(spath):
+            cmd += os.listdir(spath)
+    
+    if sys.platform == 'win32':
+        for idx in range(len(cmd)):
+            name, ext = os.path.splitext(cmd[idx])
+            if ext in (EXT_BIN, EXT_SCT):
+                cmd[idx] = name
+    
+    return cmd
+
+"""@brief Collected GRASS-relared binaries/scripts"""
+grassCmd = {}
+grassCmd['all']    = GetGRASSCmds()
+grassCmd['script'] = GetGRASSCmds(bin = False, gui_scripts = False)
+
+"""@Toolbar icon size"""
+toolbarSize = (24, 24)
+
+"""@Is g.mlist available?"""
+if 'g.mlist' in grassCmd['all']:
+    have_mlist = True
+else:
+    have_mlist = False
+
+"""@Check version of wxPython, use agwStyle for 2.8.11+"""
+hasAgw = CheckWxVersion()

Copied: grass/trunk/gui/wxpython/core/menudata.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/menudata.py)
===================================================================
--- grass/trunk/gui/wxpython/core/menudata.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/menudata.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,231 @@
+"""!
+ at package core.menudata
+
+ at brief Complex list for menu entries for wxGUI
+
+Classes:
+ - MenuData
+
+Usage:
+ at code
+python menudata.py [action] [manager|modeler]
+ at endcode
+
+where <i>action</i>:
+ - strings (default)
+ - tree
+ - commands
+ - dump
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Michael Barton (Arizona State University)
+ at author Yann Chemin <yann.chemin gmail.com>
+ at author Martin Landa <landa.martin gmail.com>
+ at author Glynn Clements
+"""
+
+import os
+import sys
+import pprint
+try:
+    import xml.etree.ElementTree   as etree
+except ImportError:
+    import elementtree.ElementTree as etree # Python <= 2.4
+
+from wx import ID_ANY
+
+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 = 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 ManagerData
+        data = ManagerData()
+    else:
+        from gmodeler.frame import ModelerData
+        data = ModelerData()
+    
+    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)

Copied: grass/trunk/gui/wxpython/core/render.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/render.py)
===================================================================
--- grass/trunk/gui/wxpython/core/render.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/render.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,1423 @@
+"""!
+ at package core.render
+
+ at brief Rendering map layers and overlays into map composition image.
+
+Classes:
+ - Layer
+ - MapLayer
+ - Overlay
+ - Map
+
+(C) 2006-2011 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+ at author Michael Barton
+ at author Jachym Cepicky
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+import glob
+import math
+import copy
+import tempfile
+import types
+
+import wx
+from wx.lib.newevent import NewEvent
+
+from grass.script import core as grass
+
+from core          import utils
+from core.gcmd     import GException, GError, RunCommand
+from core.debug    import Debug
+from core.settings import UserSettings
+
+wxUpdateProgressBar, EVT_UPDATE_PRGBAR = NewEvent()
+
+USE_GPNMCOMP = True
+
+class Layer(object):
+    """!Virtual class which stores information about layers (map layers and
+    overlays) of the map composition.
+    
+    - For map layer use MapLayer class.
+    - For overlays use Overlay class.
+    """
+    def __init__(self, type, cmd, name = None,
+                 active = True, hidden = False, opacity = 1.0):
+        """!Create new instance
+        
+        @todo pass cmd as tuple instead of list
+        
+        @param type layer type ('raster', 'vector', 'overlay', 'command', etc.)
+        @param cmd GRASS command to render layer,
+        given as list, e.g. ['d.rast', 'map=elevation at PERMANENT']
+        @param name layer name, e.g. 'elevation at PERMANENT' (for layer tree)
+        @param active layer is active, will be rendered only if True
+        @param hidden layer is hidden, won't be listed in Layer Manager if True
+        @param opacity layer opacity <0;1>
+        """
+        self.type  = type
+        self.name  = name
+        
+        if self.type == 'command':
+            self.cmd = list()
+            for c in cmd:
+                self.cmd.append(utils.CmdToTuple(c))
+        else:
+            self.cmd = utils.CmdToTuple(cmd)
+        
+        self.active  = active
+        self.hidden  = hidden
+        self.opacity = opacity
+        
+        self.force_render = True
+        
+        Debug.msg (3, "Layer.__init__(): type=%s, cmd='%s', name=%s, " \
+                       "active=%d, opacity=%d, hidden=%d" % \
+                       (self.type, self.GetCmd(string = True), self.name, self.active,
+                        self.opacity, self.hidden))
+        
+        # generated file for each layer
+        if USE_GPNMCOMP or self.type == 'overlay':
+            tmpfile = tempfile.mkstemp()[1]
+            self.maskfile = tmpfile + '.pgm'
+            if self.type == 'overlay':
+                self.mapfile  = tmpfile + '.png'
+            else:
+                self.mapfile  = tmpfile + '.ppm'
+            grass.try_remove(tmpfile)
+        else:
+            self.mapfile = self.maskfile = None
+        
+    def __del__(self):
+        Debug.msg (3, "Layer.__del__(): layer=%s, cmd='%s'" %
+                   (self.name, self.GetCmd(string = True)))
+        
+    def Render(self):
+        """!Render layer to image
+        
+        @return rendered image filename
+        @return None on error or if cmdfile is defined
+        """
+        if not self.cmd:
+            return None
+        
+        # ignore in 2D
+        if self.type == '3d-raster':
+            return None
+        
+        Debug.msg (3, "Layer.Render(): type=%s, name=%s" % \
+                       (self.type, self.name))
+        
+        # prepare command for each layer
+        layertypes = ('raster', 'rgb', 'his', 'shaded', 'rastarrow', 'rastnum',
+                      'vector','thememap','themechart',
+                      'grid', 'geodesic', 'rhumb', 'labels',
+                      'command', 'rastleg','maplegend',
+                      'overlay')
+        
+        if self.type not in layertypes:
+            raise GException(_("<%(name)s>: layer type <%(type)s> is not supported") % \
+                                 {'type' : self.type, 'name' : self.name})
+        
+        # start monitor
+	if self.mapfile:
+	    os.environ["GRASS_PNGFILE"] = self.mapfile
+        
+        # execute command
+        try:
+            if self.type == 'command':
+                read = False
+                for c in self.cmd:
+                    ret, msg = RunCommand(c[0],
+                                          getErrorMsg = True,
+                                          quiet = True,
+                                          **c[1])
+                    if ret != 0:
+                        break
+                    if not read:
+                        os.environ["GRASS_PNG_READ"] = "TRUE"
+                
+                os.environ["GRASS_PNG_READ"] = "FALSE"
+            else:
+                ret, msg = RunCommand(self.cmd[0],
+                                      getErrorMsg = True,
+                                      quiet = True,
+                                      **self.cmd[1])
+                
+            if msg:
+                sys.stderr.write(_("Command '%s' failed\n") % self.GetCmd(string = True))
+                sys.stderr.write(_("Details: %s\n") % msg)
+            if ret != 0:
+                raise GException()
+        
+        except GException:
+            # clean up after problems
+            for f in [self.mapfile, self.maskfile]:
+                if not f:
+                    continue
+                grass.try_remove(f)
+                f = None
+        
+        # stop monitor
+        if self.mapfile and "GRASS_PNGFILE" in os.environ:
+            del os.environ["GRASS_PNGFILE"]
+        
+        self.force_render = False
+        
+        return self.mapfile
+    
+    def GetCmd(self, string = False):
+        """!Get GRASS command as list of string.
+        
+        @param string get command as string if True otherwise as list
+        
+        @return command list/string
+        """
+        if string:
+            if self.type == 'command':
+                scmd = []
+                for c in self.cmd:
+                    scmd.append(utils.GetCmdString(c))
+                
+                return ';'.join(scmd)
+            else:
+                return utils.GetCmdString(self.cmd)
+        else:
+            return self.cmd
+
+    def GetType(self):
+        """!Get map layer type"""
+        return self.type
+    
+    def GetElement(self):
+        """!Get map element type"""
+        if self.type == 'raster':
+            return 'cell'
+        return self.type
+    
+    def GetOpacity(self, float = False):
+        """
+        Get layer opacity level
+        
+        @param float get opacity level in <0,1> otherwise <0,100>
+        
+        @return opacity level
+        """
+        if float:
+            return self.opacity
+        
+        return int (self.opacity * 100)
+
+    def GetName(self, fullyQualified = True):
+        """!Get map layer name
+
+        @param fullyQualified True to return fully qualified name as a
+        string 'name at mapset' otherwise directory { 'name', 'mapset' }
+        is returned
+
+        @return string / directory
+        """
+        if fullyQualified:
+            return self.name
+        else:
+            if '@' in self.name:
+                return { 'name' : self.name.split('@')[0],
+                         'mapset' : self.name.split('@')[1] }
+            else:
+                return { 'name' : self.name,
+                         'mapset' : '' }
+        
+    def IsActive(self):
+        """!Check if layer is activated for rendering"""
+        return self.active
+    
+    def SetType(self, type):
+        """!Set layer type"""
+        if type not in ('raster', '3d-raster', 'vector',
+                        'overlay', 'command',
+                        'shaded', 'rgb', 'his', 'rastarrow', 'rastnum','maplegend',
+                        'thememap', 'themechart', 'grid', 'labels',
+                        'geodesic','rhumb'):
+            raise GException(_("Unsupported map layer type '%s'") % type)
+        
+        self.type = type
+
+    def SetName(self, name):
+        """!Set layer name"""
+        self.name = name
+        
+    def SetActive(self, enable = True):
+        """!Active or deactive layer"""
+        self.active = bool(enable)
+
+    def SetHidden(self, enable = False):
+        """!Hide or show map layer in Layer Manager"""
+        self.hidden = bool(enable)
+
+    def SetOpacity(self, value):
+        """!Set opacity value"""
+        if value < 0:
+            value = 0.
+        elif value > 1:
+            value = 1.
+        
+        self.opacity = float(value)
+        
+    def SetCmd(self, cmd):
+        """!Set new command for layer"""
+        if self.type == 'command':
+            self.cmd = []
+            for c in cmd:
+                self.cmd.append(utils.CmdToTuple(c))
+        else:
+            self.cmd  = utils.CmdToTuple(cmd)
+        Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
+        
+        # for re-rendering
+        self.force_render = True
+        
+class MapLayer(Layer):
+    def __init__(self, type, cmd, name = None,
+                 active = True, hidden = False, opacity = 1.0): 
+        """!Represents map layer in the map canvas
+        
+        @param type layer type ('raster', 'vector', 'command', etc.)
+        @param cmd GRASS command to render layer,
+        given as list, e.g. ['d.rast', 'map=elevation at PERMANENT']
+        @param name layer name, e.g. 'elevation at PERMANENT' (for layer tree) or None
+        @param active layer is active, will be rendered only if True
+        @param hidden layer is hidden, won't be listed in Layer Manager if True
+        @param opacity layer opacity <0;1>
+        """
+        Layer.__init__(self, type, cmd, name,
+                       active, hidden, opacity)
+        
+    def GetMapset(self):
+        """!Get mapset of map layer
+        
+        @return mapset name
+        @return '' on error (no name given)
+        """
+        if not self.name:
+            return ''
+        
+        try:
+            return self.name.split('@')[1]
+        except IndexError:
+            return self.name
+        
+class Overlay(Layer):
+    def __init__(self, id, type, cmd,
+                 active = True, hidden = True, opacity = 1.0):
+        """!Represents overlay displayed in map canvas
+        
+        @param id overlay id (for PseudoDC)
+        @param type overlay type ('barscale', 'legend', etc.)
+        @param cmd GRASS command to render overlay,
+        given as list, e.g. ['d.legend', 'map=elevation at PERMANENT']
+        @param active layer is active, will be rendered only if True
+        @param hidden layer is hidden, won't be listed in Layer Manager if True
+        @param opacity layer opacity <0;1>
+        """
+        Layer.__init__(self, 'overlay', cmd, type,
+                       active, hidden, opacity)
+        
+        self.id = id
+        
+class Map(object):
+    def __init__(self, gisrc = None, cmdfile = None, mapfile = None, envfile = None, monitor = None):
+        """!Map composition (stack of map layers and overlays)
+
+        @param gisrc alternative gisrc (used eg. by georectifier)
+        @param cmdline full path to the cmd file (defined by d.mon)
+        @param mapfile full path to the map file (defined by d.mon)
+        @param envfile full path to the env file (defined by d.mon)
+        @param monitor name of monitor (defined by d.mon)
+        """
+        # region/extent settigns
+        self.wind      = dict() # WIND settings (wind file)
+        self.region    = dict() # region settings (g.region)
+        self.width     = 640    # map width
+        self.height    = 480    # map height
+        
+        # list of layers
+        self.layers    = list()  # stack of available GRASS layer
+        
+        self.overlays  = list()  # stack of available overlays
+        self.ovlookup  = dict()  # lookup dictionary for overlay items and overlays
+        
+        # environment settings
+        self.env   = dict()
+        # path to external gisrc
+        self.gisrc = gisrc
+        
+        self.cmdfile = cmdfile
+        self.envfile = envfile
+        self.monitor = monitor
+        
+        if mapfile:
+            self.mapfileCmd = mapfile
+            self.maskfileCmd = os.path.splitext(mapfile)[0] + '.pgm'
+        
+        # generated file for g.pnmcomp output for rendering the map
+        self.mapfile = grass.tempfile(create = False) + '.ppm'
+        
+        # setting some initial env. variables
+        self._initGisEnv() # g.gisenv
+        self.GetWindow()
+        # GRASS environment variable (for rendering)
+        env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
+               "GRASS_COMPRESSION"     : "0",
+               "GRASS_TRUECOLOR"       : "TRUE",
+               "GRASS_TRANSPARENT"     : "TRUE",
+               "GRASS_PNG_READ"        : "FALSE",
+               }
+        
+        self._writeEnvFile(env)
+        self._writeEnvFile({"GRASS_PNG_READ" : "TRUE"})
+        for k, v in env.iteritems():
+            os.environ[k] = v
+        
+        # projection info
+        self.projinfo = self._projInfo()
+        
+    def _runCommand(self, cmd, **kwargs):
+        """!Run command in environment defined by self.gisrc if
+        defined"""
+        # use external gisrc if defined
+        gisrc_orig = os.getenv("GISRC")
+        if self.gisrc:
+            os.environ["GISRC"] = self.gisrc
+        
+        ret = cmd(**kwargs)
+        
+        # back to original gisrc
+        if self.gisrc:
+            os.environ["GISRC"] = gisrc_orig
+        
+        return ret
+    
+    def _initGisEnv(self):
+        """!Stores GRASS variables (g.gisenv) to self.env variable
+        """
+        if not os.getenv("GISBASE"):
+            sys.exit(_("GISBASE not set. You must be in GRASS GIS to run this program."))
+        
+        self.env = self._runCommand(grass.gisenv)
+            
+    def GetProjInfo(self):
+        """!Get projection info"""
+        return self.projinfo
+    
+    def _projInfo(self):
+        """!Return region projection and map units information
+        """
+        projinfo = dict()
+        if not grass.find_program('g.proj', ['--help']):
+            sys.exit(_("GRASS module '%s' not found. Unable to start map "
+                       "display window.") % 'g.proj')
+        
+        ret = self._runCommand(RunCommand, prog = 'g.proj',
+                               read = True, flags = 'p')
+        
+        if not ret:
+            return projinfo
+        
+        for line in ret.splitlines():
+            if ':' in line:
+                key, val = map(lambda x: x.strip(), line.split(':'))
+                if key in ['units']:
+                    val = val.lower()
+                projinfo[key] = val
+            elif "XY location (unprojected)" in line:
+                projinfo['proj'] = 'xy'
+                projinfo['units'] = ''
+                break
+        
+        return projinfo
+    
+    def GetWindow(self):
+        """!Read WIND file and set up self.wind dictionary"""
+        # FIXME: duplicated region WIND == g.region (at least some values)
+        filename = os.path.join (self.env['GISDBASE'],
+                                 self.env['LOCATION_NAME'],
+                                 self.env['MAPSET'],
+                                 "WIND")
+        try:
+            windfile = open (filename, "r")
+        except IOError, e:
+            sys.exit(_("Error: Unable to open '%(file)s'. Reason: %(ret)s. wxGUI exited.\n") % \
+                         { 'file' : filename, 'ret' : e})
+        
+        for line in windfile.readlines():
+            line = line.strip()
+            key, value = line.split(":", 1)
+            self.wind[key.strip()] = value.strip()
+        
+        windfile.close()
+        
+        return self.wind
+        
+    def AdjustRegion(self):
+        """!Adjusts display resolution to match monitor size in
+        pixels. Maintains constant display resolution, not related to
+        computational region. Do NOT use the display resolution to set
+        computational resolution. Set computational resolution through
+        g.region.
+        """
+        mapwidth    = abs(self.region["e"] - self.region["w"])
+        mapheight   = abs(self.region['n'] - self.region['s'])
+        
+        self.region["nsres"] =  mapheight / self.height
+        self.region["ewres"] =  mapwidth  / self.width
+        self.region['rows']  = round(mapheight / self.region["nsres"])
+        self.region['cols']  = round(mapwidth / self.region["ewres"])
+        self.region['cells'] = self.region['rows'] * self.region['cols']
+        
+        Debug.msg (3, "Map.AdjustRegion(): %s" % self.region)
+        
+        return self.region
+
+    def AlignResolution(self):
+        """!Sets display extents to even multiple of current
+        resolution defined in WIND file from SW corner. This must be
+        done manually as using the -a flag can produce incorrect
+        extents.
+        """
+        # new values to use for saving to region file
+        new = {}
+        n = s = e = w = 0.0
+        nwres = ewres = 0.0
+        
+        # Get current values for region and display
+        reg = self.GetRegion()
+        nsres = reg['nsres']
+        ewres = reg['ewres']
+        
+        n = float(self.region['n'])
+        s = float(self.region['s'])
+        e = float(self.region['e'])
+        w = float(self.region['w'])
+        
+        # Calculate rows, columns, and extents
+        new['rows'] = math.fabs(round((n-s)/nsres))
+        new['cols'] = math.fabs(round((e-w)/ewres))
+        
+        # Calculate new extents
+        new['s'] = nsres * round(s / nsres)
+        new['w'] = ewres * round(w / ewres)
+        new['n'] = new['s'] + (new['rows'] * nsres)
+        new['e'] = new['w'] + (new['cols'] * ewres)
+        
+        return new
+
+    def AlignExtentFromDisplay(self):
+        """!Align region extent based on display size from center
+        point"""
+        # calculate new bounding box based on center of display
+        if self.region["ewres"] > self.region["nsres"]:
+            res = self.region["ewres"]
+        else:
+            res = self.region["nsres"]
+        
+        Debug.msg(3, "Map.AlignExtentFromDisplay(): width=%d, height=%d, res=%f, center=%f,%f" % \
+                      (self.width, self.height, res, self.region['center_easting'],
+                       self.region['center_northing']))
+            
+        ew = (self.width / 2) * res
+        ns = (self.height / 2) * res
+        
+        self.region['n'] = self.region['center_northing'] + ns
+        self.region['s'] = self.region['center_northing'] - ns
+        self.region['e'] = self.region['center_easting'] + ew
+        self.region['w'] = self.region['center_easting'] - ew
+        
+        # LL locations
+        if self.projinfo['proj'] == 'll':
+            self.region['n'] = min(self.region['n'], 90.0)
+            self.region['s'] = max(self.region['s'], -90.0)
+        
+    def _writeEnvFile(self, data):
+        """!Write display-related variable to the file (used for
+        standalone app)
+        """
+        if not self.envfile:
+            return
+        
+        try:
+            fd = open(self.envfile, "r")
+            for line in fd.readlines():
+                key, value = line.split('=')
+                if key not in data.keys():
+                    data[key] = value
+            fd.close()
+            
+            fd = open(self.envfile, "w")
+            for k, v in data.iteritems():
+                fd.write('%s=%s\n' % (k.strip(), str(v).strip()))
+        except IOError, e:
+            grass.warning(_("Unable to open file '%(file)s' for writting. Details: %(det)s") % \
+                              { 'cmd' : self.envfile, 'det' : e })
+            return
+        
+        fd.close()
+        
+    def ChangeMapSize(self, (width, height)):
+        """!Change size of rendered map.
+        
+        @param width,height map size
+        """
+        try:
+            self.width  = int(width)
+            self.height = int(height)
+        except:
+            self.width  = 640
+            self.height = 480
+
+        Debug.msg(2, "Map.ChangeMapSize(): width=%d, height=%d" % \
+                      (self.width, self.height))
+        self._writeEnvFile({'GRASS_WIDTH' : self.width,
+                            'GRASS_HEIGHT' : self.height})
+        
+    def GetRegion(self, rast = [], zoom = False, vect = [], regionName = None,
+                  n = None, s = None, e = None, w = None, default = False,
+                  update = False):
+        """!Get region settings (g.region -upgc)
+        
+        Optionally extent, raster or vector map layer can be given.
+        
+        @param rast list of raster maps
+        @param zoom zoom to raster map (ignore NULLs)
+        @param vect list of vector maps
+        @param regionName  named region or None
+        @param n,s,e,w force extent
+        @param default force default region settings
+        @param update if True update current display region settings
+        
+        @return region settings as directory, e.g. {
+        'n':'4928010', 's':'4913700', 'w':'589980',...}
+        
+        @see GetCurrentRegion()
+        """
+        region = {}
+        
+        tmpreg = os.getenv("GRASS_REGION")
+        if tmpreg:
+            del os.environ["GRASS_REGION"]
+        
+        # use external gisrc if defined
+        gisrc_orig = os.getenv("GISRC")
+        if self.gisrc:
+            os.environ["GISRC"] = self.gisrc
+        
+        # do not update & shell style output
+        cmd = {}
+        cmd['flags'] = 'ugpc'
+        
+        if default:
+            cmd['flags'] += 'd'
+        
+        if regionName:
+            cmd['region'] = regionName
+        
+        if n:
+            cmd['n'] = n
+        if s:
+            cmd['s'] = s
+        if e:
+            cmd['e'] = e
+        if w:
+            cmd['w'] = w
+        
+        if rast:
+            if zoom:
+                cmd['zoom'] = rast[0]
+            else:
+                cmd['rast'] = ','.join(rast)
+        
+        if vect:
+            cmd['vect'] = ','.join(vect)
+        
+        ret, reg, msg = RunCommand('g.region',
+                                   read = True,
+                                   getErrorMsg = True,
+                                   **cmd)
+        
+        if ret != 0:
+            if rast:
+                message = _("Unable to zoom to raster map <%s>.") % rast[0] + \
+                    "\n\n" + _("Details:") + " %s" % msg
+            elif vect:
+                message = _("Unable to zoom to vector map <%s>.") % vect[0] + \
+                    "\n\n" + _("Details:") + " %s" % msg
+            else:
+                message = _("Unable to get current geographic extent. "
+                            "Force quiting wxGUI. Please manually run g.region to "
+                            "fix the problem.")
+            GError(message)
+            return self.region
+        
+        for r in reg.splitlines():
+            key, val = r.split("=", 1)
+            try:
+                region[key] = float(val)
+            except ValueError:
+                region[key] = val
+        
+        # back to original gisrc
+        if self.gisrc:
+            os.environ["GISRC"] = gisrc_orig
+        
+        # restore region
+        if tmpreg:
+            os.environ["GRASS_REGION"] = tmpreg
+        
+        Debug.msg (3, "Map.GetRegion(): %s" % region)
+        
+        if update:
+            self.region = region
+        
+        return region
+
+    def GetCurrentRegion(self):
+        """!Get current display region settings
+        
+        @see GetRegion()
+        """
+        return self.region
+
+    def SetRegion(self, windres = False):
+        """!Render string for GRASS_REGION env. variable, so that the
+        images will be rendered from desired zoom level.
+        
+        @param windres uses resolution from WIND file rather than
+        display (for modules that require set resolution like
+        d.rast.num)
+
+        @return String usable for GRASS_REGION variable or None
+        """
+        grass_region = ""
+        
+        if windres:
+            compRegion = self.GetRegion()
+            region = copy.copy(self.region)
+            for key in ('nsres', 'ewres', 'cells'):
+                region[key] = compRegion[key]
+        else:
+            # adjust region settings to match monitor
+            region = self.AdjustRegion()
+        
+        # read values from wind file
+        try:
+            for key in self.wind.keys():
+                if key == 'north':
+                    grass_region += "north: %s; " % \
+                        (region['n'])
+                    continue
+                elif key == "south":
+                    grass_region += "south: %s; " % \
+                        (region['s'])
+                    continue
+                elif key == "east":
+                    grass_region += "east: %s; " % \
+                        (region['e'])
+                    continue
+                elif key == "west":
+                    grass_region += "west: %s; " % \
+                        (region['w'])
+                    continue
+                elif key == "e-w resol":
+                    grass_region += "e-w resol: %f; " % \
+                        (region['ewres'])
+                    continue
+                elif key == "n-s resol":
+                    grass_region += "n-s resol: %f; " % \
+                        (region['nsres'])
+                    continue
+                elif key == "cols":
+                    if windres:
+                        continue
+                    grass_region += 'cols: %d; ' % \
+                        region['cols']
+                    continue
+                elif key == "rows":
+                    if windres:
+                        continue
+                    grass_region += 'rows: %d; ' % \
+                        region['rows']
+                    continue
+                else:
+                    grass_region += key + ": "  + self.wind[key] + "; "
+            
+            Debug.msg (3, "Map.SetRegion(): %s" % grass_region)
+            
+            return grass_region
+        
+        except:
+            return None
+        
+    def GetListOfLayers(self, l_type = None, l_mapset = None, l_name = None,
+                        l_active = None, l_hidden = None):
+        """!Returns list of layers of selected properties or list of
+        all layers.
+
+        @param l_type layer type, e.g. raster/vector/wms/overlay (value or tuple of values)
+        @param l_mapset all layers from given mapset (only for maplayers)
+        @param l_name all layers with given name
+        @param l_active only layers with 'active' attribute set to True or False
+        @param l_hidden only layers with 'hidden' attribute set to True or False
+        
+        @return list of selected layers
+        """
+        selected = []
+        
+        if type(l_type) == types.StringType:
+            one_type = True
+        else:
+            one_type = False
+        
+        if one_type and l_type == 'overlay':
+            llist = self.overlays
+        else:
+            llist = self.layers
+        
+        # ["raster", "vector", "wms", ... ]
+        for layer in llist:
+            # specified type only
+            if l_type != None:
+                if one_type and layer.type != l_type:
+                    continue
+                elif not one_type and layer.type not in l_type:
+                    continue
+            
+            # mapset
+            if (l_mapset != None and l_type != 'overlay') and \
+                    layer.GetMapset() != l_mapset:
+                continue
+            
+            # name
+            if l_name != None and layer.name != l_name:
+                continue
+            
+            # hidden and active layers
+            if l_active != None and \
+                   l_hidden != None:
+                if layer.active == l_active and \
+                       layer.hidden == l_hidden:
+                    selected.append(layer)
+            
+            # active layers
+            elif l_active != None:
+                if layer.active == l_active:
+                    selected.append(layer)
+            
+            # hidden layers
+            elif l_hidden != None:
+                if layer.hidden == l_hidden:
+                    selected.append(layer)
+            
+            # all layers
+            else:
+                selected.append(layer)
+        
+        Debug.msg (3, "Map.GetListOfLayers(): numberof=%d" % len(selected))
+        
+        return selected
+
+    def _renderLayers(self, force = False, mapWindow = None, overlaysOnly = False):
+        maps = list()
+        masks = list()
+        opacities = list()
+        # render map layers
+        ilayer = 1
+        if overlaysOnly:
+            layers = self.overlays
+        else:
+            layers = self.layers + self.overlays
+        
+        for layer in layers:
+            # skip non-active map layers
+            if not layer or not layer.active:
+                continue
+            
+            # render
+            if force or layer.force_render:
+                if not layer.Render():
+                    continue
+            
+            if mapWindow:
+                # update progress bar
+                ### wx.SafeYield(mapWindow)
+                event = wxUpdateProgressBar(value = ilayer)
+                wx.PostEvent(mapWindow, event)
+            
+            # add image to compositing list
+            if layer.type != "overlay":
+                maps.append(layer.mapfile)
+                masks.append(layer.maskfile)
+                opacities.append(str(layer.opacity))
+            
+            Debug.msg(3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
+            ilayer += 1
+        
+        return maps, masks, opacities
+    
+    def GetLayersFromCmdFile(self):
+        """!Get list of map layers from cmdfile
+        """
+        if not self.cmdfile:
+            return
+        
+        nlayers = 0
+        try:
+            fd = open(self.cmdfile, 'r')
+            for line in fd.readlines():
+                cmd = utils.split(line.strip())
+                ltype = None
+                if cmd[0] == 'd.rast':
+                    ltype = 'raster'
+                elif cmd[0] == 'd.vect':
+                    ltype = 'vector'
+                
+                name = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
+                                                 layerType = ltype)[0]
+                
+                self.AddLayer(type = ltype, command = cmd, l_active = False, name = name)
+                nlayers += 1
+        except IOError, e:
+            grass.warning(_("Unable to read cmdfile '%(cmd)s'. Details: %(det)s") % \
+                              { 'cmd' : self.cmdfile, 'det' : e })
+            return
+        
+        fd.close()
+
+        Debug.msg(1, "Map.GetLayersFromCmdFile(): cmdfile=%s" % self.cmdfile)
+        Debug.msg(1, "                            nlayers=%d" % nlayers)
+                
+    def _parseCmdFile(self):
+        """!Parse cmd file for standalone application
+        """
+        nlayers = 0
+        try:
+            fd = open(self.cmdfile, 'r')
+            grass.try_remove(self.mapfile)
+            cmdLines = fd.readlines()
+            RunCommand('g.gisenv',
+                       set = 'MONITOR_%s_CMDFILE=' % self.monitor)
+
+            for cmd in cmdLines:
+                cmdStr = utils.split(cmd.strip())
+                cmd = utils.CmdToTuple(cmdStr)
+                RunCommand(cmd[0], **cmd[1])
+                nlayers += 1
+            
+            RunCommand('g.gisenv',
+                       set = 'MONITOR_%s_CMDFILE=%s' % (self.monitor, self.cmdfile))
+        except IOError, e:
+            grass.warning(_("Unable to read cmdfile '%(cmd)s'. Details: %(det)s") % \
+                              { 'cmd' : self.cmdfile, 'det' : e })
+            return
+        
+        fd.close()
+
+        Debug.msg(1, "Map.__parseCmdFile(): cmdfile=%s" % self.cmdfile)
+        Debug.msg(1, "                      nlayers=%d" % nlayers)
+        
+        return nlayers
+
+    def _renderCmdFile(self, force, windres):
+        if not force:
+            return ([self.mapfileCmd],
+                    [self.maskfileCmd],
+                    ['1.0'])
+        
+        region = os.environ["GRASS_REGION"] = self.SetRegion(windres)
+        self._writeEnvFile({'GRASS_REGION' : region})
+        currMon = grass.gisenv()['MONITOR']
+        if currMon != self.monitor:
+            RunCommand('g.gisenv',
+                       set = 'MONITOR=%s' % self.monitor)
+        
+        grass.try_remove(self.mapfileCmd) # GRASS_PNG_READ is TRUE
+        
+        nlayers = self._parseCmdFile()
+        if self.overlays:
+            RunCommand('g.gisenv',
+                       unset = 'MONITOR') # GRASS_RENDER_IMMEDIATE doesn't like monitors
+            driver = UserSettings.Get(group = 'display', key = 'driver', subkey = 'type')
+            if driver == 'png':
+                os.environ["GRASS_RENDER_IMMEDIATE"] = "png"
+            else:
+                os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo"
+            self._renderLayers(overlaysOnly = True)
+            del os.environ["GRASS_RENDER_IMMEDIATE"]
+            RunCommand('g.gisenv',
+                       set = 'MONITOR=%s' % currMon)
+        
+        if currMon != self.monitor:
+            RunCommand('g.gisenv',
+                       set = 'MONITOR=%s' % currMon)
+            
+        if nlayers > 0:
+            return ([self.mapfileCmd],
+                    [self.maskfileCmd],
+                    ['1.0'])
+        else:
+            return ([], [], [])
+    
+    def Render(self, force = False, mapWindow = None, windres = False):
+        """!Creates final image composite
+        
+        This function can conditionaly use high-level tools, which
+        should be avaliable in wxPython library
+        
+        @param force force rendering
+        @param reference for MapFrame instance (for progress bar)
+        @param windres use region resolution (True) otherwise display resolution
+        
+        @return name of file with rendered image or None
+        """
+        wx.BeginBusyCursor()
+        # use external gisrc if defined
+        gisrc_orig = os.getenv("GISRC")
+        if self.gisrc:
+            os.environ["GISRC"] = self.gisrc
+        
+        tmp_region = os.getenv("GRASS_REGION")
+        os.environ["GRASS_REGION"] = self.SetRegion(windres)
+        os.environ["GRASS_WIDTH"]  = str(self.width)
+        os.environ["GRASS_HEIGHT"] = str(self.height)
+        driver = UserSettings.Get(group = 'display', key = 'driver', subkey = 'type')
+        if driver == 'png':
+            os.environ["GRASS_RENDER_IMMEDIATE"] = "png"
+        else:
+            os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo"
+        
+        if self.cmdfile:
+            maps, masks, opacities = self._renderCmdFile(force, windres)
+        else:
+            maps, masks, opacities = self._renderLayers(force, mapWindow)
+        
+        # ugly hack for MSYS
+        if sys.platform != 'win32':
+            mapstr = ",".join(maps)
+            maskstr = ",".join(masks)
+            mapoutstr = self.mapfile
+        else:
+            mapstr = ""
+            for item in maps:
+                mapstr += item.replace('\\', '/')		
+            mapstr = mapstr.rstrip(',')
+            maskstr = ""
+            for item in masks:
+                maskstr += item.replace('\\', '/')
+            maskstr = maskstr.rstrip(',')
+            mapoutstr = self.mapfile.replace('\\', '/')
+            
+        # run g.pngcomp to get composite image
+        bgcolor = ':'.join(map(str, UserSettings.Get(group = 'display', key = 'bgcolor',
+                                                     subkey = 'color')))
+        
+        if maps:
+            ret, msg = RunCommand('g.pnmcomp',
+                                  getErrorMsg = True,
+                                  overwrite = True,
+                                  input = '%s' % ",".join(maps),
+                                  mask = '%s' % ",".join(masks),
+                                  opacity = '%s' % ",".join(opacities),
+                                  bgcolor = bgcolor,
+                                  width = self.width,
+                                  height = self.height,
+                                  output = self.mapfile)
+            
+            if ret != 0:
+                print >> sys.stderr, _("ERROR: Rendering failed. Details: %s") % msg
+                wx.EndBusyCursor()
+                return None
+        
+        Debug.msg (3, "Map.Render() force=%s file=%s" % (force, self.mapfile))
+        
+        # back to original region
+        if tmp_region:
+            os.environ["GRASS_REGION"] = tmp_region
+        else:
+            del os.environ["GRASS_REGION"]
+        
+        # back to original gisrc
+        if self.gisrc:
+            os.environ["GISRC"] = gisrc_orig
+        
+        wx.EndBusyCursor()
+        if not maps:
+            return None
+        
+        return self.mapfile
+
+    def AddLayer(self, type, command, name = None,
+                 l_active = True, l_hidden = False, l_opacity = 1.0, l_render = False,
+                 pos = -1):
+        """!Adds generic map layer to list of layers
+        
+        @param type layer type ('raster', 'vector', etc.)
+        @param command  GRASS command given as list
+        @param name layer name
+        @param l_active layer render only if True
+        @param l_hidden layer not displayed in layer tree if True
+        @param l_opacity opacity level range from 0(transparent) - 1(not transparent)
+        @param l_render render an image if True
+        @param pos position in layer list (-1 for append)
+        
+        @return new layer on success
+        @return None on failure
+        """
+        wx.BeginBusyCursor()
+        # l_opacity must be <0;1>
+        if l_opacity < 0:
+            l_opacity = 0
+        elif l_opacity > 1:
+            l_opacity = 1
+        layer = MapLayer(type = type, name = name, cmd = command,
+                         active = l_active, hidden = l_hidden, opacity = l_opacity)
+        
+        # add maplayer to the list of layers
+        if pos > -1:
+            self.layers.insert(pos, layer)
+        else:
+            self.layers.append(layer)
+        
+        Debug.msg (3, "Map.AddLayer(): layer=%s" % layer.name)
+        if l_render:
+            if not layer.Render():
+                raise GException(_("Unable to render map layer <%s>.") % name)
+        
+        wx.EndBusyCursor()
+        
+        return layer
+
+    def DeleteAllLayers(self, overlay = False):
+        """!Delete all layers 
+
+        @param overlay True to delete also overlayes
+        """
+        self.layers = []
+        if overlay:
+            self.overlays = []
+        
+    def DeleteLayer(self, layer, overlay = False):
+        """!Removes layer from list of layers
+        
+        @param layer layer instance in layer tree
+        @param overlay delete overlay (use self.DeleteOverlay() instead)
+
+        @return removed layer on success or None
+        """
+        Debug.msg (3, "Map.DeleteLayer(): name=%s" % layer.name)
+        
+        if overlay:
+            list = self.overlays
+        else:
+            list = self.layers
+        
+        if layer in list:
+            if layer.mapfile:
+                base = os.path.split(layer.mapfile)[0]
+                mapfile = os.path.split(layer.mapfile)[1]
+                tempbase = mapfile.split('.')[0]
+                if base == '' or tempbase == '':
+                    return None
+                basefile = os.path.join(base, tempbase) + r'.*'
+                for f in glob.glob(basefile):
+                    os.remove(f)
+            list.remove(layer)
+            
+            return layer
+        
+        return None
+
+    def ReorderLayers(self, layerList):
+        """!Reorder list to match layer tree
+        
+        @param layerList list of layers
+        """
+        self.layers = layerList
+        
+        layerNameList = ""
+        for layer in self.layers:
+            if layer.name:
+                layerNameList += layer.name + ','
+        Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
+                   (layerNameList))
+        
+    def ChangeLayer(self, layer, render = False, **kargs):
+        """!Change map layer properties
+
+        @param layer map layer instance
+        @param type layer type ('raster', 'vector', etc.)
+        @param command  GRASS command given as list
+        @param name layer name
+        @param active layer render only if True
+        @param hidden layer not displayed in layer tree if True
+        @param opacity opacity level range from 0(transparent) - 1(not transparent)
+        @param render render an image if True
+        """
+        Debug.msg (3, "Map.ChangeLayer(): layer=%s" % layer.name)
+        
+        if 'type' in kargs:
+            layer.SetType(kargs['type']) # check type
+        
+        if 'command' in kargs:
+            layer.SetCmd(kargs['command'])
+        
+        if 'name' in kargs:
+            layer.SetName(kargs['name'])
+        
+        if 'active' in kargs:
+            layer.SetActive(kargs['active'])
+        
+        if 'hidden' in kargs:
+            layer.SetHidden(kargs['hidden'])
+        
+        if 'opacity' in kargs:
+            layer.SetOpacity(kargs['opacity'])
+        
+        if render and not layer.Render():
+            raise GException(_("Unable to render map layer <%s>.") % 
+                             name)
+        
+        return layer
+
+    def ChangeOpacity(self, layer, l_opacity):
+        """!Changes opacity value of map layer
+
+        @param layer layer instance in layer tree
+        @param l_opacity opacity level <0;1>
+        """
+        # l_opacity must be <0;1>
+        if l_opacity < 0: l_opacity = 0
+        elif l_opacity > 1: l_opacity = 1
+        
+        layer.opacity = l_opacity
+        Debug.msg (3, "Map.ChangeOpacity(): layer=%s, opacity=%f" % \
+                   (layer.name, layer.opacity))
+
+    def ChangeLayerActive(self, layer, active):
+        """!Enable or disable map layer
+        
+        @param layer layer instance in layer tree
+        @param active to be rendered (True)
+        """
+        layer.active = active
+        
+        Debug.msg (3, "Map.ChangeLayerActive(): name='%s' -> active=%d" % \
+                   (layer.name, layer.active))
+
+    def ChangeLayerName (self, layer, name):
+        """!Change name of the layer
+        
+        @param layer layer instance in layer tree
+        @param name  layer name to set up
+        """
+        Debug.msg (3, "Map.ChangeLayerName(): from=%s to=%s" % \
+                   (layer.name, name))
+        layer.name =  name
+
+    def RemoveLayer(self, name = None, id = None):
+        """!Removes layer from layer list
+        
+        Layer is defined by name at mapset or id.
+        
+        @param name layer name (must be unique)
+        @param id layer index in layer list
+
+        @return removed layer on success
+        @return None on failure
+        """
+        # delete by name
+        if name:
+            retlayer = None
+            for layer in self.layers:
+                if layer.name == name:
+                    retlayer = layer
+                    os.remove(layer.mapfile)
+                    os.remove(layer.maskfile)
+                    self.layers.remove(layer)
+                    return layer
+        # del by id
+        elif id != None:
+            return self.layers.pop(id)
+        
+        return None
+
+    def GetLayerIndex(self, layer, overlay = False):
+        """!Get index of layer in layer list.
+        
+        @param layer layer instace in layer tree
+        @param overlay use list of overlays instead
+        
+        @return layer index
+        @return -1 if layer not found
+        """
+        if overlay:
+            list = self.overlay
+        else:
+            list = self.layers
+            
+        if layer in list:
+            return list.index(layer)
+        
+        return -1
+
+    def AddOverlay(self, id, type, command,
+                   l_active = True, l_hidden = True, l_opacity = 1.0, l_render = False):
+        """!Adds overlay (grid, barscale, legend, etc.) to list of
+        overlays
+        
+        @param id overlay id (PseudoDC)
+        @param type overlay type (barscale, legend)
+        @param command GRASS command to render overlay
+        @param l_active overlay activated (True) or disabled (False)
+        @param l_hidden overlay is not shown in layer tree (if True)
+        @param l_render render an image (if True)
+        
+        @return new layer on success
+        @retutn None on failure
+        """
+        Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
+        overlay = Overlay(id = id, type = type, cmd = command,
+                          active = l_active, hidden = l_hidden, opacity = l_opacity)
+        
+        # add maplayer to the list of layers
+        self.overlays.append(overlay)
+        
+        if l_render and command != '' and not overlay.Render():
+            raise GException(_("Unable render overlay <%s>.") % 
+                             name)
+        
+        return self.overlays[-1]
+
+    def ChangeOverlay(self, id, render = False, **kargs):
+        """!Change overlay properities
+        
+        Add new overlay if overlay with 'id' doesn't exist.
+        
+        @param id overlay id (PseudoDC)
+        @param type overlay type (barscale, legend)
+        @param command GRASS command to render overlay
+        @param l_active overlay activated (True) or disabled (False)
+        @param l_hidden overlay is not shown in layer tree (if True)
+        @param l_render render an image (if True)
+        
+        @return new layer on success
+        """
+        overlay = self.GetOverlay(id, list = False)
+        if  overlay is None:
+            overlay = Overlay(id, type = None, cmd = None)
+        
+        if 'type' in kargs:
+            overlay.SetName(kargs['type']) # type -> overlay
+        
+        if 'command' in kargs:
+            overlay.SetCmd(kargs['command'])
+        
+        if 'active' in kargs:
+            overlay.SetActive(kargs['active'])
+        
+        if 'hidden' in kargs:
+            overlay.SetHidden(kargs['hidden'])
+        
+        if 'opacity' in kargs:
+            overlay.SetOpacity(kargs['opacity'])
+        
+        if render and overlay.GetCmd() != [] and not overlay.Render():
+            raise GException(_("Unable render overlay <%s>") % 
+                             name)
+        
+        return overlay
+
+    def GetOverlay(self, id, list = False):
+        """!Return overlay(s) with 'id'
+        
+        @param id overlay id
+        @param list return list of overlays of True
+        otherwise suppose 'id' to be unique
+        
+        @return list of overlays (list=True)
+        @return overlay (list=False)
+        @retur None (list=False) if no overlay or more overlays found
+        """
+        ovl = []
+        for overlay in self.overlays:
+            if overlay.id == id:
+                ovl.append(overlay)
+                
+        if not list:
+            if len(ovl) != 1:
+                return None
+            else:
+                return ovl[0]
+        
+        return ovl
+
+    def DeleteOverlay(self, overlay):
+        """!Delete overlay
+        
+        @param overlay overlay layer
+        
+        @return removed overlay on success or None
+        """
+        return self.DeleteLayer(overlay, overlay = True)
+
+    def _clean(self, llist):
+        for layer in llist:
+            if layer.maskfile:
+                grass.try_remove(layer.maskfile)
+            if layer.mapfile:
+                grass.try_remove(layer.mapfile)
+            llist.remove(layer)
+        
+    def Clean(self):
+        """!Clean layer stack - go trough all layers and remove them
+        from layer list.
+
+        Removes also mapfile and maskfile.
+        """
+        self._clean(self.layers)
+        self._clean(self.overlays)
+        
+    def ReverseListOfLayers(self):
+        """!Reverse list of layers"""
+        return self.layers.reverse()
+
+    def RenderOverlays(self, force):
+        """!Render overlays only (for nviz)"""
+        for layer in self.overlays:
+            if force or layer.force_render:
+                layer.Render()
+                
+if __name__ == "__main__":
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+    
+    Map = Map()
+    Map.GetRegion(update = True)
+    
+    Map.AddLayer(type = "raster",
+                 name = "elevation",
+                 command = ["d.rast", "map=elevation at PERMANENT"],
+                 l_opacity = .7)
+    
+    Map.AddLayer(type = "vector",
+                 name = "roadsmajor",
+                 command = ["d.vect", "map=roadsmajor at PERMANENT", "color=red", "width=3", "type=line"])
+    
+    image = Map.Render(force = True)
+    
+    if image:
+        grass.call(["display", image])

Added: grass/trunk/gui/wxpython/core/settings.py
===================================================================
--- grass/trunk/gui/wxpython/core/settings.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/settings.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,1046 @@
+"""!
+ at package core.settings
+
+ at brief Default GUI settings
+
+List of classes:
+ - Settings
+
+Usage:
+ at code
+from core.settings import UserSettings
+ at endcode
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+import copy
+import types
+
+from core       import globalvar
+from core.gcmd  import GException, GError
+from core.utils import GetSettingsPath, PathJoin
+
+class Settings:
+    """!Generic class where to store settings"""
+    def __init__(self):
+        # settings file
+        self.filePath = os.path.join(GetSettingsPath(), 'wx')
+        
+        # key/value separator
+        self.sep = ';'
+        
+        try:
+            projFile = PathJoin(os.environ["GRASS_PROJSHARE"], 'epsg')
+        except KeyError:
+            projFile = ''
+        
+        #
+        # default settings
+        #
+        self.defaultSettings = {
+            #
+            # general
+            #
+            'general': {
+                # use default window layout (layer manager, displays, ...)
+                'defWindowPos' : {
+                    'enabled' : True,
+                    'dim' : '0,0,%d,%d,%d,0,%d,%d' % \
+                        (globalvar.GM_WINDOW_SIZE[0],
+                         globalvar.GM_WINDOW_SIZE[1],
+                         globalvar.GM_WINDOW_SIZE[0],
+                         globalvar.MAP_WINDOW_SIZE[0],
+                         globalvar.MAP_WINDOW_SIZE[1])
+                    },
+                # workspace
+                'workspace' : {
+                    'posDisplay' : {
+                        'enabled' : False
+                        },
+                    'posManager' : {
+                        'enabled' : False
+                        },
+                    },
+                },
+            'manager' : {
+                # show opacity level widget
+                'changeOpacityLevel' : {
+                    'enabled' : False
+                    }, 
+                # ask when removing layer from layer tree
+                'askOnRemoveLayer' : {
+                    'enabled' : True
+                    },
+                # ask when quiting wxGUI or closing display
+                'askOnQuit' : {
+                    'enabled' : True
+                    },
+                # hide tabs
+                'hideTabs' : {
+                    'search' : False,
+                    'pyshell' : False,
+                    },
+                'copySelectedTextToClipboard' : {
+                    'enabled' : False
+                    },
+                },
+            #
+            # appearance
+            #
+            'appearance': {
+                'outputfont' : {
+                    'type' : 'Courier New',
+                    'size': '10',
+                    },
+                # expand/collapse element list
+                'elementListExpand' : {
+                    'selection' : 0 
+                    },
+                'menustyle' : {
+                    'selection' : 1
+                    },
+                'gSelectPopupHeight' : {
+                    'value' : 200
+                    },
+                'iconTheme' : {
+                    'type' : 'grass'
+                    },
+                },
+            #
+            # display
+            #
+            'display': {
+                'font' : {
+                    'type' : '',
+                    'encoding': 'ISO-8859-1',
+                    },
+                'driver': {
+                    'type': 'cairo'
+                    },
+                'alignExtent' : {
+                    'enabled' : True
+                    },
+                'compResolution' : {
+                    'enabled' : False
+                    },
+                'autoRendering': {
+                    'enabled' : True
+                    },
+                'autoZooming' : {
+                    'enabled' : False
+                    },
+                'statusbarMode': {
+                    'selection' : 0
+                    },
+                'bgcolor': {
+                    'color' : (255, 255, 255, 255),
+                    },
+                },
+            #
+            # projection
+            #
+            'projection' : {
+                'statusbar' : {
+                    'proj4'    : '',
+                    'epsg'     : '',
+                    'projFile' : projFile,
+                    },
+                'format' : {
+                    'll'  : 'DMS',
+                    'precision' : 2,
+                    },
+                },
+            #
+            # Attribute Table Manager
+            #
+            'atm' : {
+                'highlight' : {
+                    'color' : (255, 255, 0, 255),
+                    'width' : 2
+                    },
+                'leftDbClick' : {
+                    'selection' : 1 # draw selected
+                    },
+                'askOnDeleteRec' : {
+                    'enabled' : True
+                    },
+                'keycolumn' : {
+                    'value' : 'cat'
+                    },
+                'encoding' : {
+                    'value' : '',
+                    }
+                },
+            #
+            # Command
+            #
+            'cmd': {
+                'overwrite' : {
+                    'enabled' : False
+                    },
+                'closeDlg' : {
+                    'enabled' : False
+                    },
+                'verbosity' : {
+                    'selection' : 'grassenv'
+                    },
+                # d.rast
+                'rasterOpaque' : {
+                    'enabled' : False
+                    },
+                'rasterColorTable' : {
+                    'enabled'   : False,
+                    'selection' : 'rainbow',
+                    },
+                # d.vect
+                'showType': {
+                    'point' : {
+                        'enabled' : True
+                        },
+                    'line' : {
+                        'enabled' : True
+                        },
+                    'centroid' : {
+                        'enabled' : True
+                        },
+                    'boundary' : {
+                        'enabled' : True
+                        },
+                    'area' : {
+                        'enabled' : True
+                        },
+                    'face' : {
+                        'enabled' : True
+                        },
+                    },
+                'addNewLayer' : {
+                    'enabled' : True,
+                    },
+                'interactiveInput' : {
+                    'enabled' : True,
+                    },
+                },
+            #
+            # vdigit
+            #
+            'vdigit' : {
+                # symbology
+                'symbol' : {
+                    'highlight' : {
+                        'enabled' : None,
+                        'color' : (255, 255, 0, 255)
+                        }, # yellow
+                    'highlightDupl' : {
+                        'enabled' : None,
+                        'color' : (255, 72, 0, 255)
+                        }, # red
+                    'point' : {
+                        'enabled' : True,
+                        'color' : (0, 0, 0, 255)
+                        }, # black
+                    'line' : {
+                        'enabled' : True,
+                        'color' : (0, 0, 0, 255)
+                        }, # black
+                    'boundaryNo' : {
+                        'enabled' : True,
+                        'color' : (126, 126, 126, 255)
+                        }, # grey
+                    'boundaryOne' : {
+                        'enabled' : True,
+                        'color' : (0, 255, 0, 255)
+                        }, # green
+                    'boundaryTwo' : {
+                        'enabled' : True,
+                        'color' : (255, 135, 0, 255)
+                        }, # orange
+                    'centroidIn' : {
+                        'enabled' : True,
+                        'color' : (0, 0, 255, 255)
+                        }, # blue
+                    'centroidOut' : {
+                        'enabled' : True,
+                        'color' : (165, 42, 42, 255)
+                        }, # brown
+                    'centroidDup' : {
+                        'enabled' : True,
+                        'color' : (156, 62, 206, 255)
+                        }, # violet
+                    'nodeOne' : {
+                        'enabled' : True,
+                        'color' : (255, 0, 0, 255)
+                        }, # red
+                    'nodeTwo' : {
+                        'enabled' : True,
+                        'color' : (0, 86, 45, 255)
+                        }, # dark green
+                    'vertex' : {
+                        'enabled' : False,
+                        'color' : (255, 20, 147, 255)
+                        }, # deep pink
+                    'area' : {
+                        'enabled' : False,
+                        'color' : (217, 255, 217, 255)
+                        }, # green
+                    'direction' : {
+                        'enabled' : False,
+                        'color' : (255, 0, 0, 255)
+                        }, # red
+                    },
+                # display
+                'lineWidth' : {
+                    'value' : 2,
+                    'units' : 'screen pixels'
+                    },
+                # snapping
+                'snapping' : {
+                    'value' : 10,
+                    'units' : 'screen pixels'
+                    },
+                'snapToVertex' : {
+                    'enabled' : False
+                    },
+                # digitize new record
+                'addRecord' : {
+                    'enabled' : True
+                    },
+                'layer' :{
+                    'value' : 1
+                    },
+                'category' : {
+                    'value' : 1
+                    },
+                'categoryMode' : {
+                    'selection' : 0
+                    },
+                # delete existing feature(s)
+                'delRecord' : {
+                    'enabled' : True
+                    },
+                # query tool
+                'query' : {
+                    'selection' : 0,
+                    'box' : True
+                    },
+                'queryLength' : {
+                    'than-selection' : 0,
+                    'thresh' : 0
+                    },
+                'queryDangle' : {
+                    'than-selection' : 0,
+                    'thresh' : 0
+                    },
+                # select feature (point, line, centroid, boundary)
+                'selectType': {
+                    'point' : {
+                        'enabled' : True
+                        },
+                    'line' : {
+                        'enabled' : True
+                        },
+                    'centroid' : {
+                        'enabled' : True
+                        },
+                    'boundary' : {
+                        'enabled' : True
+                        },
+                    },
+                'selectThresh' : {
+                    'value' : 10,
+                    'units' : 'screen pixels'
+                    },
+                'checkForDupl' : {
+                    'enabled' : False
+                    },
+                'selectInside' : {
+                    'enabled' : False
+                    },
+                # exit
+                'saveOnExit' : {
+                    'enabled' : False,
+                    },
+                # break lines on intersection
+                'breakLines' : {
+                    'enabled' : False,
+                    },
+                },
+             # 
+             # plots for profiles, histograms, and scatterplots
+             #
+            'profile': {
+                'raster' : {
+                    'pcolor'        : (0, 0, 255, 255), # line color
+                    'pwidth'        : 1, # line width
+                    'pstyle'        : 'solid', # line pen style
+                    'datatype'      : 'cell', # raster type
+                    },
+                'font' : {
+                    'titleSize' : 12,
+                    'axisSize' : 11,
+                    'legendSize' : 10,
+                    },
+                'marker' : {
+                    'color' : (0, 0, 0, 255),
+                    'fill' : 'transparent',
+                    'size' : 2,
+                    'type' : 'triangle',
+                    'legend' : _('Segment break'),
+                    },
+                'grid' : {
+                    'color' : (200, 200, 200, 255),
+                    'enabled' : True,
+                    },
+                'x-axis' : {
+                    'type' : 'auto', # axis format
+                    'min' : 0, # axis min for custom axis range
+                    'max': 0, # axis max for custom axis range
+                    'log' : False,
+                    },
+                'y-axis' : {
+                    'type' : 'auto', # axis format
+                    'min' : 0, # axis min for custom axis range
+                    'max': 0, # axis max for custom axis range
+                    'log' : False,
+                    },
+                'legend' : {
+                    'enabled' : True
+                    },
+                },
+             'histogram': {
+                'raster' : {
+                    'pcolor'        : (0, 0, 255, 255), # line color
+                    'pwidth'        : 1, # line width
+                    'pstyle'        : 'solid', # line pen style
+                    'datatype'      : 'cell', # raster type
+                    },
+                'font' : {
+                    'titleSize'     : 12,
+                    'axisSize'      : 11,
+                    'legendSize'    : 10,
+                    },
+                'grid' : {
+                    'color'         : (200, 200, 200, 255),
+                    'enabled'       : True,
+                    },
+                'x-axis' : {
+                    'type'          : 'auto', # axis format
+                    'min'           : 0, # axis min for custom axis range
+                    'max'           : 0, # axis max for custom axis range
+                    'log'           : False,
+                    },
+                'y-axis' : {
+                    'type'          : 'auto', # axis format
+                    'min'           : 0, # axis min for custom axis range
+                    'max'           : 0, # axis max for custom axis range
+                    'log'           : False,
+                    },
+                'legend' : {
+                    'enabled'       : True
+                    },
+                },
+             'scatter': {
+                'rasters' : {
+                    'pcolor' : (0, 0, 255, 255),
+                    'pfill' : 'solid',
+                    'psize' : 1,
+                    'ptype' : 'dot',
+                    'plegend' : _('Data point'),
+                    0 : {'datatype' : 'CELL'},
+                    1 : {'datatype' : 'CELL'},
+                    },
+                'font' : {
+                    'titleSize'     : 12,
+                    'axisSize'      : 11,
+                    'legendSize'    : 10,
+                    },
+                'grid' : {
+                    'color'         : (200, 200, 200, 255),
+                    'enabled'       : True,
+                    },
+                'x-axis' : {
+                    'type'          : 'auto', # axis format
+                    'min'           : 0, # axis min for custom axis range
+                    'max'           : 0, # axis max for custom axis range
+                    'log'           : False,
+                    },
+                'y-axis' : {
+                    'type'          : 'auto', # axis format
+                    'min'           : 0, # axis min for custom axis range
+                    'max'           : 0, # axis max for custom axis range
+                    'log'           : False,
+                    },
+                'legend' : {
+                    'enabled'       : True
+                    },
+                },
+            'gcpman' : {
+                'rms' : {
+                    'highestonly' : True,
+                    'sdfactor' : 1,
+                    },
+                'symbol' : {
+                    'color' : (0, 0, 255, 255),
+                    'hcolor' : (255, 0, 0, 255),
+                    'scolor' : (0, 255, 0, 255),
+                    'ucolor' : (255, 165, 0, 255),
+                    'unused' : True,
+                    'size' : 8,
+                    'width' : 2,
+                    },
+                },
+            'nviz' : {
+                'view' : {
+                    'persp' : {
+                        'value' : 20,
+                        'step' : 2,
+                        },
+                    'position' : {
+                        'x' : 0.84,
+                        'y' : 0.16,
+                        },
+                    'twist' : {
+                        'value' : 0,
+                        },
+                    'z-exag' : {
+                        'min' : 0,
+                        'max' : 10,
+                        'value': 1,
+                        },
+                    'background' : {
+                        'color' : (255, 255, 255, 255), # white
+                        },
+                    },
+                'fly' : {
+                    'exag' : {
+                        'move' : 5,
+                        'turn' : 5,
+                        }
+                    },
+                'animation' : {
+                    'fps' : 24,
+                    'prefix' : _("animation")
+                    },
+                'surface' : {
+                    'shine': {
+                        'map' : False,
+                        'value' : 60.0,
+                        },
+                    'color' : {
+                        'map' : True,
+                        'value' : (100, 100, 100, 255), # constant: grey
+                        },
+                    'draw' : {
+                        'wire-color' : (136, 136, 136, 255),
+                        'mode' : 1, # fine
+                        'style' : 1, # surface
+                        'shading' : 1, # gouraud
+                        'res-fine' : 6,
+                        'res-coarse' : 9,
+                        },
+                    'position' : {
+                        'x' : 0,
+                        'y' : 0,
+                        'z' : 0,
+                        },
+                    },
+                'constant' : {
+                    'color' : (100, 100, 100, 255),
+                    'value' : 0.0,
+                    'transp' : 0,
+                    'resolution': 6
+                },
+                'vector' : {
+                    'lines' : {
+                        'show' : False,
+                        'width' : 2,
+                        'color' : (0, 0, 255, 255), # blue
+                        'flat' : False,
+                        'height' : 0,
+                        'rgbcolumn': None,
+                        'sizecolumn': None,
+                        },
+                    'points' : {
+                        'show' : False,
+                        'size' : 100,
+                        'width' : 2,
+                        'marker' : 2,
+                        'color' : (0, 0, 255, 255), # blue
+                        'height' : 0,
+                        'rgbcolumn': None,
+                        'sizecolumn': None,
+                        }
+                    },
+                'volume' : {
+                    'color' : {
+                        'map' : True,
+                        'value' : (100, 100, 100, 255), # constant: grey
+                        },
+                    'draw' : {
+                        'mode'       : 0, # isosurfaces
+                        'shading'    : 1, # gouraud
+                        'resolution' : 3, # polygon resolution
+                        },
+                    'shine': {
+                        'map' : False,
+                        'value' : 60,
+                        },
+                    'topo': {
+                        'map' : None,
+                        'value' : 0.0
+                        },
+                    'transp': {
+                        'map' : None,
+                        'value': 0
+                        },
+                    'mask': {
+                        'map' : None,
+                        'value': ''
+                        },
+                    'slice_position': {
+                        'x1' : 0,
+                        'x2' : 1,
+                        'y1' : 0,
+                        'y2' : 1,
+                        'z1' : 0,
+                        'z2' : 1,
+                        'axis' : 0,
+                        }
+                    },
+                'cplane' : {
+                    'shading': 4,
+                    'rotation':{
+                        'rot': 0, 
+                        'tilt': 0
+                        }, 
+                    'position':{
+                        'x' : 0,
+                        'y' : 0,
+                        'z' : 0
+                    }   
+                },
+                'light' : {
+                    'position' : {
+                        'x' : 0.68,
+                        'y' : -0.68,
+                        'z' : 80,
+                        },
+                    'bright'  : 80,
+                    'color'   : (255, 255, 255, 255), # white
+                    'ambient' : 20,
+                    },
+                'fringe' : {
+                    'elev'   : 55,
+                    'color'  : (128, 128, 128, 255), # grey
+                    },
+                'arrow': {
+                    'color': (0, 0, 0),
+                    },
+                'scalebar': {
+                    'color': (0, 0, 0),
+                    }
+                },
+            'modeler' : {
+                'disabled': {
+                    'color': (211, 211, 211, 255), # light grey
+                    },
+                'action' : {
+                    'color' : {
+                        'valid'   :  (180, 234, 154, 255), # light green
+                        'invalid' :  (255, 255, 255, 255), # white
+                        'running' :  (255, 0, 0, 255),     # red
+                        },
+                    'size' : {
+                        'width'  : 100,
+                        'height' : 50,
+                        },
+                    'width': {
+                        'parameterized' : 2,
+                        'default'       : 1,
+                        },
+                    },
+                'data' : { 
+                    'color': {
+                        'raster'   : (215, 215, 248, 255), # light blue
+                        'raster3d' : (215, 248, 215, 255), # light green
+                        'vector'   : (248, 215, 215, 255), # light red
+                        },
+                    'size' : {
+                        'width' : 175,
+                        'height' : 50,
+                        },
+                    },
+                'loop' : {
+                    'color' : {
+                        'valid'   :  (234, 226, 154, 255), # light yellow
+                        },
+                    'size' : {
+                        'width' : 175,
+                        'height' : 40,
+                        },
+                    },
+                'if-else' : {
+                    'size' : {
+                        'width' : 150,
+                        'height' : 40,
+                        },
+                    },
+                },
+            }
+
+        # quick fix, http://trac.osgeo.org/grass/ticket/1233
+        # TODO
+        if sys.platform == 'darwin':
+            self.defaultSettings['general']['defWindowPos']['enabled'] = False
+        
+        #
+        # user settings
+        #
+        self.userSettings = copy.deepcopy(self.defaultSettings)
+        try:
+            self.ReadSettingsFile()
+        except GException, e:
+            print >> sys.stderr, e.value
+
+        #
+        # internal settings (based on user settings)
+        #
+        self.internalSettings = {}
+        for group in self.userSettings.keys():
+            self.internalSettings[group] = {}
+            for key in self.userSettings[group].keys():
+                self.internalSettings[group][key] = {}
+
+        # self.internalSettings['general']["mapsetPath"]['value'] = self.GetMapsetPath()
+        self.internalSettings['appearance']['elementListExpand']['choices'] = \
+            (_("Collapse all except PERMANENT and current"),
+             _("Collapse all except PERMANENT"),
+             _("Collapse all except current"),
+             _("Collapse all"),
+             _("Expand all"))
+        self.internalSettings['atm']['leftDbClick']['choices'] = (_('Edit selected record'),
+                                                                  _('Display selected'))
+        
+        self.internalSettings['cmd']['verbosity']['choices'] = ('grassenv',
+                                                                'verbose',
+                                                                'quiet')
+                                                                
+        self.internalSettings['appearance']['iconTheme']['choices'] = ('grass',)
+        self.internalSettings['appearance']['menustyle']['choices'] = \
+                   (_("Classic (labels only)"),
+                    _("Combined (labels and module names)"),
+                    _("Professional (module names only)"))
+        self.internalSettings['appearance']['gSelectPopupHeight']['min'] = 50
+        # there is also maxHeight given to TreeCtrlComboPopup.GetAdjustedSize
+        self.internalSettings['appearance']['gSelectPopupHeight']['max'] = 1000
+        
+        self.internalSettings['display']['driver']['choices'] = ['cairo', 'png']
+        self.internalSettings['display']['statusbarMode']['choices'] = None # set during MapFrame init
+
+        self.internalSettings['nviz']['view'] = {}
+        self.internalSettings['nviz']['view']['twist'] = {}
+        self.internalSettings['nviz']['view']['twist']['min'] = -180
+        self.internalSettings['nviz']['view']['twist']['max'] = 180
+        self.internalSettings['nviz']['view']['persp'] = {}
+        self.internalSettings['nviz']['view']['persp']['min'] = 1
+        self.internalSettings['nviz']['view']['persp']['max'] = 100
+        self.internalSettings['nviz']['view']['height'] = {}
+        self.internalSettings['nviz']['view']['height']['value'] = -1
+        self.internalSettings['nviz']['view']['z-exag'] = {}
+        self.internalSettings['nviz']['view']['z-exag']['original'] = 1
+        self.internalSettings['nviz']['view']['rotation'] = None
+        self.internalSettings['nviz']['view']['focus'] = {}
+        self.internalSettings['nviz']['view']['focus']['x'] = -1
+        self.internalSettings['nviz']['view']['focus']['y'] = -1
+        self.internalSettings['nviz']['view']['focus']['z'] = -1
+        self.internalSettings['nviz']['view']['dir'] = {}
+        self.internalSettings['nviz']['view']['dir']['x'] = -1
+        self.internalSettings['nviz']['view']['dir']['y'] = -1
+        self.internalSettings['nviz']['view']['dir']['z'] = -1
+        self.internalSettings['nviz']['view']['dir']['use'] = False
+        
+        for decor in ('arrow', 'scalebar'):
+            self.internalSettings['nviz'][decor] = {}
+            self.internalSettings['nviz'][decor]['position'] = {}
+            self.internalSettings['nviz'][decor]['position']['x'] = 0
+            self.internalSettings['nviz'][decor]['position']['y'] = 0
+            self.internalSettings['nviz'][decor]['size'] = 100
+        self.internalSettings['nviz']['vector'] = {}
+        self.internalSettings['nviz']['vector']['points'] = {}
+        self.internalSettings['nviz']['vector']['points']['marker'] = ("x",
+                                                                       _("box"),
+                                                                       _("sphere"),
+                                                                       _("cube"),
+                                                                       _("diamond"),
+                                                                       _("dtree"),
+                                                                       _("ctree"),
+                                                                       _("aster"),
+                                                                       _("gyro"),
+                                                                       _("histogram"))
+        self.internalSettings['vdigit']['bgmap'] = {}
+        self.internalSettings['vdigit']['bgmap']['value'] = ''
+        
+    def ReadSettingsFile(self, settings = None):
+        """!Reads settings file (mapset, location, gisdbase)"""
+        if settings is None:
+            settings = self.userSettings
+        
+        self._readFile(self.filePath, settings)
+        
+        # set environment variables
+        font = self.Get(group = 'display', key = 'font', subkey = 'type')
+        enc  = self.Get(group = 'display', key = 'font', subkey = 'encoding')
+        if font:
+            os.environ["GRASS_FONT"] = font
+        if enc:
+            os.environ["GRASS_ENCODING"] = enc
+        
+    def _readFile(self, filename, settings = None):
+        """!Read settings from file to dict
+
+        @param filename settings file path
+        @param settings dict where to store settings (None for self.userSettings)
+        """
+        if settings is None:
+            settings = self.userSettings
+        
+        if not os.path.exists(filename):
+            return
+        
+        try:
+            fd = open(filename, "r")
+        except IOError:
+            sys.stderr.write(_("Unable to read settings file <%s>\n") % filename)
+            return
+        
+        try:
+            line = ''
+            for line in fd.readlines():
+                line = line.rstrip('%s' % os.linesep)
+                group, key = line.split(self.sep)[0:2]
+                kv = line.split(self.sep)[2:]
+                subkeyMaster = None
+                if len(kv) % 2 != 0: # multiple (e.g. nviz)
+                    subkeyMaster = kv[0]
+                    del kv[0]
+                idx = 0
+                while idx < len(kv):
+                    if subkeyMaster:
+                        subkey = [subkeyMaster, kv[idx]]
+                    else:
+                        subkey = kv[idx]
+                    value = kv[idx+1]
+                    value = self._parseValue(value, read = True)
+                    self.Append(settings, group, key, subkey, value)
+                    idx += 2
+        except ValueError, e:
+            print >> sys.stderr, _("Error: Reading settings from file <%(file)s> failed.\n"
+                                   "\t\tDetails: %(detail)s\n"
+                                   "\t\tLine: '%(line)s'\n") % { 'file' : filename,
+                                                               'detail' : e,
+                                                               'line' : line }
+            fd.close()
+        
+        fd.close()
+        
+    def SaveToFile(self, settings = None):
+        """!Save settings to the file"""
+        if settings is None:
+            settings = self.userSettings
+        
+        dirPath = GetSettingsPath()
+        if not os.path.exists(dirPath):
+            try:
+                os.mkdir(dirPath)
+            except:
+                GError(_('Unable to create settings directory'))
+                return
+        
+        try:
+            file = open(self.filePath, "w")
+            for group in settings.keys():
+                for key in settings[group].keys():
+                    subkeys = settings[group][key].keys()
+                    file.write('%s%s%s%s' % (group, self.sep, key, self.sep))
+                    for idx in range(len(subkeys)):
+                        value = settings[group][key][subkeys[idx]]
+                        if type(value) == types.DictType:
+                            if idx > 0:
+                                file.write('%s%s%s%s%s' % (os.linesep, group, self.sep, key, self.sep))
+                            file.write('%s%s' % (subkeys[idx], self.sep))
+                            kvalues = settings[group][key][subkeys[idx]].keys()
+                            srange = range(len(kvalues))
+                            for sidx in srange:
+                                svalue = self._parseValue(settings[group][key][subkeys[idx]][kvalues[sidx]])
+                                file.write('%s%s%s' % (kvalues[sidx], self.sep,
+                                                       svalue))
+                                if sidx < len(kvalues) - 1:
+                                    file.write('%s' % self.sep)
+                        else:
+                            if idx > 0 and \
+                                    type(settings[group][key][subkeys[idx - 1]]) == types.DictType:
+                                file.write('%s%s%s%s%s' % (os.linesep, group, self.sep, key, self.sep))
+                            value = self._parseValue(settings[group][key][subkeys[idx]])
+                            file.write('%s%s%s' % (subkeys[idx], self.sep, value))
+                            if idx < len(subkeys) - 1 and \
+                                    type(settings[group][key][subkeys[idx + 1]]) != types.DictType:
+                                file.write('%s' % self.sep)
+                    file.write(os.linesep)
+        except IOError, e:
+            raise GException(e)
+        except StandardError, e:
+            raise GException(_('Writing settings to file <%(file)s> failed.'
+                               '\n\nDetails: %(detail)s') % { 'file' : self.filePath,
+                                                              'detail' : e })
+        
+        file.close()
+        
+    def _parseValue(self, value, read = False):
+        """!Parse value to be store in settings file"""
+        if read: # -> read settings (cast values)
+            if value == 'True':
+                value = True
+            elif value == 'False':
+                value = False
+            elif value == 'None':
+                value = None
+            elif ':' in value: # -> color
+                try:
+                    value = tuple(map(int, value.split(':')))
+                except ValueError: # -> string
+                    pass
+            else:
+                try:
+                    value = int(value)
+                except ValueError:
+                    try:
+                        value = float(value)
+                    except ValueError:
+                        pass
+        else: # -> write settings
+            if type(value) == type(()): # -> color
+                value = str(value[0]) + ':' +\
+                    str(value[1]) + ':' + \
+                    str(value[2])
+                
+        return value
+
+    def Get(self, group, key = None, subkey = None, internal = False):
+        """!Get value by key/subkey
+
+        Raise KeyError if key is not found
+        
+        @param group settings group
+        @param key (value, None)
+        @param subkey (value, list or None)
+        @param internal use internal settings instead
+
+        @return value
+        """
+        if internal is True:
+            settings = self.internalSettings
+        else:
+            settings = self.userSettings
+            
+        try:
+            if subkey is None:
+                if key is None:
+                    return settings[group]
+                else:
+                    return settings[group][key]
+            else:
+                if type(subkey) == type(tuple()) or \
+                        type(subkey) == type(list()):
+                    return settings[group][key][subkey[0]][subkey[1]]
+                else:
+                    return settings[group][key][subkey]  
+
+        except KeyError:
+            print >> sys.stderr, "Settings: unable to get value '%s:%s:%s'\n" % \
+                (group, key, subkey)
+        
+    def Set(self, group, value, key = None, subkey = None, internal = False):
+        """!Set value of key/subkey
+        
+        Raise KeyError if group/key is not found
+        
+        @param group settings group
+        @param key key (value, None)
+        @param subkey subkey (value, list or None)
+        @param value value
+        @param internal use internal settings instead
+        """
+        if internal is True:
+            settings = self.internalSettings
+        else:
+            settings = self.userSettings
+        
+        try:
+            if subkey is None:
+                if key is None:
+                    settings[group] = value
+                else:
+                    settings[group][key] = value
+            else:
+                if type(subkey) == type(tuple()) or \
+                        type(subkey) == type(list()):
+                    settings[group][key][subkey[0]][subkey[1]] = value
+                else:
+                    settings[group][key][subkey] = value
+        except KeyError:
+            raise GException("%s '%s:%s:%s'" % (_("Unable to set "), group, key, subkey))
+        
+    def Append(self, dict, group, key, subkey, value):
+        """!Set value of key/subkey
+
+        Create group/key/subkey if not exists
+        
+        @param dict settings dictionary to use
+        @param group settings group
+        @param key key
+        @param subkey subkey (value or list)
+        @param value value
+        """
+        if group not in dict:
+            dict[group] = {}
+
+        if key not in dict[group]:
+            dict[group][key] = {}
+
+        if type(subkey) == types.ListType:
+            # TODO: len(subkey) > 2
+            if subkey[0] not in dict[group][key]:
+                dict[group][key][subkey[0]] = {}
+            try:
+                dict[group][key][subkey[0]][subkey[1]] = value
+            except TypeError:
+                print >> sys.stderr, _("Unable to parse settings '%s'") % value + \
+                    ' (' + group + ':' + key + ':' + subkey[0] + ':' + subkey[1] + ')'
+        else:
+            try:
+                dict[group][key][subkey] = value
+            except TypeError:
+                print >> sys.stderr, _("Unable to parse settings '%s'") % value + \
+                    ' (' + group + ':' + key + ':' + subkey + ')'
+        
+    def GetDefaultSettings(self):
+        """!Get default user settings"""
+        return self.defaultSettings
+
+    def Reset(self, key = None):
+        """!Reset to default settings
+
+        @key key in settings dict (None for all keys)
+        """
+        if not key:
+            self.userSettings = copy.deepcopy(self.defaultSettings)
+        else:
+            self.userSettings[key] = copy.deepcopy(self.defaultSettings[key])
+        
+UserSettings = Settings()


Property changes on: grass/trunk/gui/wxpython/core/settings.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Copied: grass/trunk/gui/wxpython/core/units.py (from rev 49282, grass/trunk/gui/wxpython/gui_modules/units.py)
===================================================================
--- grass/trunk/gui/wxpython/core/units.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/units.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,116 @@
+"""!
+ at package core.units
+
+ at brief Units management
+
+ at todo Probably will be replaced by Python ctypes fns in the near
+future(?)
+
+Usage:
+ at code
+from core.units import Units
+ at endcode
+
+Classes:
+ - BaseUnits
+
+(C) 2009, 2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+class BaseUnits:
+    def __init__(self):
+        self._units = dict()
+        self._units['length'] = { 0 : { 'key' : 'mu', 'label' : _('map units') },
+                             1 : { 'key' : 'me', 'label' : _('meters') },
+                             2 : { 'key' : 'km', 'label' : _('kilometers') },
+                             3 : { 'key' : 'mi', 'label' : _('miles') },
+                             4 : { 'key' : 'ft', 'label' : _('feet') } }
+        
+        self._units['area']   = { 0 : { 'key' : 'mu', 'label' : _('sq map units') },
+                             1 : { 'key' : 'me', 'label' : _('sq meters') },
+                             2 : { 'key' : 'km', 'label' : _('sq kilometers') },
+                             3 : { 'key' : 'ar', 'label' : _('acres') },
+                             4 : { 'key' : 'ht', 'label' : _('hectares') } }
+
+    def GetUnitsList(self, type):
+        """!Get list of units (their labels)
+        
+        @param type units type ('length' or 'area')
+        
+        @return list of units labels
+        """
+        result = list()
+        try:
+            keys = self._units[type].keys()
+            keys.sort()
+            for idx in keys:
+                result.append(self._units[type][idx]['label'])
+        except KeyError:
+            pass
+        
+        return result
+
+    def GetUnitsKey(self, type, index):
+        """!Get units key based on index
+        
+        @param type units type ('length' or 'area')
+        @param index units index
+        """
+        return self._units[type][index]['key']
+
+    def GetUnitsIndex(self, type, key):
+        """!Get units index based on key
+        
+        @param type units type ('length' or 'area')
+        @param key units key, e.g. 'me' for meters
+
+        @return index
+        """
+        for k, u in self._units[type].iteritems():
+            if u['key'] == key:
+                return k
+        return 0
+
+Units = BaseUnits()
+
+def ConvertValue(value, type, units):
+    """!Convert value from map units to given units
+
+    Inspired by vector/v.to.db/units.c
+
+    @param value value to be converted
+    @param type units type ('length', 'area')
+    @param unit  destination units
+    """
+    # get map units
+    # TODO
+    
+    f = 1
+    if type == 'length':
+        if units == 'me':
+            f = 1.0
+        elif units == 'km':
+            f = 1.0e-3
+        elif units == 'mi':
+            f = 6.21371192237334e-4
+        elif units == 'ft':
+            f = 3.28083989501312
+    else: # -> area
+        if units == 'me':
+            f = 1.0
+        elif units == 'km':
+            f = 1.0e-6
+        elif units == 'mi':
+            f = 3.86102158542446e-7
+        elif units == 'ft':
+            f = 10.7639104167097
+        elif units == 'ar':
+            f = 2.47105381467165e-4
+        elif units == 'ht':
+            f = 1.0e-4
+
+    return f * value

Copied: grass/trunk/gui/wxpython/core/utils.py (from rev 49282, grass/trunk/gui/wxpython/gui_modules/utils.py)
===================================================================
--- grass/trunk/gui/wxpython/core/utils.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/utils.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,745 @@
+"""!
+ at package core.utils
+
+ at brief Misc utilities for wxGUI
+
+(C) 2007-2009, 2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+ at author Jachym Cepicky
+"""
+
+import os
+import sys
+import platform
+import string
+import glob
+import shlex
+import re
+
+from core.globalvar import ETCDIR
+sys.path.append(os.path.join(ETCDIR, "python"))
+
+from grass.script import core as grass
+from grass.script import task as gtask
+
+from core.gcmd  import RunCommand
+from core.debug import Debug
+
+def normalize_whitespace(text):
+    """!Remove redundant whitespace from a string"""
+    return string.join(string.split(text), ' ')
+
+def split(s):
+    """!Platform spefic shlex.split"""
+    if sys.version_info >= (2, 6):
+        return shlex.split(s, posix = (sys.platform != "win32"))
+    elif sys.platform == "win32":
+        return shlex.split(s.replace('\\', r'\\'))
+    else:
+        return shlex.split(s)
+
+def GetTempfile(pref=None):
+    """!Creates GRASS temporary file using defined prefix.
+
+    @todo Fix path on MS Windows/MSYS
+
+    @param pref prefer the given path
+
+    @return Path to file name (string) or None
+    """
+    from core import cmd as gcmd
+    
+    ret = RunCommand('g.tempfile',
+                     read = True,
+                     pid = os.getpid())
+
+    tempfile = ret.splitlines()[0].strip()
+
+    # FIXME
+    # ugly hack for MSYS (MS Windows)
+    if platform.system() == 'Windows':
+	tempfile = tempfile.replace("/", "\\")
+    try:
+        path, file = os.path.split(tempfile)
+        if pref:
+            return os.path.join(pref, file)
+	else:
+	    return tempfile
+    except:
+        return None
+
+def GetLayerNameFromCmd(dcmd, fullyQualified = False, param = None,
+                        layerType = None):
+    """!Get map name from GRASS command
+    
+    Parameter dcmd can be modified when first parameter is not
+    defined.
+    
+    @param dcmd GRASS command (given as list)
+    @param fullyQualified change map name to be fully qualified
+    @param param params directory
+    @param layerType check also layer type ('raster', 'vector', '3d-raster', ...)
+    
+    @return tuple (name, found)
+    """
+    mapname = ''
+    found   = True
+    
+    if len(dcmd) < 1:
+        return mapname, False
+    
+    if 'd.grid' == dcmd[0]:
+        mapname = 'grid'
+    elif 'd.geodesic' in dcmd[0]:
+        mapname = 'geodesic'
+    elif 'd.rhumbline' in dcmd[0]:
+        mapname = 'rhumb'
+    else:
+        params = list()
+        for idx in range(len(dcmd)):
+            try:
+                p, v = dcmd[idx].split('=', 1)
+            except ValueError:
+                continue
+            
+            if p == param:
+                params = [(idx, p, v)]
+                break
+            
+            if p in ('map', 'input', 'layer',
+                     'red', 'blue', 'green',
+                     'h_map', 's_map', 'i_map',
+                     'reliefmap', 'labels'):
+                params.append((idx, p, v))
+        
+        if len(params) < 1:
+            if len(dcmd) > 1 and '=' not in dcmd[1]:
+                task = gtask.parse_interface(dcmd[0])
+                p = task.get_options()['params'][0].get('name', '')
+                params.append((1, p, dcmd[1]))
+            else:
+                return mapname, False
+        
+        mapname = params[0][2]
+        mapset = ''
+        if fullyQualified and '@' not in mapname:
+            if layerType in ('raster', 'vector', '3d-raster', 'rgb', 'his'):
+                try:
+                    if layerType in ('raster', 'rgb', 'his'):
+                        findType = 'cell'
+                    else:
+                        findType = layerType
+                    mapset = grass.find_file(mapname, element = findType)['mapset']
+                except AttributeError, e: # not found
+                    return '', False
+                if not mapset:
+                    found = False
+            else:
+                mapset = grass.gisenv()['MAPSET']
+            
+            # update dcmd
+            for i, p, v in params:
+                if p == 'layer':
+                    continue
+                dcmd[i] = p + '=' + v
+                if mapset:
+                    dcmd[i] += '@' + mapset
+        
+        maps = list()
+        ogr = False
+        for i, p, v in params:
+            if v.lower().rfind('@ogr') > -1:
+                ogr = True
+            if p == 'layer' and not ogr:
+                continue
+            maps.append(dcmd[i].split('=', 1)[1])
+        
+        mapname = '\n'.join(maps)
+    
+    return mapname, found
+
+def GetValidLayerName(name):
+    """!Make layer name SQL compliant, based on G_str_to_sql()
+    
+    @todo: Better use directly GRASS Python SWIG...
+    """
+    retName = str(name).strip()
+    
+    # check if name is fully qualified
+    if '@' in retName:
+        retName, mapset = retName.split('@')
+    else:
+        mapset = None
+    
+    cIdx = 0
+    retNameList = list(retName)
+    for c in retNameList:
+        if not (c >= 'A' and c <= 'Z') and \
+               not (c >= 'a' and c <= 'z') and \
+               not (c >= '0' and c <= '9'):
+            retNameList[cIdx] = '_'
+        cIdx += 1
+    retName = ''.join(retNameList)
+    
+    if not (retName[0] >= 'A' and retName[0] <= 'Z') and \
+           not (retName[0] >= 'a' and retName[0] <= 'z'):
+        retName = 'x' + retName[1:]
+
+    if mapset:
+        retName = retName + '@' + mapset
+        
+    return retName
+
+def ListOfCatsToRange(cats):
+    """!Convert list of category number to range(s)
+
+    Used for example for d.vect cats=[range]
+
+    @param cats category list
+
+    @return category range string
+    @return '' on error
+    """
+
+    catstr = ''
+
+    try:
+        cats = map(int, cats)
+    except:
+        return catstr
+
+    i = 0
+    while i < len(cats):
+        next = 0
+        j = i + 1
+        while j < len(cats):
+            if cats[i + next] == cats[j] - 1:
+                next += 1
+            else:
+                break
+            j += 1
+
+        if next > 1:
+            catstr += '%d-%d,' % (cats[i], cats[i + next])
+            i += next + 1
+        else:
+            catstr += '%d,' % (cats[i])
+            i += 1
+        
+    return catstr.strip(',')
+
+def ListOfMapsets(get = 'ordered'):
+    """!Get list of available/accessible mapsets
+
+    @param get method ('all', 'accessible', 'ordered')
+    
+    @return list of mapsets
+    @return None on error
+    """
+    mapsets = []
+    
+    if get == 'all' or get == 'ordered':
+        ret = RunCommand('g.mapsets',
+                         read = True,
+                         quiet = True,
+                         flags = 'l',
+                         fs = 'newline')
+        
+        if ret:
+            mapsets = ret.splitlines()
+            ListSortLower(mapsets)
+        else:
+            return None
+        
+    if get == 'accessible' or get == 'ordered':
+        ret = RunCommand('g.mapsets',
+                         read = True,
+                         quiet = True,
+                         flags = 'p',
+                         fs = 'newline')
+        if ret:
+            if get == 'accessible':
+                mapsets = ret.splitlines()
+            else:
+                mapsets_accessible = ret.splitlines()
+                for mapset in mapsets_accessible:
+                    mapsets.remove(mapset)
+                mapsets = mapsets_accessible + mapsets
+        else:
+            return None
+    
+    return mapsets
+
+def ListSortLower(list):
+    """!Sort list items (not case-sensitive)"""
+    list.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
+
+def GetVectorNumberOfLayers(parent, vector):
+    """!Get list of vector layers"""
+    layers = list()
+    if not vector:
+        return layers
+    
+    fullname = grass.find_file(name = vector, element = 'vector')['fullname']
+    if not fullname:
+        Debug.msg(5, "utils.GetVectorNumberOfLayers(): vector map '%s' not found" % vector)
+        return layers
+    
+    ret, out, msg = RunCommand('v.db.connect',
+                               getErrorMsg = True,
+                               read = True,
+                               flags = 'g',
+                               map = fullname,
+                               fs = ';')
+    if ret != 0:
+        sys.stderr.write(_("Vector map <%(map)s>: %(msg)s\n") % { 'map' : fullname, 'msg' : msg })
+        return layers
+    else:
+        Debug.msg(1, "GetVectorNumberOfLayers(): ret %s" % ret)
+    
+    for line in ret.splitlines():
+        try:
+            layer = line.split(';')[0]
+            if '/' in layer:
+                layer = layer.split('/')[0]
+            layers.append(layer)
+        except IndexError:
+            pass
+    
+    Debug.msg(3, "utils.GetVectorNumberOfLayers(): vector=%s -> %s" % \
+                  (fullname, ','.join(layers)))
+    
+    return layers
+
+def Deg2DMS(lon, lat, string = True, hemisphere = True, precision = 3):
+    """!Convert deg value to dms string
+
+    @param lon longitude (x)
+    @param lat latitude (y)
+    @param string True to return string otherwise tuple
+    @param hemisphere print hemisphere
+    @param precision seconds precision
+    
+    @return DMS string or tuple of values
+    @return empty string on error
+    """
+    try:
+        flat = float(lat)
+        flon = float(lon)
+    except ValueError:
+        if string:
+            return ''
+        else:
+            return None
+
+    # fix longitude
+    while flon > 180.0:
+        flon -= 360.0
+    while flon < -180.0:
+        flon += 360.0
+
+    # hemisphere
+    if hemisphere:
+        if flat < 0.0:
+            flat = abs(flat)
+            hlat = 'S'
+        else:
+            hlat = 'N'
+
+        if flon < 0.0:
+            hlon = 'W'
+            flon = abs(flon)
+        else:
+            hlon = 'E'
+    else:
+        flat = abs(flat)
+        flon = abs(flon)
+        hlon = ''
+        hlat = ''
+    
+    slat = __ll_parts(flat, precision = precision)
+    slon = __ll_parts(flon, precision = precision)
+
+    if string:
+        return slon + hlon + '; ' + slat + hlat
+    
+    return (slon + hlon, slat + hlat)
+
+def DMS2Deg(lon, lat):
+    """!Convert dms value to deg
+
+    @param lon longitude (x)
+    @param lat latitude (y)
+    
+    @return tuple of converted values
+    @return ValueError on error
+    """
+    x = __ll_parts(lon, reverse = True)
+    y = __ll_parts(lat, reverse = True)
+    
+    return (x, y)
+
+def __ll_parts(value, reverse = False, precision = 3):
+    """!Converts deg to d:m:s string
+
+    @param value value to be converted
+    @param reverse True to convert from d:m:s to deg
+    @param precision seconds precision (ignored if reverse is True)
+    
+    @return converted value (string/float)
+    @return ValueError on error (reverse == True)
+    """
+    if not reverse:
+        if value == 0.0:
+            return '%s%.*f' % ('00:00:0', precision, 0.0)
+    
+        d = int(int(value))
+        m = int((value - d) * 60)
+        s = ((value - d) * 60 - m) * 60
+        if m < 0:
+            m = '00'
+        elif m < 10:
+            m = '0' + str(m)
+        else:
+            m = str(m)
+        if s < 0:
+            s = '00.0000'
+        elif s < 10.0:
+            s = '0%.*f' % (precision, s)
+        else:
+            s = '%.*f' % (precision, s)
+        
+        return str(d) + ':' + m + ':' + s
+    else: # -> reverse
+        try:
+            d, m, s = value.split(':')
+            hs = s[-1]
+            s = s[:-1]
+        except ValueError:
+            try:
+                d, m = value.split(':')
+                hs = m[-1]
+                m = m[:-1]
+                s = '0.0'
+            except ValueError:
+                try:
+                    d = value
+                    hs = d[-1]
+                    d = d[:-1]
+                    m = '0'
+                    s = '0.0'
+                except ValueError:
+                    raise ValueError
+        
+        if hs not in ('N', 'S', 'E', 'W'):
+            raise ValueError
+        
+        coef = 1.0
+        if hs in ('S', 'W'):
+            coef = -1.0
+        
+        fm = int(m) / 60.0
+        fs = float(s) / (60 * 60)
+        
+        return coef * (float(d) + fm + fs)
+    
+def GetCmdString(cmd):
+    """
+    Get GRASS command as string.
+    
+    @param cmd GRASS command given as dictionary
+    
+    @return command string
+    """
+    scmd = ''
+    if not cmd:
+        return scmd
+    
+    scmd = cmd[0]
+    
+    if 'flags' in cmd[1]:
+        for flag in cmd[1]['flags']:
+            scmd += ' -' + flag
+    for flag in ('verbose', 'quiet', 'overwrite'):
+        if flag in cmd[1] and cmd[1][flag] is True:
+            scmd += ' --' + flag
+    
+    for k, v in cmd[1].iteritems():
+        if k in ('flags', 'verbose', 'quiet', 'overwrite'):
+            continue
+        scmd += ' %s=%s' % (k, v)
+            
+    return scmd
+
+def CmdToTuple(cmd):
+    """!Convert command list to tuple for gcmd.RunCommand()"""
+    if len(cmd) < 1:
+        return None
+        
+    dcmd = {}
+    for item in cmd[1:]:
+        if '=' in item: # params
+            key, value = item.split('=', 1)
+            dcmd[str(key)] = str(value).replace('"', '')
+        elif item[:2] == '--': # long flags
+            flag = item[2:]
+            if flag in ('verbose', 'quiet', 'overwrite'):
+                dcmd[str(flag)] = True
+        else: # -> flags
+            if 'flags' not in dcmd:
+                dcmd['flags'] = ''
+            dcmd['flags'] += item.replace('-', '')
+                
+    return (cmd[0],
+            dcmd)
+
+def PathJoin(*args):
+    """!Check path created by os.path.join"""
+    path = os.path.join(*args)
+    if platform.system() == 'Windows' and \
+            '/' in path:
+        return path[1].upper() + ':\\' + path[3:].replace('/', '\\')
+    
+    return path
+
+def ReadEpsgCodes(path):
+    """!Read EPSG code from the file
+
+    @param path full path to the file with EPSG codes
+
+    @return dictionary of EPSG code
+    @return string on error
+    """
+    epsgCodeDict = dict()
+    try:
+        try:
+            f = open(path, "r")
+        except IOError:
+            return _("failed to open '%s'" % path)
+        
+        i = 0
+        code = None
+        for line in f.readlines():
+            line = line.strip()
+            if len(line) < 1:
+                continue
+                
+            if line[0] == '#':
+                descr = line[1:].strip()
+            elif line[0] == '<':
+                code, params = line.split(" ", 1)
+                try:
+                    code = int(code.replace('<', '').replace('>', ''))
+                except ValueError:
+                    return e
+            
+            if code is not None:
+                epsgCodeDict[code] = (descr, params)
+                code = None
+            i += 1
+        
+        f.close()
+    except StandardError, e:
+        return e
+    
+    return epsgCodeDict
+
+def ReprojectCoordinates(coord, projOut, projIn = None, flags = ''):
+    """!Reproject coordinates
+
+    @param coord coordinates given as tuple
+    @param projOut output projection
+    @param projIn input projection (use location projection settings)
+
+    @return reprojected coordinates (returned as tuple)
+    """
+    coors = RunCommand('m.proj',
+                       flags = flags,
+                       input = '-',
+                       proj_input = projIn,
+                       proj_output = projOut,
+                       fs = ';',
+                       stdin = '%f;%f' % (coord[0], coord[1]),
+                       read = True)
+    if coors:
+        coors = coors.split(';')
+        e = coors[0]
+        n = coors[1]
+        try:
+            proj = projOut.split(' ')[0].split('=')[1]
+        except IndexError:
+            proj = ''
+        if proj in ('ll', 'latlong', 'longlat') and 'd' not in flags:
+            return (proj, (e, n))
+        else:
+            try:
+                return (proj, (float(e), float(n)))
+            except ValueError:
+                return (None, None)
+    
+    return (None, None)
+
+def GetListOfLocations(dbase):
+    """!Get list of GRASS locations in given dbase
+
+    @param dbase GRASS database path
+
+    @return list of locations (sorted)
+    """
+    listOfLocations = list()
+
+    try:
+        for location in glob.glob(os.path.join(dbase, "*")):
+            try:
+                if os.path.join(location, "PERMANENT") in glob.glob(os.path.join(location, "*")):
+                    listOfLocations.append(os.path.basename(location))
+            except:
+                pass
+    except UnicodeEncodeError, e:
+        raise e
+    
+    ListSortLower(listOfLocations)
+    
+    return listOfLocations
+
+def GetListOfMapsets(dbase, location, selectable = False):
+    """!Get list of mapsets in given GRASS location
+
+    @param dbase      GRASS database path
+    @param location   GRASS location
+    @param selectable True to get list of selectable mapsets, otherwise all
+
+    @return list of mapsets - sorted (PERMANENT first)
+    """
+    listOfMapsets = list()
+    
+    if selectable:
+        ret = RunCommand('g.mapset',
+                         read = True,
+                         flags = 'l',
+                         location = location,
+                         gisdbase = dbase)
+        
+        if not ret:
+            return listOfMapsets
+        
+        for line in ret.rstrip().splitlines():
+            listOfMapsets += line.split(' ')
+    else:
+        for mapset in glob.glob(os.path.join(dbase, location, "*")):
+            if os.path.isdir(mapset) and \
+                    os.path.isfile(os.path.join(dbase, location, mapset, "WIND")):
+                listOfMapsets.append(os.path.basename(mapset))
+    
+    ListSortLower(listOfMapsets)
+    return listOfMapsets
+
+def GetColorTables():
+    """!Get list of color tables"""
+    ret = RunCommand('r.colors',
+                     read = True,
+                     flags = 'l')
+    if not ret:
+        return list()
+    
+    return ret.splitlines()
+
+def _getGDALFormats():
+    """!Get dictionary of avaialble GDAL drivers"""
+    ret = grass.read_command('r.in.gdal',
+                             quiet = True,
+                             flags = 'f')
+    
+    return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
+
+def _getOGRFormats():
+    """!Get dictionary of avaialble OGR drivers"""
+    ret = grass.read_command('v.in.ogr',
+                             quiet = True,
+                             flags = 'f')
+    
+    return _parseFormats(ret), _parseFormats(ret, writableOnly = True)
+
+def _parseFormats(output, writableOnly = False):
+    """!Parse r.in.gdal/v.in.ogr -f output"""
+    formats = { 'file'     : list(),
+                'database' : list(),
+                'protocol' : list()
+                }
+    
+    if not output:
+        return formats
+    
+    patt = None
+    if writableOnly:
+        patt = re.compile('\(rw\+?\)$', re.IGNORECASE)
+    
+    for line in output.splitlines():
+        key, name = map(lambda x: x.strip(), line.strip().rsplit(':', -1))
+        
+        if writableOnly and not patt.search(key):
+            continue
+        
+        if name in ('Memory', 'Virtual Raster', 'In Memory Raster'):
+            continue
+        if name in ('PostgreSQL', 'SQLite',
+                    'ODBC', 'ESRI Personal GeoDatabase',
+                    'Rasterlite',
+                    'PostGIS WKT Raster driver'):
+            formats['database'].append(name)
+        elif name in ('GeoJSON',
+                      'OGC Web Coverage Service',
+                      'OGC Web Map Service',
+                      'HTTP Fetching Wrapper'):
+            formats['protocol'].append(name)
+        else:
+            formats['file'].append(name)
+    
+    for items in formats.itervalues():
+        items.sort()
+    
+    return formats
+
+formats = None
+
+def GetFormats(writableOnly = False):
+    """!Get GDAL/OGR formats"""
+    global formats
+    if not formats:
+        gdalAll, gdalWritable = _getGDALFormats()
+        ogrAll,  ogrWritable  = _getOGRFormats()
+        formats = {
+            'all' : {
+                'gdal' : gdalAll,
+                'ogr'  : ogrAll,
+                },
+            'writable' : {
+                'gdal' : gdalWritable,
+                'ogr'  : ogrWritable,
+                },
+            }
+    
+    if writableOnly:
+        return formats['writable']
+    
+    return formats['all']
+
+def GetSettingsPath():
+    """!Get full path to the settings directory
+    """
+    try:
+        verFd = open(os.path.join(ETCDIR, "VERSIONNUMBER"))
+        version = int(verFd.readlines()[0].split(' ')[0].split('.')[0])
+    except (IOError, ValueError, TypeError, IndexError), e:
+        sys.exit(_("ERROR: Unable to determine GRASS version. Details: %s") % e)
+    
+    verFd.close()
+
+    # keep location of settings files rc and wx in sync with lib/init/grass.py
+    if sys.platform == 'win32':
+        return os.path.join(os.getenv('APPDATA'), 'grass%d' % version)
+    
+    return os.path.join(os.getenv('HOME'), '.grass%d' % version)

Copied: grass/trunk/gui/wxpython/core/workspace.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/workspace.py)
===================================================================
--- grass/trunk/gui/wxpython/core/workspace.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/workspace.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,1296 @@
+"""!
+ at package core.workspace
+
+ at brief Open/save workspace definition file
+
+Classes:
+ - ProcessWorkspaceFile
+ - WriteWorkspaceFile
+ - ProcessGrcFile
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+
+import wx
+
+from core.utils    import normalize_whitespace
+from core.settings import UserSettings
+
+class ProcessWorkspaceFile:
+    def __init__(self, tree):
+        """!A ElementTree handler for the GXW XML file, as defined in
+        grass-gxw.dtd.
+        """
+        self.tree = tree
+        self.root = self.tree.getroot()
+        
+        #
+        # layer manager properties
+        #
+        self.layerManager = {}
+        self.layerManager['pos']  = None # window position
+        self.layerManager['size'] = None # window size
+        
+        #
+        # list of mapdisplays
+        #
+        self.displays = []
+        #
+        # list of map layers
+        #
+        self.layers = []
+        #
+        # nviz state
+        #
+        self.nviz_state = {}
+        
+        self.displayIndex = -1 # first display has index '0'
+        
+        self.__processFile()
+        
+        self.nvizDefault = Nviz()
+        
+    def __filterValue(self, value):
+        """!Filter value
+        
+        @param value
+        """
+        value = value.replace('&lt;', '<')
+        value = value.replace('&gt;', '>')
+        
+        return value
+
+    def __getNodeText(self, node, tag, default = ''):
+        """!Get node text"""
+        p = node.find(tag)
+        if p is not None:
+            return normalize_whitespace(p.text)
+        
+        return default
+    
+    def __processFile(self):
+        """!Process workspace file"""
+        #
+        # layer manager
+        #
+        node_lm = self.root.find('layer_manager')
+        if node_lm is not None:
+            posAttr = node_lm.get('dim', '')
+            if posAttr:
+                posVal = map(int, posAttr.split(','))
+                try:
+                    self.layerManager['pos']  = (posVal[0], posVal[1])
+                    self.layerManager['size'] = (posVal[2], posVal[3])
+                except:
+                    pass
+        
+        #
+        # displays
+        #
+        for display in self.root.findall('display'):
+            self.displayIndex += 1
+            
+            # window position and size
+            posAttr = display.get('dim', '')
+            if posAttr:
+                posVal = map(int, posAttr.split(','))
+                try:
+                    pos  = (posVal[0], posVal[1])
+                    size = (posVal[2], posVal[3])
+                except:
+                    pos  = None
+                    size = None
+            else:
+                pos  = None
+                size = None
+            
+            extentAttr = display.get('extent', '')
+            if extentAttr:
+                # w, s, e, n
+                extent = map(float, extentAttr.split(','))
+            else:
+                extent = None
+            
+            # projection
+            node_projection = display.find('projection')
+            if node_projection is not None:
+                projection = { 'enabled' : True,
+                               'epsg' : node_projection.get('epsg', ''),
+                               'proj' : self.__getNodeText(node_projection, 'value') }
+            else:
+                projection = { 'enabled' : False }
+            
+            self.displays.append( {
+                    "render"         : bool(int(display.get('render', "0"))),
+                    "mode"           : int(display.get('mode', 0)),
+                    "showCompExtent" : bool(int(display.get('showCompExtent', "0"))),
+                    "pos"            : pos,
+                    "size"           : size,
+                    "extent"         : extent,
+                    "alignExtent"    : bool(int(display.get('alignExtent', "0"))),
+                    "constrainRes"   : bool(int(display.get('constrainRes', "0"))),
+                    "projection"     : projection,
+                    "viewMode"       : display.get('viewMode', '2d')} )
+            
+            # process all layers/groups in the display
+            self.__processLayers(display)
+            # process nviz_state
+            self.__processNvizState(display)
+
+    def __processLayers(self, node, inGroup = -1):
+        """!Process layers/groups of selected display
+        
+        @param node display tree node
+        @param inGroup in group -> index of group item otherwise -1
+        """
+        for item in node.getchildren():
+            if item.tag == 'group':
+                # -> group
+                self.layers.append( {
+                        "type"    : 'group',
+                        "name"    : item.get('name', ''),
+                        "checked" : bool(int(item.get('checked', "0"))),
+                        "opacity" : None,
+                        "cmd"     : None,
+                        "group"   : inGroup,
+                        "display" : self.displayIndex,
+                        "vdigit"  : None,
+                        "nviz"    : None})
+                
+                self.__processLayers(item, inGroup = len(self.layers) - 1) # process items in group
+                
+            elif item.tag == 'layer':
+                cmd, selected, vdigit, nviz = self.__processLayer(item)
+                
+                self.layers.append( {
+                        "type"     : item.get('type', None),
+                        "name"     : item.get('name', None),
+                        "checked"  : bool(int(item.get('checked', "0"))),
+                        "opacity"  : float(item.get('opacity', '1.0')),
+                        "cmd"      : cmd,
+                        "group"    : inGroup,
+                        "display"  : self.displayIndex,
+                        "selected" : selected,
+                        "vdigit"   : vdigit,
+                        "nviz"     : nviz } )
+            
+    def __processLayer(self, layer):
+        """!Process layer item
+
+        @param layer tree node
+        """
+        cmd = list()
+        
+        #
+        # layer attributes (task) - 2D settings
+        #
+        node_task = layer.find('task')
+        cmd.append(node_task.get('name', "unknown"))
+        
+        # flags
+        for p in node_task.findall('flag'):
+            flag = p.get('name', '')
+            if len(flag) > 1:
+                cmd.append('--' + flag)
+            else:
+                cmd.append('-' + flag)
+        
+        # parameters
+        for p in node_task.findall('parameter'):
+            cmd.append('%s=%s' % (p.get('name', ''),
+                                  self.__filterValue(self.__getNodeText(p, 'value'))))
+        
+        if layer.find('selected') is not None:
+            selected = True
+        else:
+            selected = False
+        
+        #
+        # Vector digitizer settings
+        #
+        node_vdigit = layer.find('vdigit')
+        if node_vdigit is not None:
+            vdigit = self.__processLayerVdigit(node_vdigit)
+        else:
+            vdigit = None
+        
+        #
+        # Nviz (3D settings)
+        #
+        node_nviz = layer.find('nviz')
+        if node_nviz is not None:
+            nviz = self.__processLayerNviz(node_nviz)
+        else:
+            nviz = None
+        
+        return (cmd, selected, vdigit, nviz)
+
+    def __processLayerVdigit(self, node_vdigit):
+        """!Process vector digitizer layer settings
+
+        @param node_vdigit vdigit node
+        """
+        # init nviz layer properties
+        vdigit = dict()
+        for node in node_vdigit.findall('geometryAttribute'):
+            if 'geomAttr' not in vdigit:
+                vdigit['geomAttr'] = dict()
+            type = node.get('type')
+            vdigit['geomAttr'][type] = dict()
+            vdigit['geomAttr'][type]['column'] = node.get('column') # required
+            # default map units
+            vdigit['geomAttr'][type]['units'] = node.get('units', 'mu')
+        
+        return vdigit
+    
+    def __processLayerNviz(self, node_nviz):
+        """!Process 3D layer settings
+
+        @param node_nviz nviz node
+        """
+        # init nviz layer properties
+        nviz = {}
+        if node_nviz.find('surface') is not None: # -> raster
+            nviz['surface'] = {}
+            for sec in ('attribute', 'draw', 'mask', 'position'):
+                nviz['surface'][sec] = {}
+        elif node_nviz.find('vlines') is not None or \
+                node_nviz.find('vpoints') is not None: # -> vector
+            nviz['vector'] = {}
+            for sec in ('lines', 'points'):
+                nviz['vector'][sec] = {}
+        
+        if 'surface' in nviz:
+            node_surface = node_nviz.find('surface')
+            # attributes
+            for attrb in node_surface.findall('attribute'):
+                tagName = str(attrb.tag)
+                attrbName = attrb.get('name', '')
+                dc = nviz['surface'][tagName][attrbName] = {}
+                if attrb.get('map', '0') == '0':
+                    dc['map'] = False
+                else:
+                    dc['map'] = True
+                value = self.__getNodeText(attrb, 'value')
+                try:
+                    dc['value'] = int(value)
+                except ValueError:
+                    try:
+                        dc['value'] = float(value)
+                    except ValueError:
+                        dc['value'] = str(value)
+            
+            # draw
+            node_draw = node_surface.find('draw')
+            if node_draw is not None:
+                tagName = str(node_draw.tag)
+                nviz['surface'][tagName]['all'] = False
+                nviz['surface'][tagName]['mode'] = {}
+                nviz['surface'][tagName]['mode']['value'] = -1 # to be calculated
+                nviz['surface'][tagName]['mode']['desc'] = {}
+                nviz['surface'][tagName]['mode']['desc']['shading'] = \
+                    str(node_draw.get('shading', ''))
+                nviz['surface'][tagName]['mode']['desc']['style'] = \
+                    str(node_draw.get('style', ''))
+                nviz['surface'][tagName]['mode']['desc']['mode'] = \
+                    str(node_draw.get('mode', ''))
+                
+                # resolution
+                for node_res in node_draw.findall('resolution'):
+                    resType = str(node_res.get('type', ''))
+                    if 'resolution' not in nviz['surface']['draw']:
+                        nviz['surface']['draw']['resolution'] = {}
+                    value = int(self.__getNodeText(node_res, 'value'))
+                    nviz['surface']['draw']['resolution'][resType] = value
+                
+                # wire-color
+                node_wire_color = node_draw.find('wire_color')
+                if node_wire_color is not None:
+                    nviz['surface']['draw']['wire-color'] = {}
+                    value = str(self.__getNodeText(node_wire_color, 'value'))
+                    nviz['surface']['draw']['wire-color']['value'] = value
+                
+            # position
+            node_pos = node_surface.find('position')
+            if node_pos is not None:
+                dc = nviz['surface']['position'] = {}
+                for coor in ['x', 'y', 'z']:
+                    node = node_pos.find(coor)
+                    if node is None:
+                        continue
+                    value = int(self.__getNodeText(node_pos, coor))
+                    dc[coor] = value
+            
+        elif 'vector' in nviz:
+            # vpoints
+            node_vpoints = node_nviz.find('vpoints')
+            if node_vpoints is not None:
+                marker = str(node_vpoints.get('marker', ''))
+                markerId = list(UserSettings.Get(group='nviz', key='vector',
+                                                 subkey=['points', 'marker'], internal=True)).index(marker)
+                nviz['vector']['points']['marker'] = { 'value' : markerId }
+                
+                node_mode = node_vpoints.find('mode')
+                if node_mode is not None:
+                    nviz['vector']['points']['mode'] = {}
+                    nviz['vector']['points']['mode']['type'] = str(node_mode.get('type', 'surface'))
+                    nviz['vector']['points']['mode']['surface'] = {}
+                    nviz['vector']['points']['mode']['surface']['value'] = []
+                    nviz['vector']['points']['mode']['surface']['show'] = []
+                    
+                    # map
+                    for node_map in node_mode.findall('map'):
+                        nviz['vector']['points']['mode']['surface']['value'].append(
+                            self.__processLayerNvizNode(node_map, 'name', str))
+                        nviz['vector']['points']['mode']['surface']['show'].append(bool(
+                            self.__processLayerNvizNode(node_map, 'checked', int)))
+                
+                # color
+                self.__processLayerNvizNode(node_vpoints, 'color', str,
+                                            nviz['vector']['points'])
+                
+                # width
+                self.__processLayerNvizNode(node_vpoints, 'width', int,
+                                            nviz['vector']['points'])
+                
+                # height
+                self.__processLayerNvizNode(node_vpoints, 'height', int,
+                                            nviz['vector']['points'])
+                
+                # height
+                self.__processLayerNvizNode(node_vpoints, 'size', int,
+                                            nviz['vector']['points'])
+                
+                # thematic
+                node_thematic = node_vpoints.find('thematic')
+                thematic = nviz['vector']['points']['thematic'] = {}
+                thematic['rgbcolumn'] = self.__processLayerNvizNode(node_thematic, 'rgbcolumn', str)
+                thematic['sizecolumn'] = self.__processLayerNvizNode(node_thematic, 'sizecolumn', str)
+                for col in ('rgbcolumn', 'sizecolumn'):
+                    if thematic[col] == 'None':
+                        thematic[col] = None
+                thematic['layer'] = self.__processLayerNvizNode(node_thematic, 'layer', int)
+                for use in ('usecolor', 'usesize', 'usewidth'):
+                    if node_thematic.get(use, ''):
+                        thematic[use] = int(node_thematic.get(use, '0'))
+                
+            # vlines
+            node_vlines = node_nviz.find('vlines')
+            if node_vlines is not None:
+                node_mode = node_vlines.find('mode')
+                if node_mode is not None:
+                    nviz['vector']['lines']['mode'] = {}
+                    nviz['vector']['lines']['mode']['type'] = str(node_mode.get('type', ''))
+                    nviz['vector']['lines']['mode']['surface'] = {}
+                    nviz['vector']['lines']['mode']['surface']['value'] = []
+                    nviz['vector']['lines']['mode']['surface']['show'] = []
+                    
+                    # map
+                    for node_map in node_mode.findall('map'):
+                        nviz['vector']['lines']['mode']['surface']['value'].append(
+                            self.__processLayerNvizNode(node_map, 'name', str))
+                        nviz['vector']['lines']['mode']['surface']['show'].append(bool(
+                            self.__processLayerNvizNode(node_map, 'checked', int)))
+                
+                # color
+                self.__processLayerNvizNode(node_vlines, 'color', str,
+                                            nviz['vector']['lines'])
+                
+                # width
+                self.__processLayerNvizNode(node_vlines, 'width', int,
+                                            nviz['vector']['lines'])
+                
+                # height
+                self.__processLayerNvizNode(node_vlines, 'height', int,
+                                            nviz['vector']['lines'])
+                
+                # thematic
+                node_thematic = node_vlines.find('thematic')
+                thematic = nviz['vector']['lines']['thematic'] = {}
+                thematic['rgbcolumn'] = self.__processLayerNvizNode(node_thematic, 'rgbcolumn', str)
+                thematic['sizecolumn'] = self.__processLayerNvizNode(node_thematic, 'sizecolumn', str)
+                for col in ('rgbcolumn', 'sizecolumn'):
+                    if thematic[col] == 'None':
+                        thematic[col] = None
+                thematic['layer'] = self.__processLayerNvizNode(node_thematic, 'layer', int)
+                for use in ('usecolor', 'usesize', 'usewidth'):
+                    if node_thematic.get(use, ''):
+                        thematic[use] = int(node_thematic.get(use, '0'))
+            
+        return nviz
+    
+    def __processLayerNvizNode(self, node, tag, cast, dc = None):
+        """!Process given tag nviz/vector"""
+        node_tag = node.find(tag)
+        if node_tag is not None:
+            if node_tag.find('value') is not None:
+                value = cast(self.__getNodeText(node_tag, 'value'))
+            else:
+                try:
+                    value = cast(node_tag.text)
+                except ValueError:
+                    if cast == str:
+                        value = ''
+                    else:
+                        value = None
+            if dc:
+                dc[tag] = dict()
+                dc[tag]['value'] = value
+            else:
+                return value
+    
+    def __processNvizState(self, node):
+        """!Process tag nviz_state"""
+        node_state = node.find('nviz_state')
+        if node_state is None:
+            return
+        self.nviz_state['display'] = self.displayIndex
+        #
+        # view
+        #
+        node_view = node_state.find('view')
+        view = {}
+        iview = {}
+        
+        node_position = node_view.find('v_position')
+        view['position'] = {}
+        view['position']['x'] = self.__processLayerNvizNode(node_position, 'x', float)
+        view['position']['y'] = self.__processLayerNvizNode(node_position, 'y', float)
+        node_persp = node_view.find('persp')
+        view['persp'] = {}
+        iview['persp'] = {}
+        view['persp']['value'] = self.__processLayerNvizNode(node_persp, 'value', int)
+        view['persp']['step'] = self.__processLayerNvizNode(node_persp, 'step', int)
+        iview['persp']['min'] = self.__processLayerNvizNode(node_persp, 'min', int)
+        iview['persp']['max'] = self.__processLayerNvizNode(node_persp, 'max', int)
+        node_height = node_view.find('v_height')
+        iview['height'] = {}
+        iview['height']['value'] = self.__processLayerNvizNode(node_height, 'value', int)
+        iview['height']['min'] = self.__processLayerNvizNode(node_height, 'min', int)
+        iview['height']['max'] = self.__processLayerNvizNode(node_height, 'max', int)
+        node_twist = node_view.find('twist')
+        view['twist'] = {}
+        iview['twist'] = {}
+        view['twist']['value'] = self.__processLayerNvizNode(node_twist, 'value', int)
+        iview['twist']['min'] = self.__processLayerNvizNode(node_twist, 'min', int)
+        iview['twist']['max'] = self.__processLayerNvizNode(node_twist, 'max', int)
+        node_zexag = node_view.find('z-exag')
+        view['z-exag'] = {}
+        iview['z-exag'] = {}
+        view['z-exag']['value'] = self.__processLayerNvizNode(node_zexag, 'value', int)
+        view['z-exag']['min'] = self.__processLayerNvizNode(node_zexag, 'min', int)
+        view['z-exag']['max'] = self.__processLayerNvizNode(node_zexag, 'max', int)
+        iview['z-exag']['original'] = self.__processLayerNvizNode(node_zexag, 'original', float)
+        node_focus = node_view.find('focus')
+        iview['focus'] = {}
+        iview['focus']['x'] = self.__processLayerNvizNode(node_focus, 'x', int)
+        iview['focus']['y'] = self.__processLayerNvizNode(node_focus, 'y', int)
+        iview['focus']['z'] = self.__processLayerNvizNode(node_focus, 'z', int)
+        node_dir = node_view.find('dir')
+        if node_dir:
+            iview['dir'] = {}
+            iview['dir']['x'] = self.__processLayerNvizNode(node_dir, 'x', int)
+            iview['dir']['y'] = self.__processLayerNvizNode(node_dir, 'y', int)
+            iview['dir']['z'] = self.__processLayerNvizNode(node_dir, 'z', int)
+            iview['dir']['use'] = True
+        else:
+            iview['dir'] = {}
+            iview['dir']['x'] = -1
+            iview['dir']['y'] = -1
+            iview['dir']['z'] = -1
+            iview['dir']['use'] = False
+        
+        view['background'] = {}
+        color = self.__processLayerNvizNode(node_view, 'background_color', str)
+        view['background']['color'] = tuple(map(int, color.split(':')))
+        
+        self.nviz_state['view'] = view
+        self.nviz_state['iview'] = iview
+        #
+        # light
+        #
+        node_light = node_state.find('light')
+        light = {}
+        
+        node_position = node_light.find('l_position')
+        light['position'] = {}
+        light['position']['x'] = self.__processLayerNvizNode(node_position, 'x', float)
+        light['position']['y'] = self.__processLayerNvizNode(node_position, 'y', float)
+        light['position']['z'] = self.__processLayerNvizNode(node_position, 'z', int)
+        
+        light['bright'] = self.__processLayerNvizNode(node_light, 'bright', int) 
+        light['ambient'] = self.__processLayerNvizNode(node_light, 'ambient', int)
+        color = self.__processLayerNvizNode(node_light, 'color', str)
+        light['color'] = tuple(map(int, color.split(':')))
+        
+        self.nviz_state['light'] = light
+        
+        node_constants = node_state.find('constant_planes')
+        constants = []
+        if node_constants:
+            for i, node_plane in enumerate(node_constants.findall('plane')):
+                plane = {}
+                plane['color'] = self.__processLayerNvizNode(node_plane, 'color', str)                
+                plane['resolution'] = self.__processLayerNvizNode(node_plane, 'fine_resolution', int)
+                plane['value'] = self.__processLayerNvizNode(node_plane, 'height', int)
+                plane['object'] = {}
+                constants.append({'constant': plane})
+        self.nviz_state['constants'] = constants    
+
+class WriteWorkspaceFile(object):
+    """!Generic class for writing workspace file"""
+    def __init__(self, lmgr, file):
+        self.file =  file
+        self.lmgr = lmgr
+        self.indent = 0
+        
+        # write header
+        self.file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+        self.file.write('<!DOCTYPE gxw SYSTEM "grass-gxw.dtd">\n')
+        self.file.write('%s<gxw>\n' % (' ' * self.indent))
+        
+        self.indent =+ 4
+        
+        # layer manager
+        windowPos = self.lmgr.GetPosition()
+        windowSize = self.lmgr.GetSize()
+        file.write('%s<layer_manager dim="%d,%d,%d,%d">\n' % (' ' * self.indent,
+                                                              windowPos[0],
+                                                              windowPos[1],
+                                                              windowSize[0],
+                                                              windowSize[1]
+                                                              ))
+        
+        file.write('%s</layer_manager>\n' % (' ' * self.indent))
+        
+        # list of displays
+        for page in range(0, self.lmgr.gm_cb.GetPageCount()):
+            mapTree = self.lmgr.gm_cb.GetPage(page).maptree
+            region = mapTree.Map.region
+            
+            displayPos = mapTree.mapdisplay.GetPosition()
+            displaySize = mapTree.mapdisplay.GetSize()
+            if mapTree.mapdisplay.toolbars['map'].combo.GetSelection() == 1:
+                viewmode = '3d'
+            else:
+                viewmode = '2d'
+            
+            file.write('%s<display render="%d" '
+                       'mode="%d" showCompExtent="%d" '
+                       'alignExtent="%d" '
+                       'constrainRes="%d" '
+                       'dim="%d,%d,%d,%d" '
+                       'extent="%f,%f,%f,%f" '
+                       'viewMode="%s" >\n' % (' ' * self.indent,
+                                              int(mapTree.mapdisplay.GetProperty('render')),
+                                              mapTree.mapdisplay.statusbarManager.GetMode(),
+                                              int(mapTree.mapdisplay.GetProperty('region')),
+                                              int(mapTree.mapdisplay.GetProperty('alignExtent')),
+                                              int(mapTree.mapdisplay.GetProperty('resolution')),
+                                              displayPos[0],
+                                              displayPos[1],
+                                              displaySize[0],
+                                              displaySize[1],
+                                              region['w'],
+                                              region['s'],
+                                              region['e'],
+                                              region['n'],
+                                              viewmode
+                                              ))
+            # projection statusbar info
+            if mapTree.mapdisplay.GetProperty('projection') and \
+                    UserSettings.Get(group='display', key='projection', subkey='proj4'):
+                self.indent += 4
+                file.write('%s<projection' % (' ' * self.indent))
+                epsg = UserSettings.Get(group='display', key='projection', subkey='epsg')
+                if epsg:
+                    file.write(' epsg="%s"' % epsg)
+                file.write('>\n')
+                proj = UserSettings.Get(group='display', key='projection', subkey='proj4')
+                self.indent += 4 
+                file.write('%s<value>%s</value>\n' % (' ' * self.indent, proj))
+                self.indent -= 4
+                file.write('%s</projection>\n' % (' ' * self.indent))
+                self.indent -= 4
+            
+            # list of layers
+            item = mapTree.GetFirstChild(mapTree.root)[0]
+            self.__writeLayer(mapTree, item)
+            
+            if mapTree.mapdisplay.MapWindow3D is not None:
+                nvizDisp = mapTree.mapdisplay.MapWindow3D
+                self.__writeNvizState(view = nvizDisp.view, iview =  nvizDisp.iview, 
+                                      light = nvizDisp.light, constants = nvizDisp.constants)
+            
+            file.write('%s</display>\n' % (' ' * self.indent))
+        
+        self.indent =- 4
+        file.write('%s</gxw>\n' % (' ' * self.indent))
+
+    def __filterValue(self, value):
+        """!Make value XML-valid"""
+        value = value.replace('<', '&lt;')
+        value = value.replace('>', '&gt;')
+        
+        return value
+    
+    def __writeLayer(self, mapTree, item):
+        """!Write bunch of layers to GRASS Workspace XML file"""
+        self.indent += 4
+        itemSelected = mapTree.GetSelections()
+        while item and item.IsOk():
+            type = mapTree.GetPyData(item)[0]['type']
+            if type != 'group':
+                maplayer = mapTree.GetPyData(item)[0]['maplayer']
+            else:
+                maplayer = None
+
+            checked = int(item.IsChecked())
+            if type == 'command':
+                cmd = mapTree.GetPyData(item)[0]['maplayer'].GetCmd(string=True)
+                self.file.write('%s<layer type="%s" name="%s" checked="%d">\n' % \
+                               (' ' * self.indent, type, cmd, checked));
+                self.file.write('%s</layer>\n' % (' ' * self.indent));
+            elif type == 'group':
+                name = mapTree.GetItemText(item)
+                self.file.write('%s<group name="%s" checked="%d">\n' % \
+                               (' ' * self.indent, name.encode('utf8'), checked));
+                self.indent += 4
+                subItem = mapTree.GetFirstChild(item)[0]
+                self.__writeLayer(mapTree, subItem)
+                self.indent -= 4
+                self.file.write('%s</group>\n' % (' ' * self.indent));
+            else:
+                cmd = mapTree.GetPyData(item)[0]['maplayer'].GetCmd(string=False)
+                name = mapTree.GetItemText(item)
+                opacity = maplayer.GetOpacity(float = True)
+                # remove 'opacity' part
+                if opacity < 1:
+                    name = name.split('(', -1)[0].strip()
+                self.file.write('%s<layer type="%s" name="%s" checked="%d" opacity="%f">\n' % \
+                                    (' ' * self.indent, type, name.encode('utf8'), checked, opacity));
+                
+                self.indent += 4
+                # selected ?
+                if item in itemSelected:
+                    self.file.write('%s<selected />\n' % (' ' * self.indent))
+                # layer properties
+                self.file.write('%s<task name="%s">\n' % (' ' * self.indent, cmd[0]))
+                self.indent += 4
+                for key, val in cmd[1].iteritems():
+                    if key == 'flags':
+                        for f in val:
+                            self.file.write('%s<flag name="%s" />\n' %
+                                            (' ' * self.indent, f))
+                    elif val in (True, False):
+                        self.file.write('%s<flag name="%s" />\n' %
+                                        (' ' * self.indent, key))
+                    else: # parameter
+                        self.file.write('%s<parameter name="%s">\n' %
+                                        (' ' * self.indent, key))
+                        self.indent += 4
+                        self.file.write('%s<value>%s</value>\n' %
+                                        (' ' * self.indent, self.__filterValue(val)))
+                        self.indent -= 4
+                        self.file.write('%s</parameter>\n' % (' ' * self.indent));
+                self.indent -= 4
+                self.file.write('%s</task>\n' % (' ' * self.indent));
+                # vector digitizer
+                vdigit = mapTree.GetPyData(item)[0]['vdigit']
+                if vdigit:
+                    self.file.write('%s<vdigit>\n' % (' ' * self.indent))
+                    if 'geomAttr' in vdigit:
+                        self.indent += 4
+                        for type, val in vdigit['geomAttr'].iteritems():
+                            units = ''
+                            if val['units'] != 'mu':
+                                units = ' units="%s"' % val['units']
+                            self.file.write('%s<geometryAttribute type="%s" column="%s"%s />\n' % \
+                                                (' ' * self.indent, type, val['column'], units))
+                        self.indent -= 4
+                    self.file.write('%s</vdigit>\n' % (' ' * self.indent))
+                # nviz
+                nviz = mapTree.GetPyData(item)[0]['nviz']
+                if nviz:
+                    self.file.write('%s<nviz>\n' % (' ' * self.indent))
+                    if maplayer.type == 'raster':
+                        self.__writeNvizSurface(nviz['surface'])
+                    elif maplayer.type == 'vector':
+                        self.__writeNvizVector(nviz['vector'])
+                    self.file.write('%s</nviz>\n' % (' ' * self.indent))
+                self.indent -= 4
+                self.file.write('%s</layer>\n' % (' ' * self.indent))
+            item = mapTree.GetNextSibling(item)
+        self.indent -= 4
+        
+    def __writeNvizSurface(self, data):
+        """!Save Nviz raster layer properties to workspace
+
+        @param data Nviz layer properties
+        """
+        if 'object' not in data: # skip disabled
+            return
+        self.indent += 4
+        self.file.write('%s<surface>\n' % (' ' * self.indent))
+        self.indent += 4
+        for attrb in data.iterkeys():
+            if len(data[attrb]) < 1: # skip empty attributes
+                continue
+            if attrb == 'object':
+                continue
+            
+            for name in data[attrb].iterkeys():
+                # surface attribute
+                if attrb == 'attribute':
+                    self.file.write('%s<%s name="%s" map="%d">\n' % \
+                                   (' ' * self.indent, attrb, name, data[attrb][name]['map']))
+                    self.indent += 4
+                    self.file.write('%s<value>%s</value>\n' % (' ' * self.indent, data[attrb][name]['value']))
+                    self.indent -= 4
+                    # end tag
+                    self.file.write('%s</%s>\n' % (' ' * self.indent, attrb))
+
+            # draw mode
+            if attrb == 'draw':
+                self.file.write('%s<%s' %(' ' * self.indent, attrb))
+                if 'mode' in data[attrb]:
+                    for tag, value in data[attrb]['mode']['desc'].iteritems():
+                        self.file.write(' %s="%s"' % (tag, value))
+                self.file.write('>\n') # <draw ...>
+
+                if 'resolution' in data[attrb]:
+                    self.indent += 4
+                    for type in ('coarse', 'fine'):
+                        self.file.write('%s<resolution type="%s">\n' % (' ' * self.indent, type))
+                        self.indent += 4
+                        self.file.write('%s<value>%d</value>\n' % (' ' * self.indent,
+                                                                   data[attrb]['resolution'][type]))
+                        self.indent -= 4
+                        self.file.write('%s</resolution>\n' % (' ' * self.indent))
+
+                if 'wire-color' in data[attrb]:
+                    self.file.write('%s<wire_color>\n' % (' ' * self.indent))
+                    self.indent += 4
+                    self.file.write('%s<value>%s</value>\n' % (' ' * self.indent,
+                                                               data[attrb]['wire-color']['value']))
+                    self.indent -= 4
+                    self.file.write('%s</wire_color>\n' % (' ' * self.indent))
+                self.indent -= 4
+            
+            # position
+            elif attrb == 'position':
+                self.file.write('%s<%s>\n' %(' ' * self.indent, attrb))
+                i = 0
+                for tag in ('x', 'y', 'z'):
+                    self.indent += 4
+                    self.file.write('%s<%s>%d</%s>\n' % (' ' * self.indent, tag,
+                                                        data[attrb][tag], tag))
+                    i += 1
+                    self.indent -= 4
+
+            if attrb != 'attribute':
+                # end tag
+                self.file.write('%s</%s>\n' % (' ' * self.indent, attrb))
+
+        self.indent -= 4
+        self.file.write('%s</surface>\n' % (' ' * self.indent))
+        self.indent -= 4
+
+    def __writeNvizVector(self, data):
+        """!Save Nviz vector layer properties (lines/points) to workspace
+
+        @param data Nviz layer properties
+        """
+        self.indent += 4
+        for attrb in data.iterkeys():
+            if len(data[attrb]) < 1: # skip empty attributes
+                continue
+
+            if 'object' not in data[attrb]: # skip disabled
+                continue
+            if attrb == 'lines':
+                self.file.write('%s<v%s>\n' % (' ' * self.indent, attrb))
+            elif attrb == 'points':
+                markerId = data[attrb]['marker']['value']
+                marker = UserSettings.Get(group = 'nviz', key = 'vector',
+                                          subkey = ['points', 'marker'], internal = True)[markerId]
+                self.file.write('%s<v%s marker="%s">\n' % (' ' * self.indent,
+                                                           attrb,
+                                                           marker))
+            self.indent += 4
+            for name in data[attrb].iterkeys():
+                if name in ('object', 'marker'):
+                    continue
+                if name == 'mode':
+                    self.file.write('%s<%s type="%s">\n' % (' ' * self.indent, name,
+                                                          data[attrb][name]['type']))
+                    if data[attrb][name]['type'] == 'surface':
+                        self.indent += 4
+                        for idx, surface in enumerate(data[attrb][name]['surface']['value']):
+                            checked = data[attrb][name]['surface']['show'][idx]
+                            self.file.write('%s<map>\n' % (' ' * self.indent))
+                            self.indent += 4
+                            self.file.write('%s<name>%s</name>\n' % (' ' * self.indent, surface))
+                            self.file.write('%s<checked>%s</checked>\n' % (' ' * self.indent, int(checked)))
+                            self.indent -= 4
+                            self.file.write('%s</map>\n' % (' ' * self.indent))
+                        self.indent -= 4
+                    self.file.write('%s</%s>\n' % ((' ' * self.indent, name)))
+                elif name == 'thematic':
+                    self.file.write('%s<%s ' % (' ' * self.indent, name))
+                    for key in data[attrb][name].iterkeys():
+                        if key.startswith('use'):
+                            self.file.write('%s="%s" ' % (key, int(data[attrb][name][key])))
+                    self.file.write('>\n')
+                    self.indent += 4
+                    for key, value in data[attrb][name].iteritems():
+                        if key.startswith('use'):
+                            continue
+                        if value is None:
+                            value = ''
+                        self.file.write('%s<%s>%s</%s>\n' % (' ' * self.indent, key, value, key))
+                    self.indent -= 4
+                    self.file.write('%s</%s>\n' % (' ' * self.indent, name))
+                else:
+                    self.file.write('%s<%s>\n' % (' ' * self.indent, name))
+                    self.indent += 4
+                    self.file.write('%s<value>%s</value>\n' % (' ' * self.indent, data[attrb][name]['value']))
+                    self.indent -= 4
+                    self.file.write('%s</%s>\n' % (' ' * self.indent, name))
+            self.indent -= 4
+            self.file.write('%s</v%s>\n' % (' ' * self.indent, attrb))
+
+        self.indent -= 4
+
+    def __writeNvizState(self, view, iview, light, constants):
+        """"!Save Nviz properties (view, light) to workspace
+
+        @param view Nviz view properties
+        @param iview Nviz internal view properties
+        @param light Nviz light properties
+        """
+        self.indent += 4
+        self.file.write('%s<nviz_state>\n' % (' ' * self.indent))
+        #
+        # view
+        #
+        self.indent += 4
+        self.file.write('%s<view>\n' % (' ' * self.indent))
+        self.indent += 4
+        # position
+        self.file.write('%s<v_position>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<x>%.2f</x>\n' % (' ' * self.indent, view['position']['x']))
+        self.file.write('%s<y>%.2f</y>\n' % (' ' * self.indent, view['position']['y']))
+        self.indent -= 4
+        self.file.write('%s</v_position>\n' % (' ' * self.indent))
+        # perspective
+        self.file.write('%s<persp>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<value>%d</value>\n' % (' ' * self.indent, view['persp']['value']))
+        self.file.write('%s<step>%d</step>\n' % (' ' * self.indent, view['persp']['step']))
+        self.file.write('%s<min>%d</min>\n' % (' ' * self.indent, iview['persp']['min']))
+        self.file.write('%s<max>%d</max>\n' % (' ' * self.indent, iview['persp']['max']))
+        self.indent -= 4
+        self.file.write('%s</persp>\n' % (' ' * self.indent))
+        # height
+        self.file.write('%s<v_height>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<value>%d</value>\n' % (' ' * self.indent, iview['height']['value']))
+        self.file.write('%s<min>%d</min>\n' % (' ' * self.indent, iview['height']['min']))
+        self.file.write('%s<max>%d</max>\n' % (' ' * self.indent, iview['height']['max']))
+        self.indent -= 4
+        self.file.write('%s</v_height>\n' % (' ' * self.indent))
+        # twist
+        self.file.write('%s<twist>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<value>%d</value>\n' % (' ' * self.indent, view['twist']['value']))
+        self.file.write('%s<min>%d</min>\n' % (' ' * self.indent, iview['twist']['min']))
+        self.file.write('%s<max>%d</max>\n' % (' ' * self.indent, iview['twist']['max']))
+        self.indent -= 4
+        self.file.write('%s</twist>\n' % (' ' * self.indent))
+        # z-exag
+        self.file.write('%s<z-exag>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<value>%d</value>\n' % (' ' * self.indent, view['z-exag']['value']))
+        self.file.write('%s<min>%d</min>\n' % (' ' * self.indent, view['z-exag']['min']))
+        self.file.write('%s<max>%d</max>\n' % (' ' * self.indent, view['z-exag']['max']))
+        self.file.write('%s<original>%d</original>\n' % (' ' * self.indent, iview['z-exag']['original']))
+        self.indent -= 4
+        self.file.write('%s</z-exag>\n' % (' ' * self.indent))
+        # focus (look here)
+        self.file.write('%s<focus>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<x>%d</x>\n' % (' ' * self.indent, iview['focus']['x']))
+        self.file.write('%s<y>%d</y>\n' % (' ' * self.indent, iview['focus']['y']))
+        self.file.write('%s<z>%d</z>\n' % (' ' * self.indent, iview['focus']['z']))
+        self.indent -= 4
+        self.file.write('%s</focus>\n' % (' ' * self.indent))
+        # background
+        self.__writeTagWithValue('background_color', view['background']['color'][:3], format = 'd:%d:%d')
+        
+        self.indent -= 4
+        self.file.write('%s</view>\n' % (' ' * self.indent))
+        #
+        # light
+        #
+        self.file.write('%s<light>\n' % (' ' * self.indent))
+        self.indent += 4
+        # position
+        self.file.write('%s<l_position>\n' % (' ' * self.indent))
+        self.indent += 4
+        self.file.write('%s<x>%.2f</x>\n' % (' ' * self.indent, light['position']['x']))
+        self.file.write('%s<y>%.2f</y>\n' % (' ' * self.indent, light['position']['y']))
+        self.file.write('%s<z>%d</z>\n' % (' ' * self.indent, light['position']['z']))
+        self.indent -= 4
+        self.file.write('%s</l_position>\n' % (' ' * self.indent))
+        # bright
+        self.__writeTagWithValue('bright', light['bright'])
+        # ambient
+        self.__writeTagWithValue('ambient', light['ambient'])
+        # color
+        self.__writeTagWithValue('color', light['color'][:3], format = 'd:%d:%d')
+        
+        self.indent -= 4
+        self.file.write('%s</light>\n' % (' ' * self.indent))
+        #
+        # constant planes
+        #
+        if constants:
+            self.file.write('%s<constant_planes>\n' % (' ' * self.indent))
+            self.indent += 4
+            for idx, plane in enumerate(constants):
+                self.file.write('%s<plane>\n' % (' ' * self.indent))
+                self.indent += 4
+                self.__writeTagWithValue('height', constants[idx]['constant']['value'])
+                self.__writeTagWithValue('fine_resolution', constants[idx]['constant']['resolution'])
+                self.__writeTagWithValue('color', constants[idx]['constant']['color'], format = 's')
+                self.indent -= 4
+                self.file.write('%s</plane>\n' % (' ' * self.indent))
+            self.indent -= 4
+            self.file.write('%s</constant_planes>\n' % (' ' * self.indent))
+        self.indent -= 4
+        
+        self.file.write('%s</nviz_state>\n' % (' ' * self.indent))
+        self.indent -= 4
+    
+    def __writeTagWithValue(self, tag, data, format = 'd'):
+        """!Helper function for writing pair tag
+        
+        @param tag written tag
+        @param data written data
+        @param format conversion type
+        """
+        self.file.write('%s<%s>\n' % (' ' * self.indent, tag))
+        self.indent += 4
+        self.file.write('%s' % (' ' * self.indent))
+        self.file.write(('<value>%' + format + '</value>\n') % data)
+        self.indent -= 4
+        self.file.write('%s</%s>\n' % (' ' * self.indent, tag))
+        
+class ProcessGrcFile(object):
+    def __init__(self, filename):
+        """!Process GRC file"""
+        self.filename = filename
+
+        # elements
+        self.inGroup = False
+        self.inRaster = False
+        self.inVector = False
+
+        # list of layers
+        self.layers = []
+
+        # error message
+        self.error = ''
+        self.num_error = 0
+
+    def read(self, parent):
+        """!Read GRC file
+
+        @param parent parent window
+
+        @return list of map layers
+        """
+        try:
+            file = open(self.filename, "r")
+        except IOError:
+            wx.MessageBox(parent=parent,
+                          message=_("Unable to open file <%s> for reading.") % self.filename,
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR)
+            return []
+
+        line_id = 1
+        for line in file.readlines():
+            self.process_line(line.rstrip('\n'), line_id)
+            line_id +=1
+
+        file.close()
+
+        if self.num_error > 0:
+            wx.MessageBox(parent=parent,
+                          message=_("Some lines were skipped when reading settings "
+                                    "from file <%(file)s>.\nSee 'Command output' window for details.\n\n"
+                                    "Number of skipped lines: %(line)d") % \
+                                        { 'file' : self.filename, 'line' : self.num_error },
+                          caption=_("Warning"), style=wx.OK | wx.ICON_EXCLAMATION)
+            parent.goutput.WriteLog('Map layers loaded from GRC file <%s>' % self.filename)
+            parent.goutput.WriteLog('Skipped lines:\n%s' % self.error)
+
+        return self.layers
+
+    def process_line(self, line, line_id):
+        """!Process line definition"""
+        element = self._get_element(line)
+        if element == 'Group':
+            self.groupName = self._get_value(line)
+            self.layers.append({
+                    "type"    : 'group',
+                    "name"    : self.groupName,
+                    "checked" : None,
+                    "opacity" : None,
+                    "cmd"     : None,
+                    "group"   : self.inGroup,
+                    "display" : 0 })
+            self.inGroup = True
+
+        elif element == '_check':
+            if int(self._get_value(line)) ==  1:
+                self.layers[-1]['checked'] = True
+            else:
+                self.layers[-1]['checked'] = False
+            
+        elif element == 'End':
+            if self.inRaster:
+                self.inRaster = False
+            elif self.inVector:
+                self.inVector = False
+            elif self.inGroup:
+                self.inGroup = False
+            elif self.inGridline:
+                self.inGridline = False
+        
+        elif element == 'opacity':
+            self.layers[-1]['opacity'] = float(self._get_value(line))
+
+        # raster
+        elif element == 'Raster':
+            self.inRaster = True
+            self.layers.append({
+                    "type"    : 'raster',
+                    "name"    : self._get_value(line),
+                    "checked" : None,
+                    "opacity" : None,
+                    "cmd"     : ['d.rast'],
+                    "group"   : self.inGroup,
+                    "display" : 0})
+
+        elif element == 'map' and self.inRaster:
+            self.layers[-1]['cmd'].append('map=%s' % self._get_value(line))
+            
+        elif element == 'overlay' and self.inRaster:
+            if int(self._get_value(line)) == 1:
+                self.layers[-1]['cmd'].append('-o')
+            
+        elif element == 'rastquery' and self.inRaster:
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('catlist=%s' % value)
+            
+        elif element == 'bkcolor' and self.inRaster:
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('bg=%s' % value)
+
+        # vector
+        elif element == 'Vector':
+            self.inVector = True
+            self.layers.append({
+                    "type"    : 'vector',
+                    "name"    : self._get_value(line),
+                    "checked" : None,
+                    "opacity" : None,
+                    "cmd"     : ['d.vect'],
+                    "group"   : self.inGroup,
+                    "display" : 0})
+
+        elif element == 'vect' and self.inVector:
+            self.layers[-1]['cmd'].append('map=%s' % self._get_value(line))
+                
+        elif element in ('display_shape',
+                         'display_cat',
+                         'display_topo',
+                         'display_dir',
+                         'display_attr',
+                         'type_point',
+                         'type_line',
+                         'type_boundary',
+                         'type_centroid',
+                         'type_area',
+                         'type_face') and self.inVector:
+            
+            if int(self._get_value(line)) == 1:
+                name = element.split('_')[0]
+                type = element.split('_')[1]
+                paramId = self._get_cmd_param_index(self.layers[-1]['cmd'], name)
+                if paramId == -1:
+                    self.layers[-1]['cmd'].append('%s=%s' % (name, type))
+                else:
+                    self.layers[-1]['cmd'][paramId] += ',%s' % type
+
+        elif element in ('color',
+                         'fcolor',
+                         'lcolor') and self.inVector:
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('%s=%s' % (element,
+                                                         self._color_name_to_rgb(value)))
+
+        elif element == 'rdmcolor' and self.inVector:
+            if int(self._get_value(line)) == 1:
+                self.layers[-1]['cmd'].append('-c')
+
+        elif element == 'sqlcolor' and self.inVector:
+            if int(self._get_value(line)) == 1:
+                self.layers[-1]['cmd'].append('-a')
+
+        elif element in ('icon',
+                         'size',
+                         'layer',
+                         'xref',
+                         'yref',
+                         'lsize',
+                         'where',
+                         'minreg',
+                         'maxreg') and self.inVector:
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('%s=%s' % (element,
+                                                         value))
+        
+        elif element == 'lwidth':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('width=%s' % value)
+
+        elif element == 'lfield':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('llayer=%s' % value)
+                                        
+        elif element == 'attribute':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('attrcol=%s' % value)
+
+        elif element == 'cat':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('cats=%s' % value)
+
+        # gridline
+        elif element == 'gridline':
+            self.inGridline = True
+            self.layers.append({
+                    "type"    : 'grid',
+                    "name"    : self._get_value(line),
+                    "checked" : None,
+                    "opacity" : None,
+                    "cmd"     : ['d.grid'],
+                    "group"   : self.inGroup,
+                    "display" : 0})
+
+        elif element == 'gridcolor':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('color=%s' % self._color_name_to_rgb(value))
+
+        elif element == 'gridborder':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('bordercolor=%s' % self._color_name_to_rgb(value))
+
+        elif element == 'textcolor':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('textcolor=%s' % self._color_name_to_rgb(value))
+
+        elif element in ('gridsize',
+                         'gridorigin'):
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('%s=%s' % (element[4:], value))
+
+        elif element in 'fontsize':
+            value = self._get_value(line)
+            if value != '':
+                self.layers[-1]['cmd'].append('%s=%s' % (element, value))
+        
+        elif element == 'griddraw':
+            value = self._get_value(line)
+            if value == '0':
+                self.layers[-1]['cmd'].append('-n')
+                
+        elif element == 'gridgeo':
+            value = self._get_value(line)
+            if value == '1':
+                self.layers[-1]['cmd'].append('-g')
+        
+        elif element == 'borderdraw':
+            value = self._get_value(line)
+            if value == '0':
+                self.layers[-1]['cmd'].append('-b')
+
+        elif element == 'textdraw':
+            value = self._get_value(line)
+            if value == '0':
+                self.layers[-1]['cmd'].append('-t')
+        
+        else:
+            self.error += _(' row %d:') % line_id + line + os.linesep
+            self.num_error += 1
+
+    def _get_value(self, line):
+        """!Get value of element"""
+        try:
+            return line.strip(' ').split(' ')[1].strip(' ')
+        except:
+            return ''
+
+    def _get_element(self, line):
+        """!Get element tag"""
+        return line.strip(' ').split(' ')[0].strip(' ')
+
+    def _get_cmd_param_index(self, cmd, name):
+        """!Get index of parameter in cmd list
+
+        @param cmd cmd list
+        @param name parameter name
+
+        @return index
+        @return -1 if not found
+        """
+        i = 0
+        for param in cmd:
+            if '=' not in param:
+                i += 1
+                continue
+            if param.split('=')[0] == name:
+                return i
+
+            i += 1
+
+        return -1
+
+    def _color_name_to_rgb(self, value):
+        """!Convert color name (#) to rgb values"""
+        col = wx.NamedColour(value)
+        return str(col.Red()) + ':' + \
+            str(col.Green()) + ':' + \
+            str(col.Blue())

Added: grass/trunk/gui/wxpython/create__init__.py
===================================================================
--- grass/trunk/gui/wxpython/create__init__.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/create__init__.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import glob
+
+def main(path):
+    if not os.path.exists(path) or not os.path.isdir(path):
+        print >> sys.stderr, "'%s' is not a directory" % path
+        return 1
+    
+    modules = []
+    for f in glob.glob(os.path.join(os.path.basename(path), '*.py')):
+        if f[-5:-3] == '__':
+            continue
+        modules.append(os.path.splitext(os.path.basename(f))[0])
+        
+    fd = open(os.path.join(path, '__init__.py'), 'w')
+    try:
+        fd.write('all = [%s' % os.linesep)
+        for m in modules:
+            fd.write("    '%s',%s" % (m, os.linesep))
+        fd.write('    ]%s' % os.linesep)
+    finally:
+        fd.close()
+    
+    return 0
+
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        sys.exit("usage: %s path/to/gui_modules" % sys.argv[0])
+    
+    sys.exit(main(sys.argv[1]))


Property changes on: grass/trunk/gui/wxpython/create__init__.py
___________________________________________________________________
Added: svn:executable
   + *
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Copied: grass/trunk/gui/wxpython/dbm/dialogs.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/dbm_dialogs.py)
===================================================================
--- grass/trunk/gui/wxpython/dbm/dialogs.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/dbm/dialogs.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,668 @@
+"""!
+ at package dbm.dialogs
+
+ at brief DBM-related dialogs
+
+List of classes:
+ - DisplayAttributesDialog
+ - ModifyTableRecord
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+
+from core import globalvar
+import wx
+import wx.lib.scrolledpanel as scrolled
+
+from core.gcmd     import RunCommand
+from core.debug    import Debug
+from core.settings import UserSettings
+from dbm.vinfo     import VectorDBInfo
+
+class DisplayAttributesDialog(wx.Dialog):
+    def __init__(self, parent, map,
+                 query = None, cats = None, line = None,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
+                 pos = wx.DefaultPosition,
+                 action = "add", ignoreError = False):
+        """!Standard dialog used to add/update/display attributes linked
+        to the vector map.
+        
+        Attribute data can be selected based on layer and category number
+        or coordinates.
+        
+        @param parent
+        @param map vector map
+        @param query query coordinates and distance (used for v.edit)
+        @param cats {layer: cats}
+        @param line feature id (requested for cats)
+        @param style
+        @param pos
+        @param action (add, update, display)
+        @param ignoreError True to ignore errors
+        """
+        self.parent = parent # mapdisplay.BufferedWindow
+        self.map    = map
+        self.action = action
+
+        # ids/cats of selected features
+        # fid : {layer : cats}
+        self.cats = {}
+        self.fid = -1 # feature id
+        
+        # get layer/table/column information
+        self.mapDBInfo = VectorDBInfo(self.map)
+        
+        layers = self.mapDBInfo.layers.keys() # get available layers
+
+        # check if db connection / layer exists
+        if len(layers) <= 0:
+            if not ignoreError:
+                dlg = wx.MessageDialog(parent = self.parent,
+                                       message = _("No attribute table found.\n\n"
+                                                   "Do you want to create a new attribute table "
+                                                   "and defined a link to vector map <%s>?") % self.map,
+                                       caption = _("Create table?"),
+                                       style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
+                if dlg.ShowModal() == wx.ID_YES:
+                    lmgr = self.parent.lmgr
+                    lmgr.OnShowAttributeTable(event = None, selection = 'layers')
+                
+                dlg.Destroy()
+            
+            self.mapDBInfo = None
+        
+        wx.Dialog.__init__(self, parent = self.parent, id = wx.ID_ANY,
+                           title = "", style = style, pos = pos)
+
+        # dialog body
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+        # notebook
+        self.notebook = wx.Notebook(parent = self, id = wx.ID_ANY, style = wx.BK_DEFAULT)
+
+        self.closeDialog = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                       label = _("Close dialog on submit"))
+        self.closeDialog.SetValue(True)
+        if self.action == 'display':
+            self.closeDialog.Enable(False)
+        
+        # feature id (text/choice for duplicates)
+        self.fidMulti = wx.Choice(parent = self, id = wx.ID_ANY,
+                                  size = (150, -1))
+        self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature)
+        self.fidText = wx.StaticText(parent = self, id = wx.ID_ANY)
+
+        self.noFoundMsg = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                        label = _("No attributes found"))
+        
+        self.UpdateDialog(query = query, cats = cats)
+
+        # set title
+        if self.action == "update":
+            self.SetTitle(_("Update attributes"))
+        elif self.action == "add":
+            self.SetTitle(_("Define attributes"))
+        else:
+            self.SetTitle(_("Display attributes"))
+
+        # buttons
+        btnCancel = wx.Button(self, wx.ID_CANCEL)
+        btnReset  = wx.Button(self, wx.ID_UNDO, _("&Reload"))
+        btnSubmit = wx.Button(self, wx.ID_OK, _("&Submit"))
+        if self.action == 'display':
+            btnSubmit.Enable(False)
+        
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(btnCancel)
+        btnSizer.AddButton(btnReset)
+        btnSizer.SetNegativeButton(btnReset)
+        btnSubmit.SetDefault()
+        btnSizer.AddButton(btnSubmit)
+        btnSizer.Realize()
+
+        mainSizer.Add(item = self.noFoundMsg, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        mainSizer.Add(item = self.notebook, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        fidSizer = wx.BoxSizer(wx.HORIZONTAL)
+        fidSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                        label = _("Feature id:")),
+                     proportion = 0, border = 5,
+                     flag = wx.ALIGN_CENTER_VERTICAL)
+        fidSizer.Add(item = self.fidMulti, proportion = 0,
+                     flag = wx.EXPAND | wx.ALL,  border = 5)
+        fidSizer.Add(item = self.fidText, proportion = 0,
+                     flag = wx.EXPAND | wx.ALL,  border = 5)
+        mainSizer.Add(item = fidSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
+        mainSizer.Add(item = self.closeDialog, proportion = 0, flag = wx.EXPAND | wx.LEFT | wx.RIGHT,
+                      border = 5)
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+
+        # bindigs
+        btnReset.Bind(wx.EVT_BUTTON, self.OnReset)
+        btnSubmit.Bind(wx.EVT_BUTTON, self.OnSubmit)
+        btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+        # set min size for dialog
+        w, h = self.GetBestSize()
+        if h < 200:
+            self.SetMinSize((w, 200))
+        else:
+            self.SetMinSize(self.GetBestSize())
+
+        if self.notebook.GetPageCount() == 0:
+            Debug.msg(2, "DisplayAttributesDialog(): Nothing found!")
+            ### self.mapDBInfo = None
+        
+    def OnSQLStatement(self, event):
+        """!Update SQL statement"""
+        pass
+
+    def IsFound(self):
+        """!Check for status
+
+        @return True on attributes found
+        @return False attributes not found
+        """
+        return bool(self.mapDBInfo and self.notebook.GetPageCount() > 0)
+    
+    def GetSQLString(self, updateValues = False):
+        """!Create SQL statement string based on self.sqlStatement
+
+        If updateValues is True, update dataFrame according to values
+        in textfields.
+        """
+        sqlCommands = []
+        # find updated values for each layer/category
+        for layer in self.mapDBInfo.layers.keys(): # for each layer
+            table = self.mapDBInfo.GetTable(layer)
+            key = self.mapDBInfo.GetKeyColumn(layer)
+            columns = self.mapDBInfo.GetTableDesc(table)
+            for idx in range(len(columns[key]['values'])): # for each category
+                updatedColumns = []
+                updatedValues = []
+                for name in columns.keys():
+                    if name == key:
+                        cat = columns[name]['values'][idx]
+                        continue
+                    type  = columns[name]['type']
+                    value = columns[name]['values'][idx]
+                    id    = columns[name]['ids'][idx]
+                    try:
+                        newvalue = self.FindWindowById(id).GetValue()
+                    except:
+                        newvalue = self.FindWindowById(id).GetLabel()
+
+                    if newvalue == '':
+                        newvalue = None
+                    
+                    if newvalue != value:
+                        updatedColumns.append(name)
+                        if newvalue is None:
+                            updatedValues.append('NULL')
+                        else:
+                            if type != 'character':
+                                updatedValues.append(newvalue)
+                            else:
+                                updatedValues.append("'" + newvalue + "'")
+                        columns[name]['values'][idx] = newvalue
+
+                if self.action != "add" and len(updatedValues) == 0:
+                    continue
+
+                if self.action == "add":
+                    sqlString = "INSERT INTO %s (%s," % (table, key)
+                else:
+                    sqlString = "UPDATE %s SET " % table
+
+                for idx in range(len(updatedColumns)):
+                    name = updatedColumns[idx]
+                    if self.action == "add":
+                        sqlString += name + ","
+                    else:
+                        sqlString += name + "=" + updatedValues[idx] + ","
+
+                sqlString = sqlString[:-1] # remove last comma
+
+                if self.action == "add":
+                    sqlString += ") VALUES (%s," % cat
+                    for value in updatedValues:
+                        sqlString += str(value) + ","
+                    sqlString = sqlString[:-1] # remove last comma
+                    sqlString += ")"
+                else:
+                    sqlString += " WHERE %s=%s" % (key, cat)
+                sqlCommands.append(sqlString)
+            # for each category
+        # for each layer END
+
+        Debug.msg(3, "DisplayAttributesDialog.GetSQLString(): %s" % sqlCommands)
+
+        return sqlCommands
+
+    def OnReset(self, event = None):
+        """!Reset form"""
+        for layer in self.mapDBInfo.layers.keys():
+            table = self.mapDBInfo.layers[layer]["table"]
+            key = self.mapDBInfo.layers[layer]["key"]
+            columns = self.mapDBInfo.tables[table]
+            for idx in range(len(columns[key]['values'])):
+                for name in columns.keys():
+                    type  = columns[name]['type']
+                    value = columns[name]['values'][idx]
+                    if value is None:
+                        value = ''
+                    try:
+                        id = columns[name]['ids'][idx]
+                    except IndexError:
+                        id = wx.NOT_FOUND
+                    
+                    if name != key and id != wx.NOT_FOUND:
+                        self.FindWindowById(id).SetValue(str(value))
+
+    def OnCancel(self, event):
+        """!Cancel button pressed
+        """
+        self.parent.parent.dialogs['attributes'] = None
+        
+        if hasattr(self, "digit"):
+            self.parent.digit.GetDisplay().SetSelected([])
+            self.parent.UpdateMap(render = False)
+        else:
+            self.parent.parent.OnRender(None)
+        
+        self.Close()
+
+    def OnSubmit(self, event):
+        """!Submit records"""
+        layer = 1
+        for sql in self.GetSQLString(updateValues = True):
+            enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
+            if not enc and 'GRASS_DB_ENCODING' in os.environ:
+                enc = os.environ['GRASS_DB_ENCODING']
+            if enc:
+                sql = sql.encode(enc)
+
+            driver, database = self.mapDBInfo.GetDbSettings(layer)
+            Debug.msg(1, "SQL: %s" % sql)
+            RunCommand('db.execute',
+                       parent = self,
+                       quiet = True,
+                       input = '-',
+                       stdin = sql,
+                       driver = driver,
+                       database = database)
+            
+            layer += 1
+        
+        if self.closeDialog.IsChecked():
+            self.OnCancel(event)
+
+    def OnFeature(self, event):
+        self.fid = int(event.GetString())
+        self.UpdateDialog(cats = self.cats, fid = self.fid)
+        
+    def GetCats(self):
+        """!Get id of selected vector object or 'None' if nothing selected
+
+        @param id if true return ids otherwise cats
+        """
+        if self.fid < 0:
+            return None
+        
+        return self.cats[self.fid]
+
+    def GetFid(self):
+        """!Get selected feature id"""
+        return self.fid
+    
+    def UpdateDialog(self, map = None, query = None, cats = None, fid = -1,
+                     action = None):
+        """!Update dialog
+        
+        @param map name of vector map
+        @param query
+        @param cats
+        @param fid feature id
+        @param action add, update, display or None
+        
+        @return True if updated
+        @return False
+        """
+        if action:
+            self.action = action
+            if action == 'display':
+                enabled = False
+            else:
+                enabled = True
+            self.closeDialog.Enable(enabled)
+            self.FindWindowById(wx.ID_OK).Enable(enabled)
+        
+        if map:
+            self.map = map
+            # get layer/table/column information
+            self.mapDBInfo = VectorDBInfo(self.map)
+        
+        if not self.mapDBInfo:
+            return False
+        
+        self.mapDBInfo.Reset()
+        
+        layers = self.mapDBInfo.layers.keys() # get available layers
+        
+        # id of selected line
+        if query: # select by position
+            data = self.mapDBInfo.SelectByPoint(query[0],
+                                                query[1])
+            self.cats = {}
+            if data and 'Layer' in data:
+                idx = 0
+                for layer in data['Layer']:
+                    layer = int(layer)
+                    if 'Id' in data:
+                        tfid = int(data['Id'][idx])
+                    else:
+                        tfid = 0 # Area / Volume
+                    if not tfid in self.cats:
+                        self.cats[tfid] = {}
+                    if not layer in self.cats[tfid]:
+                        self.cats[tfid][layer] = []
+                    cat = int(data['Category'][idx])
+                    self.cats[tfid][layer].append(cat)
+                    idx += 1
+        else:
+            self.cats = cats
+        
+        if fid > 0:
+            self.fid = fid
+        elif len(self.cats.keys()) > 0:
+            self.fid = self.cats.keys()[0]
+        else:
+            self.fid = -1
+        
+        if len(self.cats.keys()) == 1:
+            self.fidMulti.Show(False)
+            self.fidText.Show(True)
+            if self.fid > 0:
+                self.fidText.SetLabel("%d" % self.fid)
+            else:
+                self.fidText.SetLabel(_("Unknown"))
+        else:
+            self.fidMulti.Show(True)
+            self.fidText.Show(False)
+            choices = []
+            for tfid in self.cats.keys():
+                choices.append(str(tfid))
+            self.fidMulti.SetItems(choices)
+            self.fidMulti.SetStringSelection(str(self.fid))
+        
+        # reset notebook
+        self.notebook.DeleteAllPages()
+        
+        for layer in layers: # for each layer
+            if not query: # select by layer/cat
+                if self.fid > 0 and layer in self.cats[self.fid]:
+                    for cat in self.cats[self.fid][layer]:
+                        nselected = self.mapDBInfo.SelectFromTable(layer,
+                                                                   where = "%s=%d" % \
+                                                                   (self.mapDBInfo.layers[layer]['key'],
+                                                                    cat))
+                else:
+                    nselected = 0
+            
+            # if nselected <= 0 and self.action != "add":
+            #    continue # nothing selected ...
+            
+            if self.action == "add":
+                if nselected <= 0:
+                    if layer in self.cats[self.fid]:
+                        table = self.mapDBInfo.layers[layer]["table"]
+                        key = self.mapDBInfo.layers[layer]["key"]
+                        columns = self.mapDBInfo.tables[table]
+                        for name in columns.keys():
+                            if name == key:
+                                for cat in self.cats[self.fid][layer]:
+                                    self.mapDBInfo.tables[table][name]['values'].append(cat)
+                            else:
+                                self.mapDBInfo.tables[table][name]['values'].append(None)
+                else: # change status 'add' -> 'update'
+                    self.action = "update"
+            
+            table   = self.mapDBInfo.layers[layer]["table"]
+            key   = self.mapDBInfo.layers[layer]["key"]
+            columns = self.mapDBInfo.tables[table]
+            
+            for idx in range(len(columns[key]['values'])):
+                for name in columns.keys():
+                    if name == key:
+                        cat = int(columns[name]['values'][idx])
+                        break
+
+                # use scrolled panel instead (and fix initial max height of the window to 480px)
+                panel = scrolled.ScrolledPanel(parent = self.notebook, id = wx.ID_ANY,
+                                               size = (-1, 150))
+                panel.SetupScrolling(scroll_x = False)
+                
+                self.notebook.AddPage(page = panel, text = " %s %d / %s %d" % (_("Layer"), layer,
+                                                                           _("Category"), cat))
+                
+                # notebook body
+                border = wx.BoxSizer(wx.VERTICAL)
+                
+                flexSizer = wx.FlexGridSizer (cols = 4, hgap = 3, vgap = 3)
+                flexSizer.AddGrowableCol(3)
+                # columns (sorted by index)
+                names = [''] * len(columns.keys())
+                for name in columns.keys():
+                    names[columns[name]['index']] = name
+                
+                for name in names:
+                    if name == key: # skip key column (category)
+                        continue
+                    
+                    vtype  = columns[name]['type']
+                    
+                    if columns[name]['values'][idx] is not None:
+                        if columns[name]['ctype'] != type(''):
+                            value = str(columns[name]['values'][idx])
+                        else:
+                            value = columns[name]['values'][idx]
+                    else:
+                        value = ''
+                    
+                    colName = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                            label = name)
+                    colType = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                            label = "[" + vtype.lower() + "]")
+                    delimiter = wx.StaticText(parent = panel, id = wx.ID_ANY, label = ":")
+                    
+                    colValue = wx.TextCtrl(parent = panel, id = wx.ID_ANY, value = value)
+                    colValue.SetName(name)
+                    self.Bind(wx.EVT_TEXT, self.OnSQLStatement, colValue)
+                    if self.action == 'display':
+                        colValue.SetWindowStyle(wx.TE_READONLY)
+                    
+                    flexSizer.Add(colName, proportion = 0,
+                                  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
+                    flexSizer.Add(colType, proportion = 0,
+                                  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
+                    flexSizer.Add(delimiter, proportion = 0,
+                                  flag = wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
+                    flexSizer.Add(colValue, proportion = 1,
+                                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
+                    # add widget reference to self.columns
+                    columns[name]['ids'].append(colValue.GetId()) # name, type, values, id
+                # for each attribute (including category) END
+                border.Add(item = flexSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+                panel.SetSizer(border)
+            # for each category END
+        # for each layer END
+        
+        if self.notebook.GetPageCount() == 0:
+            self.noFoundMsg.Show(True)
+        else:
+            self.noFoundMsg.Show(False)
+        
+        self.Layout()
+        
+        return True
+
+    def SetColumnValue(self, layer, column, value):
+        """!Set attrbute value
+
+        @param column column name
+        @param value value
+        """
+        table = self.mapDBInfo.GetTable(layer)
+        columns = self.mapDBInfo.GetTableDesc(table)
+        
+        for key, col in columns.iteritems():
+            if key == column:
+                col['values'] = [col['ctype'](value),]
+                break
+        
+class ModifyTableRecord(wx.Dialog):
+    def __init__(self, parent, title, data, keyEditable = (-1, True),
+                 id = wx.ID_ANY, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
+        """!Dialog for inserting/updating table record
+        
+        @param data a list: [(column, value)]
+        @param KeyEditable (id, editable?) indicates if textarea for key column
+        is editable(True) or not
+        """
+        # parent -> VDigitWindow
+        wx.Dialog.__init__(self, parent, id, title, style = style)
+        
+        self.CenterOnParent()
+        
+        self.keyId = keyEditable[0]
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        box = wx.StaticBox(parent = self.panel, id = wx.ID_ANY)
+        box.Hide()
+        self.dataPanel = scrolled.ScrolledPanel(parent = self.panel, id = wx.ID_ANY,
+                                                style = wx.TAB_TRAVERSAL)
+        self.dataPanel.SetupScrolling(scroll_x = False)
+        
+        # buttons
+        self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
+        self.btnSubmit = wx.Button(self.panel, wx.ID_OK, _("&Submit"))
+        self.btnSubmit.SetDefault()
+        
+        # data area
+        self.widgets = []
+        cId = 0
+        self.usebox = False
+        self.cat = None
+        winFocus = False
+        for column, value in data:
+            if self.keyId == cId:
+                self.cat = int(value)
+                if not keyEditable[1]:
+                    self.usebox = True
+                    box.SetLabel(" %s %d " % (_("Category"), self.cat))
+                    box.Show()
+                    self.boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+                    cId += 1
+                    continue
+                else:
+                    valueWin = wx.SpinCtrl(parent = self.dataPanel, id = wx.ID_ANY,
+                                           value = value, min = -1e9, max = 1e9, size = (250, -1))
+            else:
+                valueWin = wx.TextCtrl(parent = self.dataPanel, id = wx.ID_ANY,
+                                       value = value, size = (250, -1))
+                if not winFocus:
+                    wx.CallAfter(valueWin.SetFocus)
+                    winFocus = True
+            
+            label = wx.StaticText(parent = self.dataPanel, id = wx.ID_ANY,
+                                  label = column + ":")
+            
+            self.widgets.append((label.GetId(), valueWin.GetId()))
+            
+            cId += 1
+        
+        self._layout()
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # data area
+        dataSizer = wx.FlexGridSizer (cols = 2, hgap = 3, vgap = 3)
+        dataSizer.AddGrowableCol(1)
+        
+        for labelId, valueId in self.widgets:
+            label = self.FindWindowById(labelId)
+            value = self.FindWindowById(valueId)
+            
+            dataSizer.Add(label, proportion = 0,
+                          flag = wx.ALIGN_CENTER_VERTICAL)
+            dataSizer.Add(value, proportion = 0,
+                          flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
+        
+        self.dataPanel.SetAutoLayout(True)
+        self.dataPanel.SetSizer(dataSizer)
+        dataSizer.Fit(self.dataPanel)
+        
+        if self.usebox:
+            self.boxSizer.Add(item = self.dataPanel, proportion = 1,
+                              flag = wx.EXPAND | wx.ALL, border = 5)
+            
+        # buttons
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnSubmit)
+        btnSizer.Realize()
+        
+        if not self.usebox:
+            sizer.Add(item = self.dataPanel, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        else:
+            sizer.Add(item = self.boxSizer, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALL, border = 5)
+        
+        framewidth = self.GetSize()[0]
+        self.SetMinSize((framewidth,150))
+        self.SetMaxSize((framewidth,300))
+        
+        # sizer.SetSizeHints(self.panel)
+        self.panel.SetAutoLayout(True)
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self.panel)
+        
+        self.Layout()
+        
+    def GetValues(self, columns = None):
+        """!Return list of values (casted to string).
+        
+        If columns is given (list), return only values of given columns.
+        """
+        valueList = []
+        for labelId, valueId in self.widgets:
+            column = self.FindWindowById(labelId).GetLabel().replace(':', '')
+            if columns is None or column in columns:
+                value = str(self.FindWindowById(valueId).GetValue())
+                valueList.append(value)
+        
+        # add key value
+        if self.usebox:
+            valueList.insert(self.keyId, str(self.cat))
+                             
+        return valueList

Copied: grass/trunk/gui/wxpython/dbm/manager.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/dbm.py)
===================================================================
--- grass/trunk/gui/wxpython/dbm/manager.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/dbm/manager.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,3092 @@
+"""!
+ at package dbm.manager
+
+ at brief GRASS Attribute Table Manager
+
+This program is based on FileHunter, published in 'The wxPython Linux
+Tutorial' on wxPython WIKI pages.
+
+It also uses some functions at
+http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/426407
+
+ at code
+python dbm.py vector at mapset
+ at endcode
+
+List of classes:
+ - Log
+ - VirtualAttributeList
+ - AttributeManager
+ - TableListCtrl
+ - LayerListCtrl
+ - LayerBook
+
+(C) 2007-2009, 2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Jachym Cepicky <jachym.cepicky gmail.com>
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import sys
+import os
+import locale
+import tempfile
+import copy
+import types
+
+from core import globalvar
+import wx
+import wx.lib.mixins.listctrl as listmix
+import wx.lib.flatnotebook    as FN
+
+import grass.script as grass
+
+from dbm.sqlbuilder   import SQLFrame
+from core.gcmd        import RunCommand, GException, GError
+from core.utils       import ListOfCatsToRange
+from gui_core.dialogs import CreateNewVector
+from dbm.vinfo        import VectorDBInfo, unicodeValue, createDbInfoDesc
+from core.debug       import Debug
+from dbm.dialogs      import ModifyTableRecord
+from core.settings    import UserSettings
+from gui_core.widgets import GNotebook
+
+class Log:
+    """
+    The log output is redirected to the status bar of the containing frame.
+    """
+    def __init__(self, parent):
+        self.parent = parent
+
+    def write(self, text_string):
+        """!Update status bar"""
+        self.parent.SetStatusText(text_string.strip())
+
+
+class VirtualAttributeList(wx.ListCtrl,
+                           listmix.ListCtrlAutoWidthMixin,
+                           listmix.ColumnSorterMixin):
+    """
+    Support virtual list class
+    """
+    def __init__(self, parent, log, mapDBInfo, layer):
+        #
+        # initialize variables
+        #
+        self.parent  = parent
+        self.log     = log
+        self.mapDBInfo = mapDBInfo
+        self.layer   = layer
+        
+        self.columns = {} # <- LoadData()
+
+        wx.ListCtrl.__init__(self, parent=parent, id=wx.ID_ANY,
+                             style=wx.LC_REPORT | wx.LC_HRULES |
+                             wx.LC_VRULES | wx.LC_VIRTUAL | wx.LC_SORT_ASCENDING)
+        
+        try:
+            keyColumn = self.LoadData(layer)
+        except GException, e:
+            GError(parent = self,
+                   message = e.value)
+            return
+        
+        #
+        # add some attributes (colourful background for each item rows)
+        #
+        self.attr1 = wx.ListItemAttr()
+        self.attr1.SetBackgroundColour(wx.Colour(238,238,238))
+        self.attr2 = wx.ListItemAttr()
+        self.attr2.SetBackgroundColour("white")
+        self.il = wx.ImageList(16, 16)
+        self.sm_up = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP,   wx.ART_TOOLBAR,
+                                                          (16,16)))
+        self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN, wx.ART_TOOLBAR,
+                                                          (16,16)))
+        self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
+        
+        # setup mixins
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        listmix.ColumnSorterMixin.__init__(self, len(self.columns))
+
+        # sort item by category (id)
+        if keyColumn > -1:
+            self.SortListItems(col=keyColumn, ascending=True) 
+        else:
+            self.SortListItems(col=0, ascending=True) 
+        
+        # events
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED,   self.OnItemSelected)
+        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
+        self.Bind(wx.EVT_LIST_COL_CLICK,       self.OnColumnSort)     
+        self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColumnMenu)     
+        
+    def Update(self, mapDBInfo):
+        """!Update list according new mapDBInfo description"""
+        self.mapDBInfo = mapDBInfo
+        self.LoadData(self.layer)
+
+    def LoadData(self, layer, columns=None, where=None, sql=None):
+        """!Load data into list
+
+        @param layer layer number
+        @param columns list of columns for output (-> v.db.select)
+        @param where where statement (-> v.db.select)
+        @param sql full sql statement (-> db.select)
+        
+        @return id of key column 
+        @return -1 if key column is not displayed
+        """
+        self.log.write(_("Loading data..."))
+        
+        tableName    = self.mapDBInfo.layers[layer]['table']
+        keyColumn    = self.mapDBInfo.layers[layer]['key']
+        try:
+            self.columns = self.mapDBInfo.tables[tableName]
+        except KeyError:
+            raise GException(_("Attribute table <%s> not found. "
+                                    "For creating the table switch to "
+                                    "'Manage layers' tab.") % tableName)
+        
+        if not columns:
+            columns = self.mapDBInfo.GetColumns(tableName)
+        else:
+            all = self.mapDBInfo.GetColumns(tableName)
+            for col in columns:
+                if col not in all:
+                    wx.MessageBox(parent=self,
+                                  message=_("Column <%(column)s> not found in "
+                                            "in the table <%(table)s>.") % \
+                                      { 'column' : col, 'table' : tableName },
+                                  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                    return
+        
+        try:
+            # for maps connected via v.external
+            keyId = columns.index(keyColumn)
+        except:
+            keyId = -1
+        
+        #
+        # read data
+        #
+        # FIXME: Max. number of rows, while the GUI is still usable
+
+        # stdout can be very large, do not use PIPE, redirect to temp file
+        # TODO: more effective way should be implemented...
+        outFile = tempfile.NamedTemporaryFile(mode='w+b')
+        
+        if sql:
+            ret = RunCommand('db.select',
+                             quiet = True,
+                             parent = self,
+                             flags = 'c',
+                             sql = sql,
+                             output = outFile.name)
+        else:
+            if columns:
+                ret = RunCommand('v.db.select',
+                                 quiet = True,
+                                 parent = self,
+                                 flags = 'c',
+                                 map = self.mapDBInfo.map,
+                                 layer = layer,
+                                 columns = ','.join(columns),
+                                 where = where,
+                                 stdout = outFile)
+            else:
+                ret = RunCommand('v.db.select',
+                                 quiet = True,
+                                 parent = self,
+                                 flags = 'c',
+                                 map = self.mapDBInfo.map,
+                                 layer = layer,
+                                 where = where,
+                                 stdout = outFile) 
+        
+        # These two should probably be passed to init more cleanly
+        # setting the numbers of items = number of elements in the dictionary
+        self.itemDataMap  = {}
+        self.itemIndexMap = []
+        self.itemCatsMap  = {}
+        
+        self.DeleteAllItems()
+        
+        # self.ClearAll()
+        for i in range(self.GetColumnCount()):
+            self.DeleteColumn(0)
+        
+        i = 0
+        info = wx.ListItem()
+        info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
+        info.m_image = -1
+        info.m_format = 0
+        for column in columns:
+            info.m_text = column
+            self.InsertColumnInfo(i, info)
+            i += 1
+            
+            if i >= 256:
+                self.log.write(_("Can display only 256 columns."))
+        
+        i = 0
+        outFile.seek(0)
+        
+        while True:
+            # os.linesep doesn't work here (MSYS)
+            record = outFile.readline().replace('\n', '')
+            
+            if not record:
+                break
+           
+            self.AddDataRow(i, record, columns, keyId)
+
+            i += 1
+            if i >= 100000:
+                self.log.write(_("Limit 100000 records."))
+                break
+        
+        self.SetItemCount(i)
+        
+        i = 0
+        for col in columns:
+            width = self.columns[col]['length'] * 6 # FIXME
+            if width < 60:
+                width = 60
+            if width > 300:
+                width = 300
+            self.SetColumnWidth(col=i, width=width)
+            i += 1
+        
+        self.SendSizeEvent()
+        
+        self.log.write(_("Number of loaded records: %d") % \
+                           self.GetItemCount())
+        
+        return keyId
+    
+    def AddDataRow(self, i, record, columns, keyId):
+        """!Add row to the data list"""
+        self.itemDataMap[i] = []
+        keyColumn = self.mapDBInfo.layers[self.layer]['key']
+        j = 0
+        cat = None
+        
+        if keyColumn == 'OGC_FID':
+            self.itemDataMap[i].append(i+1)
+            j += 1
+            cat = i + 1
+        
+        for value in record.split('|'):
+            if self.columns[columns[j]]['ctype'] != types.StringType:
+                try:
+                    ### casting disabled (2009/03)
+                    ### self.itemDataMap[i].append(self.columns[columns[j]]['ctype'](value))
+                    self.itemDataMap[i].append(value)
+                except ValueError:
+                    self.itemDataMap[i].append(_('Unknown value'))
+            else:
+                # encode string values
+                try:
+                    self.itemDataMap[i].append(unicodeValue(value))
+                except UnicodeDecodeError:
+                    self.itemDataMap[i].append(_("Unable to decode value. "
+                                                 "Set encoding in GUI preferences ('Attributes')."))
+                
+            if not cat and keyId > -1 and keyId == j:
+                try:
+                    cat = self.columns[columns[j]]['ctype'] (value)
+                except ValueError, e:
+                    cat = -1
+                    gGError(parent = self,
+                            message=_("Error loading attribute data. "
+                                      "Record number: %(rec)d. Unable to convert value '%(val)s' in "
+                                      "key column (%(key)s) to integer.\n\n"
+                                      "Details: %(detail)s") % \
+                                { 'rec' : i + 1, 'val' : value,
+                                  'key' : keyColumn, 'detail' : e})
+            j += 1
+        
+        self.itemIndexMap.append(i)
+        if keyId > -1: # load cats only when LoadData() is called first time
+            self.itemCatsMap[i] = cat
+        
+    def OnItemSelected(self, event):
+        """!Item selected. Add item to selected cats..."""
+        #         cat = int(self.GetItemText(event.m_itemIndex))
+        #         if cat not in self.selectedCats:
+        #             self.selectedCats.append(cat)
+        #             self.selectedCats.sort()
+        
+        event.Skip()
+
+    def OnItemDeselected(self, event):
+        """!Item deselected. Remove item from selected cats..."""
+        #         cat = int(self.GetItemText(event.m_itemIndex))
+        #         if cat in self.selectedCats:
+        #             self.selectedCats.remove(cat)
+        #             self.selectedCats.sort()
+
+        event.Skip()
+
+    def GetSelectedItems(self):
+        """!Return list of selected items (category numbers)"""
+        cats = []
+        item = self.GetFirstSelected()
+        while item != -1:
+            cats.append(self.GetItemText(item))
+            item = self.GetNextSelected(item)
+
+        return cats
+
+    def GetColumnText(self, index, col):
+        """!Return column text"""
+        item = self.GetItem(index, col)
+        return item.GetText()
+
+    def GetListCtrl(self):
+        """!Returt list"""
+        return self
+
+    def OnGetItemText(self, item, col):
+        """!Get item text"""
+        index = self.itemIndexMap[item]
+        s = self.itemDataMap[index][col]
+        return s
+
+    def OnGetItemAttr(self, item):
+        """!Get item attributes"""
+        if ( item % 2) == 0:
+            return self.attr2
+        else:
+            return self.attr1
+
+    def OnColumnMenu(self, event):
+        """!Column heading right mouse button -> pop-up menu"""
+        self._col = event.GetColumn()
+        
+        popupMenu = wx.Menu()
+
+        if not hasattr (self, "popupID1"):
+            self.popupID1 = wx.NewId()
+            self.popupID2 = wx.NewId()
+            self.popupID3 = wx.NewId()
+            self.popupID4 = wx.NewId()
+            self.popupID5 = wx.NewId()
+            self.popupID6 = wx.NewId()
+            self.popupID7 = wx.NewId()
+            self.popupID8 = wx.NewId()
+            self.popupID9 = wx.NewId()
+            self.popupID10 = wx.NewId()
+            self.popupID11 = wx.NewId()
+            self.popupID12 = wx.NewId()
+        
+        popupMenu.Append(self.popupID1, text=_("Sort ascending"))
+        popupMenu.Append(self.popupID2, text=_("Sort descending"))
+        popupMenu.AppendSeparator()
+        subMenu = wx.Menu()
+        popupMenu.AppendMenu(self.popupID3, _("Calculate (only numeric columns)"),
+                             subMenu)
+        if not self.log.parent.editable or \
+                self.columns[self.GetColumn(self._col).GetText()]['ctype'] not in (types.IntType, types.FloatType):
+            popupMenu.Enable(self.popupID3, False)
+        
+        subMenu.Append(self.popupID4,  text=_("Area size"))
+        subMenu.Append(self.popupID5,  text=_("Line length"))
+        subMenu.Append(self.popupID6,  text=_("Compactness of an area"))
+        subMenu.Append(self.popupID7,  text=_("Fractal dimension of boundary defining a polygon"))
+        subMenu.Append(self.popupID8,  text=_("Perimeter length of an area"))
+        subMenu.Append(self.popupID9,  text=_("Number of features for each category"))
+        subMenu.Append(self.popupID10, text=_("Slope steepness of 3D line"))
+        subMenu.Append(self.popupID11, text=_("Line sinuousity"))
+        subMenu.Append(self.popupID12, text=_("Line azimuth"))
+        
+        self.Bind (wx.EVT_MENU, self.OnColumnSortAsc,  id=self.popupID1)
+        self.Bind (wx.EVT_MENU, self.OnColumnSortDesc, id=self.popupID2)
+        for id in (self.popupID4, self.popupID5, self.popupID6,
+                   self.popupID7, self.popupID8, self.popupID9,
+                   self.popupID10, self.popupID11, self.popupID12):
+            self.Bind(wx.EVT_MENU, self.OnColumnCompute, id = id)
+        
+        self.PopupMenu(popupMenu)
+        popupMenu.Destroy()
+
+    def OnColumnSort(self, event):
+        """!Column heading left mouse button -> sorting"""
+        self._col = event.GetColumn()
+        
+        self.ColumnSort()
+        
+        event.Skip()
+
+    def OnColumnSortAsc(self, event):
+        """!Sort values of selected column (ascending)"""
+        self.SortListItems(col = self._col, ascending = True)
+        event.Skip()
+
+    def OnColumnSortDesc(self, event):
+        """!Sort values of selected column (descending)"""
+        self.SortListItems(col = self._col, ascending = False)
+        event.Skip()
+        
+    def OnColumnCompute(self, event):
+        """!Compute values of selected column"""
+        id = event.GetId()
+        
+        option = None
+        if id == self.popupID4:
+            option = 'area'
+        elif id == self.popupID5:
+            option = 'length'
+        elif id == self.popupID6:
+            option = 'compact'
+        elif id == self.popupID7:
+            option = 'fd'
+        elif id == self.popupID8:
+            option = 'perimeter'
+        elif id == self.popupID9:
+            option = 'count'
+        elif id == self.popupID10:
+            option = 'slope'
+        elif id == self.popupID11:
+            option = 'sinuous'
+        elif id == self.popupID12:
+            option = 'azimuth'
+        
+        if not option:
+            return
+        
+        RunCommand('v.to.db',
+                   parent = self.parent,
+                   map = self.mapDBInfo.map,
+                   layer = self.layer, 
+                   option = option,
+                   columns = self.GetColumn(self._col).GetText())
+        
+        self.LoadData(self.layer)
+        
+    def ColumnSort(self):
+        """!Sort values of selected column (self._col)"""
+        # remove duplicated arrow symbol from column header
+        # FIXME: should be done automatically
+        info = wx.ListItem()
+        info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE
+        info.m_image = -1
+        for column in range(self.GetColumnCount()):
+            info.m_text = self.GetColumn(column).GetText()
+            self.SetColumn(column, info)
+        
+    def SortItems(self, sorter=cmp):
+        """!Sort items"""
+        items = list(self.itemDataMap.keys())
+        items.sort(self.Sorter)
+        self.itemIndexMap = items
+
+        # redraw the list
+        self.Refresh()
+        
+    def Sorter(self, key1, key2):
+        colName = self.GetColumn(self._col).GetText()
+        ascending = self._colSortFlag[self._col]
+        try:
+            item1 = self.columns[colName]["ctype"](self.itemDataMap[key1][self._col])
+            item2 = self.columns[colName]["ctype"](self.itemDataMap[key2][self._col])
+        except ValueError:
+            item1 = self.itemDataMap[key1][self._col]
+            item2 = self.itemDataMap[key2][self._col]
+
+        if type(item1) == type('') or type(item2) == type(''):
+            cmpVal = locale.strcoll(str(item1), str(item2))
+        else:
+            cmpVal = cmp(item1, item2)
+
+
+        # If the items are equal then pick something else to make the sort value unique
+        if cmpVal == 0:
+            cmpVal = apply(cmp, self.GetSecondarySortValues(self._col, key1, key2))
+        
+        if ascending:
+            return cmpVal
+        else:
+            return -cmpVal
+
+    def GetSortImages(self):
+        """!Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py"""
+        return (self.sm_dn, self.sm_up)
+
+    def IsEmpty(self):
+        """!Check if list if empty"""
+        if self.columns:
+            return False
+        
+        return True
+    
+class AttributeManager(wx.Frame):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 title = None, vectorName = None, item = None, log = None,
+                 selection = None, **kwargs):
+        """!GRASS Attribute Table Manager window
+
+        @param parent parent window
+        @parem id window id
+        @param title window title or None for default title
+        @param vetorName name of vector map
+        @param item item from Layer Tree
+        @param log log window
+        @param selection name of page to be selected
+        @param kwagrs other wx.Frame's arguments
+        """
+        self.vectorName = vectorName
+        self.parent     = parent # GMFrame
+        self.treeItem   = item   # item in layer tree
+        if self.parent and self.parent.GetName() == "LayerManager" and \
+                self.treeItem and not self.vectorName:
+            maptree = self.parent.curr_page.maptree
+            name = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetName()
+            self.vectorName = name
+        
+        # vector attributes can be changed only if vector map is in
+        # the current mapset
+        if grass.find_file(name = self.vectorName, element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
+            self.editable = True
+        else:
+            self.editable = False
+        
+        self.cmdLog = log    # self.parent.goutput
+        
+        wx.Frame.__init__(self, parent, id, *kwargs)
+
+        # title
+        if not title:
+            self.SetTitle("%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
+                                         self.vectorName))
+        else:
+            self.SetTitle(title)
+        
+        # icon
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO))
+
+        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
+
+        try:
+            self.map        = self.parent.curr_page.maptree.Map
+            self.mapdisplay = self.parent.curr_page.maptree.mapdisplay
+        except:
+            self.map = self.mapdisplay = None
+        
+        # status bar log class
+        self.log = Log(self) # -> statusbar
+
+        # query map layer (if parent (GMFrame) is given)
+        self.qlayer = None
+
+        # -> layers / tables description
+        self.mapDBInfo = VectorDBInfo(self.vectorName)
+
+        # sqlbuilder
+        self.builder = None
+        
+        if len(self.mapDBInfo.layers.keys()) == 0:
+            wx.MessageBox(parent=self.parent,
+                          message=_("Database connection for vector map <%s> "
+                                    "is not defined in DB file. "
+                                    "You can define new connection in "
+                                    "'Manage layers' tab.") % self.vectorName,
+                          caption=_("Attribute Table Manager"),
+                          style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
+
+        #
+        # list of command/SQL statements to be performed
+        #
+        self.listOfCommands      = []
+        self.listOfSQLStatements = []
+
+        self.CreateStatusBar(number=1)
+
+        # set up virtual lists (each layer)
+        ### {layer: list, widgets...}
+        self.layerPage = {}
+
+        self.notebook = GNotebook(self.panel, style = globalvar.FNPageDStyle)
+        
+        if globalvar.hasAgw:
+            dbmStyle = { 'agwStyle' : globalvar.FNPageStyle }
+        else:
+            dbmStyle = { 'style' : globalvar.FNPageStyle }
+        
+        self.browsePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
+                                          **dbmStyle)
+        self.notebook.AddPage(page = self.browsePage, text = _("Browse data"),
+                              name = 'browse')
+        self.browsePage.SetTabAreaColour(globalvar.FNPageColor)
+
+        self.manageTablePage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
+                                               **dbmStyle)
+        self.notebook.AddPage(page = self.manageTablePage, text = _("Manage tables"),
+                              name = 'table')
+        if not self.editable:
+            self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
+        self.manageTablePage.SetTabAreaColour(globalvar.FNPageColor)
+
+        self.manageLayerPage = FN.FlatNotebook(self.panel, id = wx.ID_ANY,
+                                               **dbmStyle)
+        self.notebook.AddPage(page = self.manageLayerPage, text = _("Manage layers"),
+                              name = 'layers')
+        self.manageLayerPage.SetTabAreaColour(globalvar.FNPageColor)
+        if not self.editable:
+            self.notebook.GetPage(self.notebook.GetPageCount()-1).Enable(False)
+        
+        self._createBrowsePage()
+        self._createManageTablePage()
+        self._createManageLayerPage()
+
+        if selection:
+            wx.CallAfter(self.notebook.SetSelectionByName, selection) # select browse tab
+
+        # buttons
+        self.btnQuit   = wx.Button(parent=self.panel, id=wx.ID_EXIT)
+        self.btnQuit.SetToolTipString(_("Close Attribute Table Manager"))
+        self.btnReload = wx.Button(parent=self.panel, id=wx.ID_REFRESH)
+        self.btnReload.SetToolTipString(_("Reload attribute data (selected layer only)"))
+
+        # events
+        self.btnQuit.Bind(wx.EVT_BUTTON,   self.OnCloseWindow)
+        self.btnReload.Bind(wx.EVT_BUTTON, self.OnDataReload)
+        self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
+        self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.browsePage)
+        self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnLayerPageChanged, self.manageTablePage)
+        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+
+        # do layout
+        self._layout()
+
+        # self.SetMinSize(self.GetBestSize())
+        self.SetSize((680, 550)) # FIXME hard-coded size
+        self.SetMinSize(self.GetSize())
+
+    def _createBrowsePage(self, onlyLayer=-1):
+        """!Create browse tab page"""
+        for layer in self.mapDBInfo.layers.keys():
+            if onlyLayer > 0 and layer != onlyLayer:
+                continue
+
+            panel = wx.Panel(parent=self.browsePage, id=wx.ID_ANY)
+            
+            #IMPORTANT NOTE: wx.StaticBox MUST be defined BEFORE any of the 
+            #   controls that are placed IN the wx.StaticBox, or it will freeze
+            #   on the Mac
+            
+            listBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                       label=" %s " % _("Attribute data - right-click to edit/manage records"))
+            listSizer = wx.StaticBoxSizer(listBox, wx.VERTICAL)
+            
+            win = VirtualAttributeList(panel, self.log,
+                                       self.mapDBInfo, layer)
+            if win.IsEmpty():
+                del panel
+                continue
+            
+            win.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnDataItemActivated)
+
+            self.layerPage[layer] = {'browsePage': panel.GetId()}
+            
+            label = _("Table")
+            if not self.editable:
+                label += _(" (readonly)")
+            self.browsePage.AddPage(page=panel, text=" %d / %s %s" % \
+                                        (layer, label, self.mapDBInfo.layers[layer]['table']))
+
+            pageSizer = wx.BoxSizer(wx.VERTICAL)
+
+            # attribute data            
+            sqlBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                                  label=" %s " % _("SQL Query"))
+
+            sqlSizer = wx.StaticBoxSizer(sqlBox, wx.VERTICAL)
+
+            win.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnDataRightUp) #wxMSW
+            win.Bind(wx.EVT_RIGHT_UP,            self.OnDataRightUp) #wxGTK
+            if UserSettings.Get(group='atm', key='leftDbClick', subkey='selection') == 0:
+                win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataItemEdit)
+                win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataItemEdit)
+            else:
+                win.Bind(wx.EVT_LEFT_DCLICK, self.OnDataDrawSelected)
+                win.Bind(wx.EVT_COMMAND_LEFT_DCLICK, self.OnDataDrawSelected)
+                
+            
+            listSizer.Add(item=win, proportion=1,
+                          flag=wx.EXPAND | wx.ALL,
+                          border=3)
+
+            # sql statement box
+            btnApply = wx.Button(parent=panel, id=wx.ID_APPLY)
+            btnApply.SetToolTipString(_("Apply SELECT statement and reload data records"))
+            btnApply.Bind(wx.EVT_BUTTON, self.OnApplySqlStatement)
+            btnSqlBuilder = wx.Button(parent=panel, id=wx.ID_ANY, label=_("SQL Builder"))
+            btnSqlBuilder.Bind(wx.EVT_BUTTON, self.OnBuilder)
+
+            sqlSimple = wx.RadioButton(parent=panel, id=wx.ID_ANY,
+                                       label=_("Simple"))
+            sqlSimple.SetValue(True)
+            sqlAdvanced = wx.RadioButton(parent=panel, id=wx.ID_ANY,
+                                         label=_("Advanced"))
+            sqlSimple.Bind(wx.EVT_RADIOBUTTON,   self.OnChangeSql)
+            sqlAdvanced.Bind(wx.EVT_RADIOBUTTON, self.OnChangeSql)
+
+            sqlWhereColumn = wx.Choice(parent=panel, id=wx.ID_ANY,
+                                       size=(100,-1),
+                                       choices=self.mapDBInfo.GetColumns(self.mapDBInfo.layers[layer]['table']))
+            sqlWhereValue = wx.TextCtrl(parent=panel, id=wx.ID_ANY, value="",
+                                        style=wx.TE_PROCESS_ENTER)
+            sqlWhereValue.SetToolTipString(_("Example: %s") % "MULTILANE = 'no' AND OBJECTID < 10")
+
+            sqlStatement = wx.TextCtrl(parent=panel, id=wx.ID_ANY,
+                                       value="SELECT * FROM %s" % \
+                                           self.mapDBInfo.layers[layer]['table'],
+                                       style=wx.TE_PROCESS_ENTER)
+            sqlStatement.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' AND OBJECTID < 10")
+            sqlWhereValue.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
+            sqlStatement.Bind(wx.EVT_TEXT_ENTER, self.OnApplySqlStatement)
+
+            sqlLabel = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                     label="SELECT * FROM %s WHERE " % \
+                                         self.mapDBInfo.layers[layer]['table'])
+            label_query = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                        label="")
+
+            sqlFlexSizer = wx.FlexGridSizer (cols=3, hgap=5, vgap=5)
+            sqlFlexSizer.AddGrowableCol(1)
+
+            sqlFlexSizer.Add(item=sqlSimple,
+                             flag=wx.ALIGN_CENTER_VERTICAL)
+            sqlSimpleSizer = wx.BoxSizer(wx.HORIZONTAL)
+            sqlSimpleSizer.Add(item=sqlLabel,
+                               flag=wx.ALIGN_CENTER_VERTICAL)
+            sqlSimpleSizer.Add(item=sqlWhereColumn,
+                               flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
+            sqlSimpleSizer.Add(item=sqlWhereValue, proportion=1,
+                               flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
+            sqlFlexSizer.Add(item=sqlSimpleSizer,
+                             flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+            sqlFlexSizer.Add(item=btnApply,
+                             flag=wx.ALIGN_RIGHT)
+            sqlFlexSizer.Add(item=sqlAdvanced,
+                             flag=wx.ALIGN_CENTER_VERTICAL)
+            sqlFlexSizer.Add(item=sqlStatement,
+                             flag=wx.EXPAND)
+            sqlFlexSizer.Add(item=btnSqlBuilder,
+                             flag=wx.ALIGN_RIGHT)
+
+            sqlSizer.Add(item=sqlFlexSizer,
+                         flag=wx.ALL | wx.EXPAND,
+                         border=3)
+
+            pageSizer.Add(item=listSizer,
+                          proportion=1,
+                          flag=wx.ALL | wx.EXPAND,
+                          border=5)
+
+            pageSizer.Add(item=sqlSizer,
+                          proportion=0,
+                          flag=wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.EXPAND,
+                          border=5)
+
+            panel.SetSizer(pageSizer)
+
+            self.layerPage[layer]['data']      = win.GetId()
+            self.layerPage[layer]['simple']    = sqlSimple.GetId()
+            self.layerPage[layer]['advanced']  = sqlAdvanced.GetId()
+            self.layerPage[layer]['whereColumn'] = sqlWhereColumn.GetId()
+            self.layerPage[layer]['where']     = sqlWhereValue.GetId()
+            self.layerPage[layer]['builder']   = btnSqlBuilder.GetId()
+            self.layerPage[layer]['statement'] = sqlStatement.GetId()
+
+
+        self.browsePage.SetSelection(0) # select first layer
+        try:
+            self.layer = self.mapDBInfo.layers.keys()[0]
+            self.OnChangeSql(None)
+            self.log.write(_("Number of loaded records: %d") % \
+                           self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
+        except (IndexError, KeyError):
+            self.layer = None
+        
+    def _createManageTablePage(self, onlyLayer=-1):
+        """!Create manage page (create/link and alter tables)"""
+        for layer in self.mapDBInfo.layers.keys():
+            if onlyLayer > 0 and layer != onlyLayer:
+                continue
+            
+            if not layer in self.layerPage:
+                continue
+            
+            panel = wx.Panel(parent=self.manageTablePage, id=wx.ID_ANY)
+            self.layerPage[layer]['tablePage'] = panel.GetId()
+            label = _("Table")
+            if not self.editable:
+                label += _(" (readonly)")
+            self.manageTablePage.AddPage(page=panel,
+                                         text=" %d / %s %s" % (layer, label,
+                                                               self.mapDBInfo.layers[layer]['table']))
+            
+            pageSizer = wx.BoxSizer(wx.VERTICAL)
+            
+            #
+            # dbInfo
+            #
+            dbBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                                          label=" %s " % _("Database connection"))
+            dbSizer = wx.StaticBoxSizer(dbBox, wx.VERTICAL)
+            dbSizer.Add(item=createDbInfoDesc(panel, self.mapDBInfo, layer),
+                        proportion=1,
+                        flag=wx.EXPAND | wx.ALL,
+                        border=3)
+            
+            #
+            # table description
+            #
+            table = self.mapDBInfo.layers[layer]['table']
+            tableBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                                    label=" %s " % _("Table <%s> - right-click to delete column(s)") % table)
+            
+            tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
+            
+            list = self._createTableDesc(panel, table)
+            list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnTableRightUp) #wxMSW
+            list.Bind(wx.EVT_RIGHT_UP,            self.OnTableRightUp) #wxGTK
+            self.layerPage[layer]['tableData'] = list.GetId()
+            
+            #
+            # add column
+            #
+            columnBox = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                                     label=" %s " % _("Manage columns"))
+            
+            columnSizer = wx.StaticBoxSizer(columnBox, wx.VERTICAL)
+            
+            addSizer = wx.FlexGridSizer (cols=5, hgap=3, vgap=3)
+            addSizer.AddGrowableCol(3)
+            
+            label  = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Column name"))
+            column = wx.TextCtrl(parent=panel, id=wx.ID_ANY, value='',
+                                 size=(150, -1), style=wx.TE_PROCESS_ENTER)
+            column.Bind(wx.EVT_TEXT,       self.OnTableAddColumnName)
+            column.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemAdd)
+            self.layerPage[layer]['addColName'] = column.GetId()
+            addSizer.Add(item=label,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            addSizer.Add(item=column,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            # data type
+            label  = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Data type"))
+            addSizer.Add(item=label,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            
+            subSizer = wx.BoxSizer(wx.HORIZONTAL)
+            type = wx.Choice (parent=panel, id=wx.ID_ANY,
+                              choices = ["integer",
+                                         "double",
+                                         "varchar",
+                                         "date"]) # FIXME
+            type.SetSelection(0)
+            type.Bind(wx.EVT_CHOICE, self.OnTableChangeType)
+            self.layerPage[layer]['addColType'] = type.GetId()
+            subSizer.Add(item=type,
+                         flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
+                         border=3)
+            # length
+            label  = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Data length"))
+            length = wx.SpinCtrl(parent=panel, id=wx.ID_ANY, size=(65, -1),
+                                 initial=250,
+                                 min=1, max=1e6)
+            length.Enable(False)
+            self.layerPage[layer]['addColLength'] = length.GetId()
+            subSizer.Add(item=label,
+                         flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
+                         border=3)
+            subSizer.Add(item=length,
+                         flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
+                         border=3)
+            
+            addSizer.Add(item=subSizer,
+                         flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
+                         border=3)
+            
+            btnAddCol = wx.Button(parent=panel, id=wx.ID_ANY, label=_("Add"))
+            btnAddCol.Bind(wx.EVT_BUTTON, self.OnTableItemAdd)
+            btnAddCol.Enable(False)
+            self.layerPage[layer]['addColButton'] = btnAddCol.GetId()
+            addSizer.Add(item=btnAddCol,
+                         proportion=0,
+                         flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE |
+                         wx.ALIGN_CENTER_VERTICAL )
+            
+            # rename col
+            label  = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Rename column"))
+            column = wx.ComboBox(parent=panel, id=wx.ID_ANY, size=(150, -1),
+                                 style=wx.CB_SIMPLE | wx.CB_READONLY,
+                                 choices=self.mapDBInfo.GetColumns(table))
+            column.SetSelection(0)
+            self.layerPage[layer]['renameCol'] = column.GetId()
+            addSizer.Add(item=label,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            addSizer.Add(item=column,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            label  = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("To"))
+            columnTo = wx.TextCtrl(parent=panel, id=wx.ID_ANY, value='',
+                                   size=(150, -1), style=wx.TE_PROCESS_ENTER)
+            columnTo.Bind(wx.EVT_TEXT,       self.OnTableRenameColumnName)
+            columnTo.Bind(wx.EVT_TEXT_ENTER, self.OnTableItemChange)
+            self.layerPage[layer]['renameColTo'] = columnTo.GetId()
+            addSizer.Add(item=label,
+                         flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER)
+            addSizer.Add(item=columnTo,
+                         flag=wx.ALIGN_CENTER_VERTICAL)
+            btnRenameCol = wx.Button(parent=panel, id=wx.ID_ANY, label=_("&Rename"))
+            btnRenameCol.Bind(wx.EVT_BUTTON, self.OnTableItemChange)
+            btnRenameCol.Enable(False)
+            self.layerPage[layer]['renameColButton'] = btnRenameCol.GetId()
+            
+            addSizer.Add(item=btnRenameCol,
+                         proportion=0,
+                         flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE |
+                         wx.ALIGN_CENTER_VERTICAL)
+
+            columnSizer.Add(item=addSizer, proportion=1,
+                            flag=wx.ALL | wx.EXPAND, border=3)
+            
+            tableSizer.Add(item=list,
+                           flag=wx.ALL | wx.EXPAND,
+                           proportion=1,
+                           border=3)
+            
+            pageSizer.Add(item=dbSizer,
+                          flag=wx.ALL | wx.EXPAND,
+                          proportion=0,
+                          border=3)
+            
+            pageSizer.Add(item=tableSizer,
+                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
+                          proportion=1,
+                          border=3)
+ 
+            pageSizer.Add(item=columnSizer,
+                          flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
+                          proportion=0,
+                          border=3)
+            
+            panel.SetSizer(pageSizer)
+        
+        self.manageTablePage.SetSelection(0) # select first layer
+        try:
+            self.layer = self.mapDBInfo.layers.keys()[0]
+        except IndexError:
+            self.layer = None
+        
+    def _createTableDesc(self, parent, table):
+        """!Create list with table description"""
+        list = TableListCtrl(parent=parent, id=wx.ID_ANY,
+                             table=self.mapDBInfo.tables[table],
+                             columns=self.mapDBInfo.GetColumns(table))
+        list.Populate()
+        # sorter
+        # itemDataMap = list.Populate()
+        # listmix.ColumnSorterMixin.__init__(self, 2)
+
+        return list
+
+    def _createManageLayerPage(self):
+        """!Create manage page"""
+        splitterWin = wx.SplitterWindow(parent=self.manageLayerPage, id=wx.ID_ANY)
+        splitterWin.SetMinimumPaneSize(100)
+        
+        label = _("Layers of vector map")
+        if not self.editable:
+            label += _(" (readonly)")
+        self.manageLayerPage.AddPage(page=splitterWin,
+                                     text=label) # dummy page
+        
+        #
+        # list of layers
+        #
+        panelList = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
+
+        panelListSizer  = wx.BoxSizer(wx.VERTICAL)
+        layerBox = wx.StaticBox(parent=panelList, id=wx.ID_ANY,
+                                label=" %s " % _("List of layers"))
+        layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
+
+        self.layerList = self._createLayerDesc(panelList)
+        self.layerList.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnLayerRightUp) #wxMSW
+        self.layerList.Bind(wx.EVT_RIGHT_UP,            self.OnLayerRightUp) #wxGTK
+        
+        layerSizer.Add(item=self.layerList,
+                       flag=wx.ALL | wx.EXPAND,
+                       proportion=1,
+                       border=3)
+
+        panelListSizer.Add(item=layerSizer,
+                           flag=wx.ALL | wx.EXPAND,
+                           proportion=1,
+                           border=3)
+
+        panelList.SetSizer(panelListSizer)
+
+        #
+        # manage part
+        #
+        panelManage = wx.Panel(parent=splitterWin, id=wx.ID_ANY)
+         
+        manageSizer = wx.BoxSizer(wx.VERTICAL)
+
+        self.manageLayerBook = LayerBook(parent=panelManage, id=wx.ID_ANY,
+                                         parentDialog=self)
+
+        manageSizer.Add(item=self.manageLayerBook,
+                        proportion=1,
+                        flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
+                        border=5)
+
+        panelManage.SetSizer(manageSizer)
+        splitterWin.SplitHorizontally(panelList, panelManage, 100) 
+        splitterWin.Fit()
+
+    def _createLayerDesc(self, parent):
+        """!Create list of linked layers"""
+        list = LayerListCtrl(parent=parent, id=wx.ID_ANY,
+                             layers=self.mapDBInfo.layers)
+        
+        list.Populate()
+        # sorter
+        # itemDataMap = list.Populate()
+        # listmix.ColumnSorterMixin.__init__(self, 2)
+
+        return list
+
+    def _layout(self):
+        """!Do layout"""
+        # frame body
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+        # buttons
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(item=self.btnReload, proportion=1,
+                     flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
+        btnSizer.Add(item=self.btnQuit, proportion=1,
+                     flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
+
+        mainSizer.Add(item=self.notebook, proportion=1, flag=wx.EXPAND)
+        mainSizer.Add(item=btnSizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
+
+        self.panel.SetAutoLayout(True)
+        self.panel.SetSizer(mainSizer)
+        mainSizer.Fit(self.panel)
+        self.Layout()
+        
+    def OnDataRightUp(self, event):
+        """!Table description area, context menu"""
+        if not hasattr(self, "popupDataID1"):
+            self.popupDataID1 = wx.NewId()
+            self.popupDataID2 = wx.NewId()
+            self.popupDataID3 = wx.NewId()
+            self.popupDataID4 = wx.NewId()
+            self.popupDataID5 = wx.NewId()
+            self.popupDataID6 = wx.NewId()
+            self.popupDataID7 = wx.NewId()
+            self.popupDataID8 = wx.NewId()
+            self.popupDataID9 = wx.NewId()
+            self.popupDataID10 = wx.NewId()
+            self.popupDataID11 = wx.NewId()
+
+            self.Bind(wx.EVT_MENU, self.OnDataItemEdit,       id=self.popupDataID1)
+            self.Bind(wx.EVT_MENU, self.OnDataItemAdd,        id=self.popupDataID2)
+            self.Bind(wx.EVT_MENU, self.OnDataItemDelete,     id=self.popupDataID3)
+            self.Bind(wx.EVT_MENU, self.OnDataItemDeleteAll,  id=self.popupDataID4)
+            self.Bind(wx.EVT_MENU, self.OnDataSelectAll,      id=self.popupDataID5)
+            self.Bind(wx.EVT_MENU, self.OnDataSelectNone,     id=self.popupDataID6)
+            self.Bind(wx.EVT_MENU, self.OnDataDrawSelected,   id=self.popupDataID7)
+            self.Bind(wx.EVT_MENU, self.OnDataDrawSelectedZoom, id=self.popupDataID8)
+            self.Bind(wx.EVT_MENU, self.OnExtractSelected,    id=self.popupDataID9)
+            self.Bind(wx.EVT_MENU, self.OnDeleteSelected,     id=self.popupDataID11)
+            self.Bind(wx.EVT_MENU, self.OnDataReload,         id=self.popupDataID10)
+
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupDataID1, _("Edit selected record"))
+        selected = list.GetFirstSelected()
+        if not self.editable or selected == -1 or list.GetNextSelected(selected) != -1:
+            menu.Enable(self.popupDataID1, False)
+        menu.Append(self.popupDataID2, _("Insert new record"))
+        menu.Append(self.popupDataID3, _("Delete selected record(s)"))
+        menu.Append(self.popupDataID4, _("Delete all records"))
+        if not self.editable:
+            menu.Enable(self.popupDataID2, False)
+            menu.Enable(self.popupDataID3, False)
+            menu.Enable(self.popupDataID4, False)
+        menu.AppendSeparator()
+        menu.Append(self.popupDataID5, _("Select all"))
+        menu.Append(self.popupDataID6, _("Deselect all"))
+        menu.AppendSeparator()
+        menu.Append(self.popupDataID7, _("Highlight selected features"))
+        menu.Append(self.popupDataID8, _("Highlight selected features and zoom"))
+        if not self.map or len(list.GetSelectedItems()) == 0:
+            menu.Enable(self.popupDataID7, False)
+            menu.Enable(self.popupDataID8, False)
+        menu.Append(self.popupDataID9, _("Extract selected features"))
+        menu.Append(self.popupDataID11, _("Delete selected features"))
+        if not self.editable:
+            menu.Enable(self.popupDataID11, False)
+        if list.GetFirstSelected() == -1:
+            menu.Enable(self.popupDataID3, False)
+            menu.Enable(self.popupDataID9, False)
+            menu.Enable(self.popupDataID11, False)
+        menu.AppendSeparator()
+        menu.Append(self.popupDataID10, _("Reload"))
+
+        self.PopupMenu(menu)
+        menu.Destroy()
+
+        # update statusbar
+        self.log.write(_("Number of loaded records: %d") % \
+                           list.GetItemCount())
+
+    def OnDataItemDelete(self, event):
+        """!Delete selected item(s) from the list (layer/category pair)"""
+        dlist = self.FindWindowById(self.layerPage[self.layer]['data'])
+        item = dlist.GetFirstSelected()
+        
+        table    = self.mapDBInfo.layers[self.layer]["table"]
+        key      = self.mapDBInfo.layers[self.layer]["key"]
+        
+        indeces = []
+        # collect SQL statements
+        while item != -1:
+            index = dlist.itemIndexMap[item]
+            indeces.append(index)
+            
+            cat = dlist.itemCatsMap[index]
+            
+            self.listOfSQLStatements.append('DELETE FROM %s WHERE %s=%d' % \
+                                                (table, key, cat))
+            
+            item = dlist.GetNextSelected(item)
+        
+        if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
+            deleteDialog = wx.MessageBox(parent=self,
+                                         message=_("Selected data records (%d) will permanently deleted "
+                                                   "from table. Do you want to delete them?") % \
+                                             (len(self.listOfSQLStatements)),
+                                         caption=_("Delete records"),
+                                         style=wx.YES_NO | wx.CENTRE)
+            if deleteDialog != wx.YES:
+                self.listOfSQLStatements = []
+                return False
+        
+        # restore maps
+        i = 0
+        indexTemp = copy.copy(dlist.itemIndexMap)
+        dlist.itemIndexMap = []
+        dataTemp = copy.deepcopy(dlist.itemDataMap)
+        dlist.itemDataMap = {}
+        catsTemp = copy.deepcopy(dlist.itemCatsMap)
+        dlist.itemCatsMap = {}
+        
+        i = 0
+        for index in indexTemp:
+            if index in indeces:
+                continue
+            dlist.itemIndexMap.append(i)
+            dlist.itemDataMap[i] = dataTemp[index]
+            dlist.itemCatsMap[i] = catsTemp[index]
+            
+            i += 1
+            
+        dlist.SetItemCount(len(dlist.itemIndexMap))
+        
+        # deselect items
+        item = dlist.GetFirstSelected()
+        while item != -1:
+            dlist.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
+            item = dlist.GetNextSelected(item)
+        
+        # submit SQL statements
+        self.ApplyCommands()
+        
+        return True
+
+    def OnDataItemDeleteAll(self, event):
+        """!Delete all items from the list"""
+        dlist = self.FindWindowById(self.layerPage[self.layer]['data'])
+        if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
+            deleteDialog = wx.MessageBox(parent=self,
+                                         message=_("All data records (%d) will permanently deleted "
+                                                   "from table. Do you want to delete them?") % \
+                                             (len(dlist.itemIndexMap)),
+                                         caption=_("Delete records"),
+                                         style=wx.YES_NO | wx.CENTRE)
+            if deleteDialog != wx.YES:
+                return
+
+        dlist.DeleteAllItems()
+        dlist.itemDataMap  = {}
+        dlist.itemIndexMap = []
+        dlist.SetItemCount(0)
+
+        table = self.mapDBInfo.layers[self.layer]["table"]
+        self.listOfSQLStatements.append('DELETE FROM %s' % table)
+
+        self.ApplyCommands()
+        
+        event.Skip()
+
+    def _drawSelected(self, zoom):
+        """!Highlight selected features"""
+        if not self.map or not self.mapdisplay:
+            return
+        
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        cats = map(int, list.GetSelectedItems())
+
+        digitToolbar = None
+        if 'vdigit' in self.mapdisplay.toolbars:
+            digitToolbar = self.mapdisplay.toolbars['vdigit']
+        if digitToolbar and digitToolbar.GetLayer() and \
+                digitToolbar.GetLayer().GetName() == self.vectorName:
+
+            self.mapdisplay.digit.driver.SetSelected(cats, field=self.layer)
+            if zoom:
+                n, s, w, e = self.mapdisplay.digit.driver.GetRegionSelected()
+                self.mapdisplay.Map.GetRegion(n=n, s=s, w=w, e=e,
+                                              update=True)
+        else:
+            # add map layer with higlighted vector features
+            self.AddQueryMapLayer() # -> self.qlayer
+
+            # set opacity based on queried layer
+            if self.parent and self.parent.GetName() == "LayerManager" and \
+                    self.treeItem:
+                maptree = self.parent.curr_page.maptree
+                opacity = maptree.GetPyData(self.treeItem)[0]['maplayer'].GetOpacity(float=True)
+                self.qlayer.SetOpacity(opacity)
+            if zoom:
+                keyColumn = self.mapDBInfo.layers[self.layer]['key']
+                where = ''
+                for range in ListOfCatsToRange(cats).split(','):
+                    if '-' in range:
+                        min, max = range.split('-')
+                        where += '%s >= %d and %s <= %d or ' % \
+                            (keyColumn, int(min),
+                             keyColumn, int(max))
+                    else:
+                        where += '%s = %d or ' % (keyColumn, int(range))
+                where = where.rstrip('or ')
+                
+                select = RunCommand('v.db.select',
+                                    parent = self,
+                                    read = True,
+                                    quiet = True,
+                                    flags = 'r',
+                                    map = self.mapDBInfo.map,
+                                    layer = int(self.layer),
+                                    where = where)
+                
+                region = {}
+                for line in select.splitlines():
+                    key, value = line.split('=')
+                    region[key.strip()] = float(value.strip())
+                
+                self.mapdisplay.Map.GetRegion(n=region['n'], s=region['s'],
+                                              w=region['w'], e=region['e'],
+                                              update=True)
+        
+        if zoom:
+            self.mapdisplay.Map.AdjustRegion() # adjust resolution
+            self.mapdisplay.Map.AlignExtentFromDisplay() # adjust extent
+            self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
+        else:
+            self.mapdisplay.MapWindow.UpdateMap(render=False, renderVector=True)
+        
+    def OnDataDrawSelected(self, event):
+        """!Reload table description"""
+        self._drawSelected(zoom=False)
+        event.Skip()
+
+    def OnDataDrawSelectedZoom(self, event):
+        self._drawSelected(zoom=True)
+        event.Skip()
+        
+    def OnDataItemAdd(self, event):
+        """!Add new record to the attribute table"""
+        list      = self.FindWindowById(self.layerPage[self.layer]['data'])
+        table     = self.mapDBInfo.layers[self.layer]['table']
+        keyColumn = self.mapDBInfo.layers[self.layer]['key']
+        
+        # (column name, value)
+        data = []
+
+        # collect names of all visible columns
+        columnName = []
+        for i in range(list.GetColumnCount()): 
+            columnName.append(list.GetColumn(i).GetText())
+
+        # maximal category number
+        if len(list.itemCatsMap.values()) > 0:
+            maxCat = max(list.itemCatsMap.values())
+        else:
+            maxCat = 0 # starting category '1'
+        
+        # key column must be always presented
+        if keyColumn not in columnName:
+            columnName.insert(0, keyColumn) # insert key column on first position
+            data.append((keyColumn, str(maxCat + 1)))
+            missingKey = True
+        else:
+            missingKey = False
+            
+        # add other visible columns
+        colIdx = 0
+        keyId = -1
+        for col in columnName:
+            if col == keyColumn: # key 
+                if missingKey is False: 
+                    data.append((col, str(maxCat + 1)))
+                    keyId = colIdx
+            else:
+                data.append((col, ''))
+            colIdx += 1
+                
+        dlg = ModifyTableRecord(parent = self,
+                                title = _("Insert new record"),
+                                data = data, keyEditable = (keyId, True))
+
+        if dlg.ShowModal() == wx.ID_OK:
+            try: # get category number
+                cat = int(dlg.GetValues(columns=[keyColumn])[0])
+            except:
+                cat = -1
+
+            try:
+                if cat in list.itemCatsMap.values():
+                    raise ValueError(_("Record with category number %d "
+                                       "already exists in the table.") % cat)
+
+                values = dlg.GetValues() # values (need to be casted)
+                columnsString = ''
+                valuesString   = ''
+                
+                for i in range(len(values)):
+                    if len(values[i]) == 0: # NULL
+                        if columnName[i] == keyColumn:
+                            raise ValueError(_("Category number (column %s)"
+                                               " is missing.") % keyColumn)
+                        else:
+                            continue
+
+                    try:
+                        if list.columns[columnName[i]]['ctype'] == int:
+                            # values[i] is stored as text. 
+                            value = float(values[i])
+                        else:
+                            value = values[i]
+                        values[i] = list.columns[columnName[i]]['ctype'] (value)
+
+                    except:
+                        raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % 
+                                         {'value' : str(values[i]),
+                                          'type' : list.columns[columnName[i]]['type']})
+                    columnsString += '%s,' % columnName[i]
+                    if list.columns[columnName[i]]['ctype'] == str:
+                        valuesString += "'%s'," % values[i]
+                    else:
+                        valuesString += "%s," % values[i]
+
+            except ValueError, err:
+                wx.MessageBox(parent=self,
+                              message="%s%s%s" % (_("Unable to insert new record."),
+                                                    os.linesep, err),
+                              caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                return
+
+            # remove category if need 
+            if missingKey is True:
+                del values[0]
+                
+            # add new item to the list
+            if len(list.itemIndexMap) > 0:
+                index = max(list.itemIndexMap) + 1
+            else:
+                index = 0
+            
+            list.itemIndexMap.append(index)
+            list.itemDataMap[index] = values
+            list.itemCatsMap[index] = cat
+            list.SetItemCount(list.GetItemCount() + 1)
+
+            self.listOfSQLStatements.append('INSERT INTO %s (%s) VALUES(%s)' % \
+                                                (table,
+                                                 columnsString.strip(','),
+                                                 valuesString.strip(',')))
+            self.ApplyCommands()
+            
+    def OnDataItemEdit(self, event):
+        """!Edit selected record of the attribute table"""
+        list      = self.FindWindowById(self.layerPage[self.layer]['data'])
+        item      = list.GetFirstSelected()
+        if item == -1:
+            return
+
+        table     = self.mapDBInfo.layers[self.layer]['table']
+        keyColumn = self.mapDBInfo.layers[self.layer]['key']
+        cat       = list.itemCatsMap[list.itemIndexMap[item]]
+
+        # (column name, value)
+        data = []
+
+        # collect names of all visible columns
+        columnName = []
+        for i in range(list.GetColumnCount()): 
+            columnName.append(list.GetColumn(i).GetText())
+
+
+        # key column must be always presented
+        if keyColumn not in columnName:
+            columnName.insert(0, keyColumn) # insert key column on first position
+            data.append((keyColumn, str(cat)))
+            keyId = 0
+            missingKey = True
+        else:
+            missingKey = False
+            
+        # add other visible columns
+        for i in range(len(columnName)):
+            if columnName[i] == keyColumn: # key 
+                if missingKey is False: 
+                    data.append((columnName[i], str(cat)))
+                    keyId = i
+            else:
+                if missingKey is True:
+                    value = list.GetItem(item, i-1).GetText()
+                else:
+                    value = list.GetItem(item, i).GetText()
+                data.append((columnName[i], value))
+
+        dlg = ModifyTableRecord(parent = self, 
+                                title = _("Update existing record"),
+                                data = data, keyEditable = (keyId, False))
+
+        if dlg.ShowModal() == wx.ID_OK:
+            values = dlg.GetValues() # string
+            updateString = ''
+            try:
+                for i in range(len(values)): 
+                    if i == keyId: # skip key column
+                        continue
+                    if list.GetItem(item, i).GetText() != values[i]:
+                        if len(values[i]) > 0:
+                            try:
+                                if missingKey is True:
+                                    idx = i - 1
+                                else:
+                                    idx = i
+                                if list.columns[columnName[i]]['ctype'] != type(''):
+                                    if list.columns[columnName[i]]['ctype'] == int:
+                                        value = float(values[i])
+                                    else:
+                                        value = values[i]
+                                    list.itemDataMap[item][idx] = \
+                                        list.columns[columnName[i]]['ctype'] (value)
+                                else:
+                                    list.itemDataMap[item][idx] = values[i]
+                            except:
+                                raise ValueError(_("Value '%(value)s' needs to be entered as %(type)s.") % \
+                                                     {'value' : str(values[i]),
+                                                      'type' : list.columns[columnName[i]]['type']})
+
+                            if list.columns[columnName[i]]['ctype'] == str:
+                                updateString += "%s='%s'," % (columnName[i], values[i])
+                            else:
+                                updateString += "%s=%s," % (columnName[i], values[i])
+                        else: # NULL
+                            updateString += "%s=NULL," % (columnName[i])
+                            
+            except ValueError, err:
+                wx.MessageBox(parent=self,
+                              message="%s%s%s" % (_("Unable to update existing record."),
+                                                  os.linesep, err),
+                              caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                return
+            
+            if len(updateString) > 0:
+                self.listOfSQLStatements.append('UPDATE %s SET %s WHERE %s=%d' % \
+                                                    (table, updateString.strip(','),
+                                                     keyColumn, cat))
+                self.ApplyCommands()
+
+            list.Update(self.mapDBInfo)
+                        
+    def OnDataReload(self, event):
+        """!Reload list of records"""
+        self.OnApplySqlStatement(None)
+        self.listOfSQLStatements = []
+
+    def OnDataSelectAll(self, event):
+        """!Select all items"""
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        item = -1
+
+        while True:
+            item = list.GetNextItem(item)
+            if item == -1:
+                break
+            list.SetItemState(item, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
+
+        event.Skip()
+
+    def OnDataSelectNone(self, event):
+        """!Deselect items"""
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        item = -1
+
+        while True:
+            item = list.GetNextItem(item, wx.LIST_STATE_SELECTED)
+            if item == -1:
+                break
+            list.SetItemState(item, 0, wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED)
+
+        event.Skip()
+
+
+    def OnTableChangeType(self, event):
+        """!Data type for new column changed. Enable or disable
+        data length widget"""
+        win = self.FindWindowById(self.layerPage[self.layer]['addColLength'])
+        if event.GetString() == "varchar":
+            win.Enable(True)
+        else:
+            win.Enable(False)
+
+    def OnTableRenameColumnName(self, event):
+        """!Editing column name to be added to the table"""
+        btn  = self.FindWindowById(self.layerPage[self.layer]['renameColButton'])
+        col  = self.FindWindowById(self.layerPage[self.layer]['renameCol'])
+        colTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo'])
+        if len(col.GetValue()) > 0 and len(colTo.GetValue()) > 0:
+            btn.Enable(True)
+        else:
+            btn.Enable(False)
+
+        event.Skip()
+
+    def OnTableAddColumnName(self, event):
+        """!Editing column name to be added to the table"""
+        btn = self.FindWindowById(self.layerPage[self.layer]['addColButton'])
+        if len(event.GetString()) > 0:
+            btn.Enable(True)
+        else:
+            btn.Enable(False)
+
+        event.Skip()
+
+    def OnTableItemChange(self, event):
+        """!Rename column in the table"""
+        list   = self.FindWindowById(self.layerPage[self.layer]['tableData'])
+        name   = self.FindWindowById(self.layerPage[self.layer]['renameCol']).GetValue()
+        nameTo = self.FindWindowById(self.layerPage[self.layer]['renameColTo']).GetValue()
+
+        table = self.mapDBInfo.layers[self.layer]["table"]
+
+        if not name or not nameTo:
+            wx.MessageBox(self=self,
+                          message=_("Unable to rename column. "
+                                    "No column name defined."),
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            return
+        else:
+            item = list.FindItem(start=-1, str=name)
+            if item > -1:
+                if list.FindItem(start=-1, str=nameTo) > -1:
+                    wx.MessageBox(parent=self,
+                                  message=_("Unable to rename column <%(column)s> to "
+                                            "<%(columnTo)s>. Column already exists "
+                                            "in the table <%(table)s>.") % \
+                                      {'column' : name, 'columnTo' : nameTo,
+                                       'table' : table},
+                                  caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                    return
+                else:
+                    list.SetItemText(item, nameTo)
+
+                    self.listOfCommands.append(('v.db.renamecolumn',
+                                                { 'map'    : self.vectorName,
+                                                  'layer'  : self.layer,
+                                                  'column' : '%s,%s' % (name, nameTo) }
+                                                ))
+            else:
+                wx.MessageBox(parent=self,
+                              message=_("Unable to rename column. "
+                                        "Column <%(column)s> doesn't exist in the table <%(table)s>.") % 
+                              {'column' : name, 'table' : table},
+                              caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                return
+            
+        # apply changes
+        self.ApplyCommands()
+
+        # update widgets
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
+        self.FindWindowById(self.layerPage[self.layer]['renameColTo']).SetValue('')
+
+        event.Skip()
+
+    def OnTableRightUp(self, event):
+        """!Table description area, context menu"""
+        if not hasattr(self, "popupTableID"):
+            self.popupTableID1 = wx.NewId()
+            self.popupTableID2 = wx.NewId()
+            self.popupTableID3 = wx.NewId()
+            self.Bind(wx.EVT_MENU, self.OnTableItemDelete,    id=self.popupTableID1)
+            self.Bind(wx.EVT_MENU, self.OnTableItemDeleteAll, id=self.popupTableID2)
+            self.Bind(wx.EVT_MENU, self.OnTableReload,        id=self.popupTableID3)
+
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupTableID1, _("Drop selected column"))
+        if self.FindWindowById(self.layerPage[self.layer]['tableData']).GetFirstSelected() == -1:
+            menu.Enable(self.popupTableID1, False)
+        menu.Append(self.popupTableID2, _("Drop all columns"))
+        menu.AppendSeparator()
+        menu.Append(self.popupTableID3, _("Reload"))
+
+        self.PopupMenu(menu)
+        menu.Destroy()
+
+    def OnTableItemDelete(self, event):
+        """!Delete selected item(s) from the list"""
+        list = self.FindWindowById(self.layerPage[self.layer]['tableData'])
+        
+        item = list.GetFirstSelected()
+        
+        if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
+            deleteDialog = wx.MessageBox(parent=self,
+                                         message=_("Selected column '%s' will PERMANENTLY removed "
+                                                   "from table. Do you want to drop the column?") % \
+                                             (list.GetItemText(item)),
+                                         caption=_("Drop column(s)"),
+                                         style=wx.YES_NO | wx.CENTRE)
+            if deleteDialog != wx.YES:
+                return False
+        
+        while item != -1:
+            self.listOfCommands.append(('v.db.dropcolumn',
+                                        { 'map' : self.vectorName,
+                                          'layer' : self.layer,
+                                          'column' : list.GetItemText(item) }
+                                        ))
+            list.DeleteItem(item)
+            item = list.GetFirstSelected()
+        
+        # apply changes
+        self.ApplyCommands()
+        
+        # update widgets
+        table = self.mapDBInfo.layers[self.layer]['table']
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
+        
+        event.Skip()
+
+    def OnTableItemDeleteAll(self, event):
+        """!Delete all items from the list"""
+        table     = self.mapDBInfo.layers[self.layer]['table']
+        cols      = self.mapDBInfo.GetColumns(table)
+        keyColumn = self.mapDBInfo.layers[self.layer]['key']
+        if keyColumn in cols:
+            cols.remove(keyColumn)
+        
+        if UserSettings.Get(group='atm', key='askOnDeleteRec', subkey='enabled'):
+            deleteDialog = wx.MessageBox(parent=self,
+                                         message=_("Selected columns\n%s\nwill PERMANENTLY removed "
+                                                   "from table. Do you want to drop the columns?") % \
+                                             ('\n'.join(cols)),
+                                         caption=_("Drop column(s)"),
+                                         style=wx.YES_NO | wx.CENTRE)
+            if deleteDialog != wx.YES:
+                return False
+        
+        for col in cols:
+            self.listOfCommands.append(('v.db.dropcolumn',
+                                        { 'map' : self.vectorName,
+                                          'layer' : self.layer,
+                                          'column' : col }
+                                        ))
+        self.FindWindowById(self.layerPage[self.layer]['tableData']).DeleteAllItems()
+
+        # apply changes
+        self.ApplyCommands()
+
+        # update widgets
+        table = self.mapDBInfo.layers[self.layer]['table']
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
+
+        event.Skip()
+
+    def OnTableReload(self, event=None):
+        """!Reload table description"""
+        self.FindWindowById(self.layerPage[self.layer]['tableData']).Populate(update=True)
+        self.listOfCommands = []
+
+    def OnTableItemAdd(self, event):
+        """!Add new column to the table"""
+	table = self.mapDBInfo.layers[self.layer]['table']
+        name = self.FindWindowById(self.layerPage[self.layer]['addColName']).GetValue()
+        
+        if not name:
+            GError(parent = self,
+                   message = _("Unable to add column to the table. "
+                               "No column name defined."))
+            return
+        
+        ctype = self.FindWindowById(self.layerPage[self.layer]['addColType']). \
+            GetStringSelection()
+        
+        # cast type if needed
+        if ctype == 'double':
+            ctype = 'double precision'
+        if ctype == 'varchar':
+            length = int(self.FindWindowById(self.layerPage[self.layer]['addColLength']). \
+                             GetValue())
+        else:
+            length = '' # FIXME
+        
+        # add item to the list of table columns
+        tlist = self.FindWindowById(self.layerPage[self.layer]['tableData'])
+        # check for duplicate items
+        if tlist.FindItem(start=-1, str=name) > -1:
+            GError(parent = self,
+                   message = _("Column <%(column)s> already exists in table <%(table)s>.") % \
+                       {'column' : name, 'table' : self.mapDBInfo.layers[self.layer]["table"]}
+                   )
+            return
+        index = tlist.InsertStringItem(sys.maxint, str(name))
+        tlist.SetStringItem(index, 0, str(name))
+        tlist.SetStringItem(index, 1, str(ctype))
+        tlist.SetStringItem(index, 2, str(length))
+        
+        # add v.db.addcolumn command to the list
+        if ctype == 'varchar':
+            ctype += ' (%d)' % length
+        self.listOfCommands.append(('v.db.addcolumn',
+                                    { 'map'     : self.vectorName,
+                                      'layer'   : self.layer,
+                                      'columns' : '%s %s' % (name, ctype) }
+                                    ))
+        # apply changes
+        self.ApplyCommands()
+        
+        # update widgets
+        self.FindWindowById(self.layerPage[self.layer]['addColName']).SetValue('')
+	self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetItems(self.mapDBInfo.GetColumns(table))
+        self.FindWindowById(self.layerPage[self.layer]['renameCol']).SetSelection(0)
+        
+        event.Skip()
+        
+    def OnLayerPageChanged(self, event):
+        """!Layer tab changed"""
+        pageNum = event.GetSelection()
+        self.layer = self.mapDBInfo.layers.keys()[pageNum]
+        
+        try:
+            idCol = self.layerPage[self.layer]['whereColumn']
+        except KeyError:
+            idCol = None
+        
+        try:
+            self.OnChangeSql(None)
+            # update statusbar
+            self.log.write(_("Number of loaded records: %d") % \
+                               self.FindWindowById(self.layerPage[self.layer]['data']).\
+                               GetItemCount())
+        except:
+            pass
+        
+        if idCol:
+            winCol = self.FindWindowById(idCol)
+            table = self.mapDBInfo.layers[self.layer]["table"]
+            self.mapDBInfo.GetColumns(table)
+        
+        event.Skip()
+        
+    def OnPageChanged(self, event):
+        try:
+            id = self.layerPage[self.layer]['data']
+        except KeyError:
+            id = None
+        
+        if event.GetSelection() == 0 and id:
+            win = self.FindWindowById(id)
+            if win:
+                self.log.write(_("Number of loaded records: %d") % win.GetItemCount())
+            else:
+                self.log.write("")
+            self.btnReload.Enable()
+        else:
+            self.log.write("")
+            self.btnReload.Enable(False)
+        
+        event.Skip()
+        
+    def OnLayerRightUp(self, event):
+        """!Layer description area, context menu"""
+        pass
+
+    def OnChangeSql(self, event):
+        """!Switch simple/advanced sql statement"""
+        if self.FindWindowById(self.layerPage[self.layer]['simple']).GetValue():
+            self.FindWindowById(self.layerPage[self.layer]['where']).Enable(True)
+            self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(False)
+            self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(False)
+        else:
+            self.FindWindowById(self.layerPage[self.layer]['where']).Enable(False)
+            self.FindWindowById(self.layerPage[self.layer]['statement']).Enable(True)
+            self.FindWindowById(self.layerPage[self.layer]['builder']).Enable(True)
+
+    def ApplyCommands(self):
+        """!Apply changes"""
+        # perform GRASS commands (e.g. v.db.addcolumn)
+        wx.BeginBusyCursor()
+        
+        if len(self.listOfCommands) > 0:
+            for cmd in self.listOfCommands:
+                RunCommand(prog = cmd[0],
+                           quiet = True,
+                           parent = self,
+                           **cmd[1])
+            
+            self.mapDBInfo = VectorDBInfo(self.vectorName)
+            table = self.mapDBInfo.layers[self.layer]['table']
+
+            # update table description
+            list = self.FindWindowById(self.layerPage[self.layer]['tableData'])
+            list.Update(table=self.mapDBInfo.tables[table],
+                        columns=self.mapDBInfo.GetColumns(table))
+            self.OnTableReload(None)
+
+            # update data list
+            list = self.FindWindowById(self.layerPage[self.layer]['data'])
+            list.Update(self.mapDBInfo)
+
+            # reset list of commands
+            self.listOfCommands = []
+        
+        # perform SQL non-select statements (e.g. 'delete from table where cat=1')
+        if len(self.listOfSQLStatements) > 0:
+            sqlFile = tempfile.NamedTemporaryFile(mode="wt")
+            for sql in self.listOfSQLStatements:
+                enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
+                if not enc and 'GRASS_DB_ENCODING' in os.environ:
+                    enc = os.environ['GRASS_DB_ENCODING']
+                if enc:
+                    sqlFile.file.write(sql.encode(enc) + ';')
+                else:
+                    sqlFile.file.write(sql + ';')
+                sqlFile.file.write(os.linesep)
+                sqlFile.file.flush()
+
+            driver   = self.mapDBInfo.layers[self.layer]["driver"]
+            database = self.mapDBInfo.layers[self.layer]["database"]
+            
+            Debug.msg(3, 'AttributeManger.ApplyCommands(): %s' %
+                      ';'.join(["%s" % s for s in self.listOfSQLStatements]))
+            
+            RunCommand('db.execute',
+                       parent = self,
+                       input = sqlFile.name,
+                       driver = driver,
+                       database = database)
+            
+            # reset list of statements
+            self.listOfSQLStatements = []
+            
+        wx.EndBusyCursor()
+        
+    def OnApplySqlStatement(self, event):
+        """!Apply simple/advanced sql statement"""
+        keyColumn = -1 # index of key column
+        listWin = self.FindWindowById(self.layerPage[self.layer]['data'])
+        sql = None
+        
+        wx.BeginBusyCursor()
+        
+        if self.FindWindowById(self.layerPage[self.layer]['simple']).GetValue():
+            # simple sql statement
+            whereCol = self.FindWindowById(self.layerPage[self.layer]['whereColumn']).GetStringSelection()
+            whereVal = self.FindWindowById(self.layerPage[self.layer]['where']).GetValue().strip()
+            try:
+                if len(whereVal) > 0:
+                    keyColumn = listWin.LoadData(self.layer, where=whereCol + whereVal)
+                else:
+                    keyColumn = listWin.LoadData(self.layer)
+            except GException, e:
+                GError(parent = self,
+                       message = _("Loading attribute data failed.\n\n%s") % e.value)
+                self.FindWindowById(self.layerPage[self.layer]['where']).SetValue('')
+        else:
+            # advanced sql statement
+            win = self.FindWindowById(self.layerPage[self.layer]['statement'])
+            try:
+                cols, where = self.ValidateSelectStatement(win.GetValue())
+                if cols is None and where is None:
+                    sql = win.GetValue()
+            except TypeError:
+                wx.MessageBox(parent=self,
+                              message=_("Loading attribute data failed.\n"
+                                        "Invalid SQL select statement.\n\n%s") % win.GetValue(),
+                              caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+                win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
+                cols = None
+                where = None
+            
+            if cols or where or sql:
+                try:
+                    keyColumn = listWin.LoadData(self.layer, columns=cols,
+                                                 where=where, sql=sql)
+                except GException, e:
+                    GError(parent = self,
+                           message = _("Loading attribute data failed.\n\n%s") % e.value)
+                    win.SetValue("SELECT * FROM %s" % self.mapDBInfo.layers[self.layer]['table'])
+        
+        # sort by key column
+        if sql and 'order by' in sql.lower():
+            pass # don't order by key column
+        else:
+            if keyColumn > -1:
+                listWin.SortListItems(col=keyColumn, ascending=True)
+            else:
+                listWin.SortListItems(col=0, ascending=True) 
+        
+        wx.EndBusyCursor()
+        
+        # update statusbar
+        self.log.write(_("Number of loaded records: %d") % \
+                           self.FindWindowById(self.layerPage[self.layer]['data']).GetItemCount())
+
+    def ValidateSelectStatement(self, statement):
+        """!Validate SQL select statement
+
+        @return (columns, where)
+        @return None on error
+        """
+        if statement[0:7].lower() != 'select ':
+            return None
+        
+        cols = ''
+        index = 7
+        for c in statement[index:]:
+            if c == ' ':
+                break
+            cols += c
+            index += 1
+        if cols == '*':
+            cols = None
+        else:
+            cols = cols.split(',')
+        
+        tablelen = len(self.mapDBInfo.layers[self.layer]['table'])
+        
+        if statement[index+1:index+6].lower() != 'from ' or \
+                statement[index+6:index+6+tablelen] != '%s' % \
+                (self.mapDBInfo.layers[self.layer]['table']):
+            return None
+        
+        if len(statement[index+7+tablelen:]) > 0:
+            index = statement.lower().find('where ')
+            if index > -1:
+                where = statement[index+6:]
+            else:
+                where = None
+        else:
+            where = None
+        
+        return (cols, where)
+    
+    def OnCloseWindow(self, event):
+        """!Cancel button pressed"""
+        if self.parent and self.parent.GetName() == 'LayerManager':
+            # deregister ATM
+            self.parent.dialogs['atm'].remove(self)
+                    
+        if not isinstance(event, wx.CloseEvent):
+            self.Destroy()
+        
+        event.Skip()
+
+    def OnBuilder(self,event):
+        """!SQL Builder button pressed -> show the SQLBuilder dialog"""
+        if not self.builder:
+            self.builder = SQLFrame(parent = self, id = wx.ID_ANY,
+                                    title = _("SQL Builder"),
+                                    vectmap = self.vectorName,
+                                    evtheader = self.OnBuilderEvt)
+            self.builder.Show()
+        else:
+            self.builder.Raise()
+        
+    def OnBuilderEvt(self, event):
+        if event == 'apply':
+            sqlstr = self.builder.GetSQLStatement()
+            self.FindWindowById(self.layerPage[self.layer]['statement']).SetValue(sqlstr)
+            if self.builder.CloseOnApply():
+                self.builder = None
+        elif event == 'close':
+            self.builder = None
+        
+    def OnTextEnter(self, event):
+        pass
+    
+    def OnDataItemActivated(self, event):
+        """!Item activated, highlight selected item"""
+        self.OnDataDrawSelected(event)
+
+        event.Skip()
+
+    def OnExtractSelected(self, event):
+        """!Extract vector objects selected in attribute browse window
+        to new vector map
+        """
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        # cats = list.selectedCats[:]
+        cats = list.GetSelectedItems()
+        if len(cats) == 0:
+            wx.MessageBox(parent=self,
+                          message=_('Nothing to extract.'),
+                          caption=_('Message'), style=wx.CENTRE)
+            return
+        else:
+            # dialog to get file name
+            dlg = CreateNewVector(parent = self, title = _('Extract selected features'),
+                                  log = self.cmdLog,
+                                  cmd = (('v.extract',
+                                          { 'input' : self.vectorName,
+                                            'cats' : ListOfCatsToRange(cats) },
+                                          'output')),
+                                  disableTable = True)
+            if not dlg:
+                return
+            
+            name = dlg.GetName(full = True)
+            if name and dlg.IsChecked('add'):
+                # add layer to map layer tree
+                self.parent.curr_page.maptree.AddLayer(ltype = 'vector',
+                                                       lname = name,
+                                                       lcmd = ['d.vect', 'map=%s' % name])
+            dlg.Destroy()
+            
+    def OnDeleteSelected(self, event):
+        """!Delete vector objects selected in attribute browse window
+        (attribures and geometry)
+        """
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        cats = list.GetSelectedItems()
+        if len(cats) == 0:
+            wx.MessageBox(parent=self,
+                          message=_('Nothing to delete.'),
+                          caption=_('Message'), style=wx.CENTRE)
+        
+        if self.OnDataItemDelete(None):
+            digitToolbar = self.mapdisplay.toolbars['vdigit']
+            if digitToolbar and digitToolbar.GetLayer() and \
+                    digitToolbar.GetLayer().GetName() == self.vectorName:
+                self.mapdisplay.digit.driver.SetSelected(map(int, cats), field=self.layer)
+                self.mapdisplay.digit.DeleteSelectedLines()
+            else:
+                RunCommand('v.edit',
+                           parent = self,
+                           quiet = True,
+                           map = self.vectorName,
+                           tool = 'delete',
+                           cats = ListOfCatsToRange(cats))
+                
+            self.mapdisplay.MapWindow.UpdateMap(render=True, renderVector=True)
+        
+    def AddQueryMapLayer(self):
+        """!Redraw a map
+
+        Return True if map has been redrawn, False if no map is given
+        """
+        list = self.FindWindowById(self.layerPage[self.layer]['data'])
+        cats = { 
+            self.layer : list.GetSelectedItems()
+            }
+        
+        if self.mapdisplay.Map.GetLayerIndex(self.qlayer) < 0:
+            self.qlayer = None
+            
+        if self.qlayer:
+            self.qlayer.SetCmd(self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats, addLayer=False))
+        else:
+            self.qlayer = self.mapdisplay.AddTmpVectorMapLayer(self.vectorName, cats)
+
+        return self.qlayer
+    
+    def UpdateDialog(self, layer):
+        """!Updates dialog layout for given layer"""
+        # delete page
+        if layer in self.mapDBInfo.layers.keys():
+            # delete page
+            # draging pages disallowed
+            # if self.browsePage.GetPageText(page).replace('Layer ', '').strip() == str(layer):
+            # self.browsePage.DeletePage(page)
+            # break
+            self.browsePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
+            self.manageTablePage.DeletePage(self.mapDBInfo.layers.keys().index(layer))
+            # set current page selection
+            self.notebook.SetSelectionByName('layers')
+            
+        # fetch fresh db info
+        self.mapDBInfo = VectorDBInfo(self.vectorName)    
+
+        #
+        # add new page
+        #
+        if layer in self.mapDBInfo.layers.keys():
+            # 'browse data' page
+            self._createBrowsePage(layer)
+            # 'manage tables' page
+            self._createManageTablePage(layer)
+            # set current page selection
+            self.notebook.SetSelectionByName('layers')
+            
+        #
+        # 'manage layers' page
+        #
+        # update list of layers
+        self.layerList.Update(self.mapDBInfo.layers)
+        self.layerList.Populate(update=True)
+        # update selected widgets
+        listOfLayers = map(str, self.mapDBInfo.layers.keys())
+        ### delete layer page
+        self.manageLayerBook.deleteLayer.SetItems(listOfLayers)
+        if len(listOfLayers) > 0:
+            self.manageLayerBook.deleteLayer.SetStringSelection(listOfLayers[0])
+            tableName = self.mapDBInfo.layers[int(listOfLayers[0])]['table']
+            maxLayer = max(self.mapDBInfo.layers.keys())
+        else:
+            tableName = ''
+            maxLayer = 0
+        self.manageLayerBook.deleteTable.SetLabel( \
+            _('Drop also linked attribute table (%s)') % \
+                tableName)
+        ### add layer page
+        self.manageLayerBook.addLayerWidgets['layer'][1].SetValue(\
+            maxLayer+1)
+        ### modify layer
+        self.manageLayerBook.modifyLayerWidgets['layer'][1].SetItems(listOfLayers)
+        self.manageLayerBook.OnChangeLayer(event=None)
+
+    def GetVectorName(self):
+        """!Get vector name"""
+        return self.vectorName
+    
+    def LoadData(self, layer, columns=None, where=None, sql=None):
+        """!Load data into list
+
+        @param layer layer number
+        @param columns list of columns for output
+        @param where where statement
+        @param sql full sql statement
+
+        @return id of key column 
+        @return -1 if key column is not displayed
+        """
+        listWin = self.FindWindowById(self.layerPage[layer]['data'])
+        return listWin.LoadData(layer, columns, where, sql)
+    
+class TableListCtrl(wx.ListCtrl,
+                    listmix.ListCtrlAutoWidthMixin):
+                    #                    listmix.TextEditMixin):
+    """!Table description list"""
+
+    def __init__(self, parent, id, table, columns, pos=wx.DefaultPosition,
+                 size=wx.DefaultSize):
+
+        self.parent  = parent
+        self.table   = table
+        self.columns = columns
+        wx.ListCtrl.__init__(self, parent, id, pos, size,
+                             style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
+                             wx.BORDER_NONE)
+
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        # listmix.TextEditMixin.__init__(self)
+
+    def Update(self, table, columns):
+        """!Update column description"""
+        self.table   = table
+        self.columns = columns
+
+    def Populate(self, update=False):
+        """!Populate the list"""
+        itemData = {} # requested by sorter
+
+        if not update:
+            headings = [_("Column name"), _("Data type"), _("Data length")]
+            i = 0
+            for h in headings:
+                self.InsertColumn(col=i, heading=h)
+                self.SetColumnWidth(col=i, width=150)
+                i += 1
+        else:
+            self.DeleteAllItems()
+
+        i = 0
+        for column in self.columns:
+            index = self.InsertStringItem(sys.maxint, str(column))
+            self.SetStringItem(index, 0, str(column))
+            self.SetStringItem(index, 1, str(self.table[column]['type']))
+            self.SetStringItem(index, 2, str(self.table[column]['length']))
+            self.SetItemData(index, i)
+            itemData[i] = (str(column),
+                           str(self.table[column]['type']),
+                           int(self.table[column]['length']))
+            i = i + 1
+
+        self.SendSizeEvent()
+        
+        return itemData
+
+class LayerListCtrl(wx.ListCtrl,
+                    listmix.ListCtrlAutoWidthMixin):
+                    # listmix.ColumnSorterMixin):
+                    # listmix.TextEditMixin):
+    """!Layer description list"""
+
+    def __init__(self, parent, id, layers,
+                 pos=wx.DefaultPosition,
+                 size=wx.DefaultSize):
+
+        self.parent = parent
+        self.layers = layers
+        wx.ListCtrl.__init__(self, parent, id, pos, size,
+                             style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES |
+                             wx.BORDER_NONE)
+
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        # listmix.TextEditMixin.__init__(self)
+
+    def Update(self, layers):
+        """!Update description"""
+        self.layers = layers
+
+    def Populate(self, update=False):
+        """!Populate the list"""
+        itemData = {} # requested by sorter
+
+        if not update:
+            headings = [_("Layer"),  _("Driver"), _("Database"), _("Table"), _("Key")]
+            i = 0
+            for h in headings:
+                self.InsertColumn(col=i, heading=h)
+                i += 1
+        else:
+            self.DeleteAllItems()
+
+        i = 0
+        for layer in self.layers.keys():
+            index = self.InsertStringItem(sys.maxint, str(layer))
+            self.SetStringItem(index, 0, str(layer))
+            database = str(self.layers[layer]['database'])
+            driver   = str(self.layers[layer]['driver'])
+            table    = str(self.layers[layer]['table'])
+            key      = str(self.layers[layer]['key'])
+            self.SetStringItem(index, 1, driver)
+            self.SetStringItem(index, 2, database)
+            self.SetStringItem(index, 3, table)
+            self.SetStringItem(index, 4, key)
+            self.SetItemData(index, i)
+            itemData[i] = (str(layer),
+                           driver,
+                           database,
+                           table,
+                           key)
+            i += 1
+
+        for i in range(self.GetColumnCount()):
+            self.SetColumnWidth(col=i, width=wx.LIST_AUTOSIZE)
+            if self.GetColumnWidth(col=i) < 60:
+                self.SetColumnWidth(col=i, width=60)
+
+        self.SendSizeEvent()
+        
+        return itemData
+
+class LayerBook(wx.Notebook):
+    """!Manage layers (add, delete, modify)"""
+    def __init__(self, parent, id,
+                 parentDialog,
+                 style=wx.BK_DEFAULT):
+        wx.Notebook.__init__(self, parent, id, style=style)
+
+        self.parent       = parent
+        self.parentDialog = parentDialog
+        self.mapDBInfo    = self.parentDialog.mapDBInfo
+
+        #
+        # drivers
+        #
+        drivers = RunCommand('db.drivers',
+                             quiet = True,
+                             read = True,
+                             flags = 'p')
+        
+        self.listOfDrivers = []
+        for drv in drivers.splitlines():
+            self.listOfDrivers.append(drv.strip())
+        
+        #
+        # get default values
+        #
+        self.defaultConnect = {}
+        connect = RunCommand('db.connect',
+                             flags = 'p',
+                             read = True,
+                             quiet = True)
+        
+        for line in connect.splitlines():
+            item, value = line.split(':', 1)
+            self.defaultConnect[item.strip()] = value.strip()
+        
+        if len(self.defaultConnect['driver']) == 0 or \
+               len(self.defaultConnect['database']) == 0:
+            wx.MessageBox(parent=self.parent,
+                          message=_("Unknown default DB connection. "
+                                    "Please define DB connection using db.connect module."),
+                          caption=_("Warning"),
+                          style=wx.OK | wx.ICON_WARNING | wx.CENTRE)
+        
+        self.defaultTables = self._getTables(self.defaultConnect['driver'],
+                                              self.defaultConnect['database'])
+        try:
+            self.defaultColumns = self._getColumns(self.defaultConnect['driver'],
+                                                    self.defaultConnect['database'],
+                                                    self.defaultTables[0])
+        except IndexError:
+            self.defaultColumns = []
+
+        self._createAddPage()
+        self._createDeletePage()
+        self._createModifyPage()
+
+    def _createAddPage(self):
+        """!Add new layer"""
+        self.addPanel = wx.Panel(parent=self, id=wx.ID_ANY)
+        self.AddPage(page=self.addPanel, text=_("Add layer"))
+        
+        try:
+            maxLayer = max(self.mapDBInfo.layers.keys())
+        except ValueError:
+            maxLayer = 0
+
+        # layer description
+        
+        layerBox = wx.StaticBox (parent=self.addPanel, id=wx.ID_ANY,
+                                 label=" %s " % (_("Layer description")))
+        layerSizer = wx.StaticBoxSizer(layerBox, wx.VERTICAL)
+        
+        #
+        # list of layer widgets (label, value)
+        #
+        self.addLayerWidgets = {'layer':
+                                    (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Layer")),
+                                     wx.SpinCtrl(parent=self.addPanel, id=wx.ID_ANY, size=(65, -1),
+                                                 initial=maxLayer+1,
+                                                 min=1, max=1e6)),
+                                'driver':
+                                    (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Driver")),
+                                     wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
+                                               choices=self.listOfDrivers)),
+                                'database':
+                                    (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Database")),
+                                     wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
+                                                 value='',
+                                                 style=wx.TE_PROCESS_ENTER)),
+                                'table':
+                                    (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Table")),
+                                     wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
+                                               choices=self.defaultTables)),
+                                'key':
+                                    (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Key column")),
+                                     wx.Choice(parent=self.addPanel, id=wx.ID_ANY, size=(200, -1),
+                                               choices=self.defaultColumns)),
+                                'addCat':
+                                    (wx.CheckBox(parent=self.addPanel, id=wx.ID_ANY,
+                                                 label=_("Insert record for each category into table")),
+                                     None),
+                                }
+        
+        # set default values for widgets
+        self.addLayerWidgets['driver'][1].SetStringSelection(self.defaultConnect['driver'])
+        self.addLayerWidgets['database'][1].SetValue(self.defaultConnect['database'])
+        self.addLayerWidgets['table'][1].SetSelection(0)
+        self.addLayerWidgets['key'][1].SetSelection(0)
+        # events
+        self.addLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
+        self.addLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
+        self.addLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
+        
+        # tooltips
+        self.addLayerWidgets['addCat'][0].SetToolTipString(_("You need to add categories "
+                                                             "by v.category module."))
+        #
+        # list of table widgets
+        #
+        keyCol = UserSettings.Get(group='atm', key='keycolumn', subkey='value')
+        self.tableWidgets = {'table': (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                     label='%s:' % _("Table name")),
+                                       wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
+                                                   value='',
+                                                   style=wx.TE_PROCESS_ENTER)),
+                             'key': (wx.StaticText(parent=self.addPanel, id=wx.ID_ANY,
+                                                   label='%s:' % _("Key column")),
+                                     wx.TextCtrl(parent=self.addPanel, id=wx.ID_ANY,
+                                                 value=keyCol,
+                                                 style=wx.TE_PROCESS_ENTER))}
+        # events
+        self.tableWidgets['table'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
+        self.tableWidgets['key'][1].Bind(wx.EVT_TEXT_ENTER, self.OnCreateTable)
+        
+        btnTable   = wx.Button(self.addPanel, wx.ID_ANY, _("&Create table"),
+                             size=(125,-1))
+        btnTable.Bind(wx.EVT_BUTTON, self.OnCreateTable)
+        
+        btnLayer   = wx.Button(self.addPanel, wx.ID_ANY, _("&Add layer"),
+                             size=(125,-1))
+        btnLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
+        
+        btnDefault = wx.Button(self.addPanel, wx.ID_ANY, _("&Set default"),
+                               size=(125,-1))
+        btnDefault.Bind(wx.EVT_BUTTON, self.OnSetDefault)
+        
+        # do layout
+        
+        pageSizer = wx.BoxSizer(wx.HORIZONTAL)
+                
+        # data area
+        dataSizer = wx.GridBagSizer(hgap=5, vgap=5)
+        dataSizer.AddGrowableCol(1)
+        row = 0
+        for key in ('layer', 'driver', 'database', 'table', 'key', 'addCat'):
+            label, value = self.addLayerWidgets[key]
+            if not value:
+                span = (1, 2)
+            else:
+                span = (1, 1)
+            dataSizer.Add(item=label,
+                          flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0),
+                          span=span)
+            
+            if not value:
+                row += 1
+                continue
+
+            if label.GetLabel() == "Layer:":
+                style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT
+            else:
+                style = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND
+            
+            dataSizer.Add(item=value,
+                          flag=style, pos=(row, 1))
+            
+            row += 1
+        
+        layerSizer.Add(item=dataSizer,
+                       proportion=1,
+                       flag=wx.ALL | wx.EXPAND,
+                       border=5)
+        
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(item=btnDefault,
+                     proportion=0,
+                     flag=wx.ALL | wx.ALIGN_LEFT,
+                     border=5)
+        
+        btnSizer.Add(item=(5, 5),
+                     proportion=1,
+                     flag=wx.ALL | wx.EXPAND,
+                     border=5)
+        
+        btnSizer.Add(item=btnLayer,
+                     proportion=0,
+                     flag=wx.ALL | wx.ALIGN_RIGHT,
+                     border=5)
+        
+        layerSizer.Add(item=btnSizer,
+                       proportion=0,
+                       flag=wx.ALL | wx.EXPAND,
+                       border=0)
+                
+        # table description
+        tableBox = wx.StaticBox (parent=self.addPanel, id=wx.ID_ANY,
+                                 label=" %s " % (_("Table description")))
+        tableSizer = wx.StaticBoxSizer(tableBox, wx.VERTICAL)
+        
+        # data area
+        dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
+        dataSizer.AddGrowableCol(1)
+        for key in ['table', 'key']:
+            label, value = self.tableWidgets[key]
+            dataSizer.Add(item=label,
+                          flag=wx.ALIGN_CENTER_VERTICAL)
+            dataSizer.Add(item=value,
+                          flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+
+        tableSizer.Add(item=dataSizer,
+                       proportion=1,
+                       flag=wx.ALL | wx.EXPAND,
+                       border=5)
+
+        tableSizer.Add(item=btnTable,
+                       proportion=0,
+                       flag=wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_RIGHT,
+                       border=5)
+
+        pageSizer.Add(item=layerSizer,
+                      proportion=3,
+                      flag=wx.ALL | wx.EXPAND,
+                      border=3)
+        
+        pageSizer.Add(item=tableSizer,
+                      proportion=2,
+                      flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
+                      border=3)
+        
+        self.addPanel.SetAutoLayout(True)
+        self.addPanel.SetSizer(pageSizer)
+        pageSizer.Fit(self.addPanel)
+        
+    def _createDeletePage(self):
+        """!Delete layer"""
+        self.deletePanel = wx.Panel(parent=self, id=wx.ID_ANY)
+        self.AddPage(page=self.deletePanel, text=_("Remove layer"))
+
+        label = wx.StaticText(parent=self.deletePanel, id=wx.ID_ANY,
+                              label='%s:' % _("Layer to remove"))
+
+        self.deleteLayer = wx.ComboBox(parent=self.deletePanel, id=wx.ID_ANY, size=(100, -1),
+                                       style=wx.CB_SIMPLE | wx.CB_READONLY,
+                                       choices=map(str, self.mapDBInfo.layers.keys()))
+        self.deleteLayer.SetSelection(0)           
+        self.deleteLayer.Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
+
+        try:
+            tableName = self.mapDBInfo.layers[int(self.deleteLayer.GetStringSelection())]['table']
+        except ValueError:
+            tableName = ''
+            
+        self.deleteTable = wx.CheckBox(parent=self.deletePanel, id=wx.ID_ANY,
+                                       label=_('Drop also linked attribute table (%s)') % \
+                                       tableName)
+
+        if tableName == '':
+            self.deleteLayer.Enable(False)
+            self.deleteTable.Enable(False)
+            
+        btnDelete   = wx.Button(self.deletePanel, wx.ID_DELETE, _("&Remove layer"),
+                                size=(125,-1))
+        btnDelete.Bind(wx.EVT_BUTTON, self.OnDeleteLayer)
+
+        #
+        # do layout
+        #
+        pageSizer = wx.BoxSizer(wx.VERTICAL)
+
+        dataSizer = wx.BoxSizer(wx.VERTICAL)
+
+        flexSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
+        flexSizer.AddGrowableCol(2)
+
+        flexSizer.Add(item=label,
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+        flexSizer.Add(item=self.deleteLayer,
+                      flag=wx.ALIGN_CENTER_VERTICAL)
+
+        dataSizer.Add(item=flexSizer,
+                      proportion=0,
+                      flag=wx.ALL | wx.EXPAND,
+                      border=1)
+
+        dataSizer.Add(item=self.deleteTable,
+                      proportion=0,
+                      flag=wx.ALL | wx.EXPAND,
+                      border=1)
+
+        pageSizer.Add(item=dataSizer,
+                      proportion=1,
+                      flag=wx.ALL | wx.EXPAND,
+                      border=5)
+
+        pageSizer.Add(item=btnDelete,
+                      proportion=0,
+                      flag=wx.ALL | wx.ALIGN_RIGHT,
+                      border=5)
+
+        self.deletePanel.SetSizer(pageSizer)
+
+    def _createModifyPage(self):
+        """!Modify layer"""
+        self.modifyPanel = wx.Panel(parent=self, id=wx.ID_ANY)
+        self.AddPage(page=self.modifyPanel, text=_("Modify layer"))
+
+        #
+        # list of layer widgets (label, value)
+        #
+        self.modifyLayerWidgets = {'layer':
+                                       (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                      label='%s:' % _("Layer")),
+                                        wx.ComboBox(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                    size=(100, -1),
+                                                    style=wx.CB_SIMPLE | wx.CB_READONLY,
+                                                    choices=map(str, 
+                                                                self.mapDBInfo.layers.keys()))),
+                                   'driver':
+                                       (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                      label='%s:' % _("Driver")),
+                                        wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                  size=(200, -1),
+                                                  choices=self.listOfDrivers)),
+                                   'database':
+                                       (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                      label='%s:' % _("Database")),
+                                        wx.TextCtrl(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                    value='', size=(350, -1),
+                                                    style=wx.TE_PROCESS_ENTER)),
+                                   'table':
+                                       (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                      label='%s:' % _("Table")),
+                                        wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                  size=(200, -1),
+                                                  choices=self.defaultTables)),
+                                   'key':
+                                       (wx.StaticText(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                      label='%s:' % _("Key column")),
+                                        wx.Choice(parent=self.modifyPanel, id=wx.ID_ANY,
+                                                  size=(200, -1),
+                                                  choices=self.defaultColumns))}
+        
+        # set default values for widgets
+        self.modifyLayerWidgets['layer'][1].SetSelection(0)
+        try:
+            layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
+        except ValueError:
+            layer = None
+            for label in self.modifyLayerWidgets.keys():
+                self.modifyLayerWidgets[label][1].Enable(False)
+
+        if layer:
+            driver   = self.mapDBInfo.layers[layer]['driver']
+            database = self.mapDBInfo.layers[layer]['database']
+            table    = self.mapDBInfo.layers[layer]['table']
+
+            listOfColumns = self._getColumns(driver, database, table)
+            self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
+            self.modifyLayerWidgets['database'][1].SetValue(database)
+            if table in self.modifyLayerWidgets['table'][1].GetItems():
+                self.modifyLayerWidgets['table'][1].SetStringSelection(table)
+            else:
+                if self.defaultConnect['schema'] != '':
+                    table = self.defaultConnect['schema'] + table # try with default schema
+                else:
+                    table = 'public.' + table # try with 'public' schema
+                self.modifyLayerWidgets['table'][1].SetStringSelection(table)
+            self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
+            self.modifyLayerWidgets['key'][1].SetSelection(0)
+
+        # events
+        self.modifyLayerWidgets['layer'][1].Bind(wx.EVT_COMBOBOX, self.OnChangeLayer)
+        # self.modifyLayerWidgets['driver'][1].Bind(wx.EVT_CHOICE, self.OnDriverChanged)
+        # self.modifyLayerWidgets['database'][1].Bind(wx.EVT_TEXT_ENTER, self.OnDatabaseChanged)
+        # self.modifyLayerWidgets['table'][1].Bind(wx.EVT_CHOICE, self.OnTableChanged)
+
+        btnModify = wx.Button(self.modifyPanel, wx.ID_DELETE, _("&Modify layer"),
+                              size=(125,-1))
+        btnModify.Bind(wx.EVT_BUTTON, self.OnModifyLayer)
+
+        #
+        # do layout
+        #
+        pageSizer = wx.BoxSizer(wx.VERTICAL)
+
+        # data area
+        dataSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
+        dataSizer.AddGrowableCol(1)
+        for key in ('layer', 'driver', 'database', 'table', 'key'):
+            label, value = self.modifyLayerWidgets[key]
+            dataSizer.Add(item=label,
+                          flag=wx.ALIGN_CENTER_VERTICAL)
+            if label.GetLabel() == "Layer:":
+                dataSizer.Add(item=value,
+                              flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+            else:
+                dataSizer.Add(item=value,
+                              flag=wx.ALIGN_CENTER_VERTICAL)
+
+        pageSizer.Add(item=dataSizer,
+                      proportion=1,
+                      flag=wx.ALL | wx.EXPAND,
+                      border=5)
+
+        pageSizer.Add(item=btnModify,
+                      proportion=0,
+                      flag=wx.ALL | wx.ALIGN_RIGHT,
+                      border=5)
+
+        self.modifyPanel.SetSizer(pageSizer)
+
+    def _getTables(self, driver, database):
+        """!Get list of tables for given driver and database"""
+        tables = []
+
+        ret = RunCommand('db.tables',
+                         parent = self,
+                         read = True,
+                         flags = 'p',
+                         driver = driver,
+                         database = database)
+        
+        if ret is None:
+            wx.MessageBox(parent=self,
+                          message=_("Unable to get list of tables.\n"
+                                    "Please use db.connect to set database parameters."),
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            
+            return tables
+        
+        for table in ret.splitlines():
+            tables.append(table)
+        
+        return tables
+
+    def _getColumns(self, driver, database, table):
+        """!Get list of column of given table"""
+        columns = []
+
+        ret = RunCommand('db.columns',
+                         parent = self,
+                         quiet = True,
+                         read = True,
+                         driver = driver,
+                         database = database,
+                         table = table)
+        
+        if ret == None:
+            return columns
+        
+        for column in ret.splitlines():
+            columns.append(column)
+        
+        return columns
+
+    def OnDriverChanged(self, event):
+        """!Driver selection changed, update list of tables"""
+        driver = event.GetString()
+        database = self.addLayerWidgets['database'][1].GetValue()
+
+        winTable = self.addLayerWidgets['table'][1]
+        winKey   = self.addLayerWidgets['key'][1]
+        tables   = self._getTables(driver, database)
+
+        winTable.SetItems(tables)
+        winTable.SetSelection(0)
+
+        if len(tables) == 0:
+            winKey.SetItems([])
+
+        event.Skip()
+
+    def OnDatabaseChanged(self, event):
+        """!Database selection changed, update list of tables"""
+        event.Skip()
+
+    def OnTableChanged(self, event):
+        """!Table name changed, update list of columns"""
+        driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
+        database = self.addLayerWidgets['database'][1].GetValue()
+        table    = event.GetString()
+
+        win  = self.addLayerWidgets['key'][1]
+        cols = self._getColumns(driver, database, table)
+        win.SetItems(cols)
+        win.SetSelection(0)
+
+        event.Skip()
+
+    def OnSetDefault(self, event):
+        """!Set default values"""
+        driver   = self.addLayerWidgets['driver'][1]
+        database = self.addLayerWidgets['database'][1]
+        table    = self.addLayerWidgets['table'][1]
+        key      = self.addLayerWidgets['key'][1]
+
+        driver.SetStringSelection(self.defaultConnect['driver'])
+        database.SetValue(self.defaultConnect['database'])
+        tables = self._getTables(self.defaultConnect['driver'],
+                                  self.defaultConnect['database'])
+        table.SetItems(tables)
+        table.SetSelection(0)
+        if len(tables) == 0:
+            key.SetItems([])
+        else:
+            cols = self._getColumns(self.defaultConnect['driver'],
+                                     self.defaultConnect['database'],
+                                     tables[0])
+            key.SetItems(cols)
+            key.SetSelection(0)
+
+        event.Skip()
+
+    def OnCreateTable(self, event):
+        """!Create new table (name and key column given)"""
+        driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
+        database = self.addLayerWidgets['database'][1].GetValue()
+        table    = self.tableWidgets['table'][1].GetValue()
+        key      = self.tableWidgets['key'][1].GetValue()
+        
+        if not table or not key:
+            wx.MessageBox(parent=self,
+                          message=_("Unable to create new table. "
+                                    "Table name or key column name is missing."),
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            return
+
+        if table in self.addLayerWidgets['table'][1].GetItems():
+            wx.MessageBox(parent=self,
+                          message=_("Unable to create new table. "
+                                    "Table <%s> already exists in the database.") % table,
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            return
+        
+        # create table
+        sql = 'CREATE TABLE %s (%s INTEGER)' % (table, key)
+
+        RunCommand('db.execute',
+                   quiet = True,
+                   parent = self,
+                   stdin = sql,
+                   input = '-',
+                   driver = driver,
+                   database = database)
+        
+        # update list of tables
+        tableList = self.addLayerWidgets['table'][1]
+        tableList.SetItems(self._getTables(driver, database))
+        tableList.SetStringSelection(table)
+
+        # update key column selection
+        keyList = self.addLayerWidgets['key'][1]
+        keyList.SetItems(self._getColumns(driver, database, table))
+        keyList.SetStringSelection(key)
+        
+        event.Skip()
+
+    def OnAddLayer(self, event):
+        """!Add new layer to vector map"""
+        layer    = int(self.addLayerWidgets['layer'][1].GetValue())
+        layerWin = self.addLayerWidgets['layer'][1]
+        driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
+        database = self.addLayerWidgets['database'][1].GetValue()
+        table    = self.addLayerWidgets['table'][1].GetStringSelection()
+        key      = self.addLayerWidgets['key'][1].GetStringSelection()
+        
+        if layer in self.mapDBInfo.layers.keys():
+            wx.MessageBox(parent=self,
+                          message=_("Unable to add new layer to vector map <%(vector)s>. "
+                                    "Layer %(layer)d already exists.") % 
+                          {'vector' : self.mapDBInfo.map, 'layer' : layer},
+                          caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            return
+
+        # add new layer
+        ret = RunCommand('v.db.connect',
+                         parent = self,
+                         quiet = True,
+                         map = self.mapDBInfo.map,
+                         driver = driver,
+                         database = database,
+                         table = table,
+                         key = key,
+                         layer = layer)
+        
+        # insert records into table if required
+        if self.addLayerWidgets['addCat'][0].IsChecked():
+            RunCommand('v.to.db',
+                       parent = self,
+                       quiet = True,
+                       map = self.mapDBInfo.map,
+                       layer = layer,
+                       qlayer = layer,
+                       option = 'cat',
+                       columns = key)
+
+        if ret == 0:
+            # update dialog (only for new layer)
+            self.parentDialog.UpdateDialog(layer=layer) 
+            # update db info
+            self.mapDBInfo = self.parentDialog.mapDBInfo
+            # increase layer number
+            layerWin.SetValue(layer+1)
+
+        if len(self.mapDBInfo.layers.keys()) == 1:
+            # first layer add --- enable previously disabled widgets
+            self.deleteLayer.Enable()
+            self.deleteTable.Enable()
+            for label in self.modifyLayerWidgets.keys():
+                self.modifyLayerWidgets[label][1].Enable()
+            
+    def OnDeleteLayer(self, event):
+        """!Delete layer"""
+        try:
+            layer = int(self.deleteLayer.GetValue())
+        except:
+            return
+
+        RunCommand('v.db.connect',
+                   parent = self,
+                   flags = 'd',
+                   map = self.mapDBInfo.map,
+                   layer = layer)
+
+        # drop also table linked to layer which is deleted
+        if self.deleteTable.IsChecked():
+            driver   = self.addLayerWidgets['driver'][1].GetStringSelection()
+            database = self.addLayerWidgets['database'][1].GetValue()
+            table    = self.mapDBInfo.layers[layer]['table']
+            sql      = 'DROP TABLE %s' % (table)
+
+            RunCommand('db.execute',
+                       parent = self,
+                       stdin = sql,
+                       quiet = True,
+                       driver = driver,
+                       database = database)
+            
+            # update list of tables
+            tableList = self.addLayerWidgets['table'][1]
+            tableList.SetItems(self._getTables(driver, database))
+            tableList.SetStringSelection(table)
+        
+        # update dialog
+        self.parentDialog.UpdateDialog(layer=layer) 
+        # update db info
+        self.mapDBInfo = self.parentDialog.mapDBInfo
+
+        if len(self.mapDBInfo.layers.keys()) == 0:
+            # disable selected widgets
+            self.deleteLayer.Enable(False)
+            self.deleteTable.Enable(False)
+            for label in self.modifyLayerWidgets.keys():
+                self.modifyLayerWidgets[label][1].Enable(False)
+            
+        event.Skip()
+
+    def OnChangeLayer(self, event):
+        """!Layer number of layer to be deleted is changed"""
+        try:
+            layer = int(event.GetString())
+        except:
+            try:
+                layer = self.mapDBInfo.layers.keys()[0]
+            except:
+                return
+
+        if self.GetCurrentPage() == self.modifyPanel:
+            driver   = self.mapDBInfo.layers[layer]['driver']
+            database = self.mapDBInfo.layers[layer]['database']
+            table    = self.mapDBInfo.layers[layer]['table']
+            listOfColumns = self._getColumns(driver, database, table)
+            self.modifyLayerWidgets['driver'][1].SetStringSelection(driver)
+            self.modifyLayerWidgets['database'][1].SetValue(database)
+            self.modifyLayerWidgets['table'][1].SetStringSelection(table)
+            self.modifyLayerWidgets['key'][1].SetItems(listOfColumns)
+            self.modifyLayerWidgets['key'][1].SetSelection(0)
+        else:
+            self.deleteTable.SetLabel(_('Drop also linked attribute table (%s)') % \
+                                          self.mapDBInfo.layers[layer]['table'])
+        if event:
+            event.Skip()
+
+    def OnModifyLayer(self, event):
+        """!Modify layer connection settings"""
+
+        layer = int(self.modifyLayerWidgets['layer'][1].GetStringSelection())
+
+        modify = False
+        if self.modifyLayerWidgets['driver'][1].GetStringSelection() != \
+                self.mapDBInfo.layers[layer]['driver'] or \
+                self.modifyLayerWidgets['database'][1].GetStringSelection() != \
+                self.mapDBInfo.layers[layer]['database'] or \
+                self.modifyLayerWidgets['table'][1].GetStringSelection() != \
+                self.mapDBInfo.layers[layer]['table'] or \
+                self.modifyLayerWidgets['key'][1].GetStringSelection() != \
+                self.mapDBInfo.layers[layer]['key']:
+            modify = True
+
+        if modify:
+            # delete layer
+            RunCommand('v.db.connect',
+                       parent = self,
+                       quiet = True,
+                       flag = 'd',
+                       map = self.mapDBInfo.map,
+                       layer = layer)
+
+            # add modified layer
+            RunCommand('v.db.connect',
+                       quiet = True,
+                       map = self.mapDBInfo.map,
+                       driver = self.modifyLayerWidgets['driver'][1].GetStringSelection(),
+                       database = self.modifyLayerWidgets['database'][1].GetValue(),
+                       table = self.modifyLayerWidgets['table'][1].GetStringSelection(),
+                       key = self.modifyLayerWidgets['key'][1].GetStringSelection(),
+                       layer = int(layer))
+            
+            # update dialog (only for new layer)
+            self.parentDialog.UpdateDialog(layer=layer) 
+            # update db info
+            self.mapDBInfo = self.parentDialog.mapDBInfo
+
+        event.Skip()
+
+def main(argv = None):
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
+    
+    if argv is None:
+        argv = sys.argv
+    
+    if len(argv) != 2:
+        print >> sys.stderr, __doc__
+        sys.exit()
+    
+    #some applications might require image handlers
+    wx.InitAllImageHandlers()
+    
+    app = wx.PySimpleApp()
+    f = AttributeManager(parent=None, id=wx.ID_ANY,
+                         title="%s - <%s>" % (_("GRASS GIS Attribute Table Manager"),
+                                              argv[1]),
+                         size=(900,600), vectorName=argv[1])
+    f.Show()
+    
+    app.MainLoop()
+    
+if __name__ == '__main__':
+    main()

Copied: grass/trunk/gui/wxpython/dbm/sqlbuilder.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/sqlbuilder.py)
===================================================================
--- grass/trunk/gui/wxpython/dbm/sqlbuilder.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/dbm/sqlbuilder.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,458 @@
+"""!
+ at package dbm.sqlbuilder
+
+ at brief GRASS SQL Builder
+
+Classes:
+ - SQLFrame
+
+Usage:
+ at code
+python sqlbuilder.py vector_map
+ at endcode
+
+(C) 2007-2009, 2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Jachym Cepicky <jachym.cepicky gmail.com> (original author)
+ at author Martin Landa <landa.martin gmail.com>
+ at author Hamish Bowman <hamish_b yahoo com>
+"""
+
+import os
+import sys
+
+from core import globalvar
+import wx
+
+import grass.script as grass
+
+from core.gcmd import RunCommand, GError
+from dbm.vinfo import createDbInfoDesc, VectorDBInfo
+
+class SQLFrame(wx.Frame):
+    """!SQL Frame class"""
+    def __init__(self, parent, title, vectmap, id = wx.ID_ANY,
+                 layer = 1, qtype = "select", evtheader = None):
+        
+        wx.Frame.__init__(self, parent, id, title)
+        
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'),
+                             wx.BITMAP_TYPE_ICO))
+        
+        self.parent = parent
+        self.evtHeader = evtheader
+
+        #
+        # variables
+        #
+        self.vectmap = vectmap # fullname
+        if not "@" in self.vectmap:
+            self.vectmap = grass.find_file(self.vectmap, element = 'vector')['fullname']
+        self.mapname, self.mapset = self.vectmap.split("@", 1)
+        
+        # db info
+        self.layer = layer
+        self.dbInfo = VectorDBInfo(self.vectmap)
+        self.tablename = self.dbInfo.GetTable(self.layer)
+        self.driver, self.database = self.dbInfo.GetDbSettings(self.layer)
+        
+        self.qtype = qtype      # type of query: SELECT, UPDATE, DELETE, ...
+        self.colvalues = []     # array with unique values in selected column
+
+        # set dialog title
+        self.SetTitle(_("GRASS SQL Builder (%(type)s): vector map <%(map)s>") % \
+                          { 'type' : self.qtype.upper(), 'map' : self.vectmap })
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+
+        # statusbar
+        self.statusbar = self.CreateStatusBar(number=1)
+        self.statusbar.SetStatusText(_("SQL statement not verified"), 0)
+       
+        self._doLayout()
+
+    def _doLayout(self):
+        """!Do dialog layout"""
+      
+        pagesizer = wx.BoxSizer(wx.VERTICAL)
+
+        
+        # dbInfo
+        databasebox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                   label = " %s " % _("Database connection"))
+        databaseboxsizer = wx.StaticBoxSizer(databasebox, wx.VERTICAL)
+        databaseboxsizer.Add(item=createDbInfoDesc(self.panel, self.dbInfo, layer = self.layer),
+                             proportion=1,
+                             flag=wx.EXPAND | wx.ALL,
+                             border=3)
+
+        #
+        # text areas
+        #
+        # sql box
+        sqlbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                              label = " %s " % _("Query"))
+        sqlboxsizer = wx.StaticBoxSizer(sqlbox, wx.VERTICAL)
+
+        self.text_sql = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
+                                    value = '', size = (-1, 50),
+                                    style=wx.TE_MULTILINE)
+        if self.qtype.lower() == "select":
+            self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
+        self.text_sql.SetInsertionPointEnd()
+        self.text_sql.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' OR OBJECTID < 10")
+        wx.CallAfter(self.text_sql.SetFocus)
+
+        sqlboxsizer.Add(item = self.text_sql, flag = wx.EXPAND)
+        
+        #
+        # buttons
+        #
+        self.btn_clear  = wx.Button(parent = self.panel, id = wx.ID_CLEAR)
+        self.btn_clear.SetToolTipString(_("Set SQL statement to default"))
+        self.btn_verify = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                    label = _("Verify"))
+        self.btn_verify.SetToolTipString(_("Verify SQL statement"))
+        self.btn_apply  = wx.Button(parent = self.panel, id = wx.ID_APPLY)
+        self.btn_apply.SetToolTipString(_("Apply SQL statement and close the dialog"))
+        self.btn_close  = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
+        self.btn_close.SetToolTipString(_("Close the dialog"))
+        
+        self.btn_lv = { 'is'    : ['=', ],
+                        'isnot' : ['!=', ],
+                        'like'  : ['LIKE', ],
+                        'gt'    : ['>', ],
+                        'ge'    : ['>=', ],
+                        'lt'    : ['<', ],
+                        'le'    : ['<=', ],
+                        'or'    : ['OR', ],
+                        'not'   : ['NOT', ],
+                        'and'   : ['AND', ],
+                        'brac'  : ['()', ],
+                        'prc'   : ['%', ] }
+        
+        for key, value in self.btn_lv.iteritems():
+            btn = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                            label = value[0])
+            self.btn_lv[key].append(btn.GetId())
+        
+        buttonsizer = wx.FlexGridSizer(cols = 4, hgap = 5, vgap = 5)
+        buttonsizer.Add(item = self.btn_clear)
+        buttonsizer.Add(item = self.btn_verify)
+        buttonsizer.Add(item = self.btn_apply)
+        buttonsizer.Add(item = self.btn_close)
+        
+        buttonsizer2 = wx.GridBagSizer(5, 5)
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['is'][1]), pos = (0,0))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['isnot'][1]), pos = (1,0))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['like'][1]), pos = (2, 0))
+
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['gt'][1]), pos = (0, 1))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['ge'][1]), pos = (1, 1))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['or'][1]), pos = (2, 1))
+
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['lt'][1]), pos = (0, 2))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['le'][1]), pos = (1, 2))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['not'][1]), pos = (2, 2))
+
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['brac'][1]), pos = (0, 3))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['prc'][1]), pos = (1, 3))
+        buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['and'][1]), pos = (2, 3))
+        
+        #
+        # list boxes (columns, values)
+        #
+        hsizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        columnsbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                  label = " %s " % _("Columns"))
+        columnsizer = wx.StaticBoxSizer(columnsbox, wx.VERTICAL)
+        self.list_columns = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
+                                       choices = self.dbInfo.GetColumns(self.tablename),
+                                       style = wx.LB_MULTIPLE)
+        columnsizer.Add(item = self.list_columns, proportion = 1,
+                        flag = wx.EXPAND)
+
+        radiosizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.radio_cv = wx.RadioBox(parent = self.panel, id = wx.ID_ANY,
+                                    label = " %s " % _("Add on double-click"),
+                                    choices = [_("columns"), _("values")])
+        self.radio_cv.SetSelection(1) # default 'values'
+        radiosizer.Add(item = self.radio_cv, proportion = 1,
+                       flag = wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND, border = 5)
+
+        columnsizer.Add(item = radiosizer, proportion = 0,
+                        flag = wx.TOP | wx.EXPAND, border = 5)
+        # self.list_columns.SetMinSize((-1,130))
+        # self.list_values.SetMinSize((-1,100))
+
+        valuesbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                 label = " %s " % _("Values"))
+        valuesizer = wx.StaticBoxSizer(valuesbox, wx.VERTICAL)
+        self.list_values = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
+                                      choices = self.colvalues,
+                                      style = wx.LB_MULTIPLE)
+        valuesizer.Add(item = self.list_values, proportion = 1,
+                       flag = wx.EXPAND)
+        
+        self.btn_unique = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                    label = _("Get all values"))
+        self.btn_unique.Enable(False)
+        self.btn_uniquesample = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                          label = _("Get sample"))
+        self.btn_uniquesample.Enable(False)
+
+        buttonsizer3 = wx.BoxSizer(wx.HORIZONTAL)
+        buttonsizer3.Add(item = self.btn_uniquesample, proportion = 0,
+                         flag = wx.ALIGN_CENTER_HORIZONTAL | wx.RIGHT, border = 5)
+        buttonsizer3.Add(item = self.btn_unique, proportion = 0,
+                         flag = wx.ALIGN_CENTER_HORIZONTAL)
+
+        valuesizer.Add(item = buttonsizer3, proportion = 0,
+                       flag = wx.TOP, border = 5)
+        
+        # hsizer1.Add(wx.StaticText(self.panel,-1, "Unique values: "), border=0, proportion=1)
+ 
+        hsizer.Add(item = columnsizer, proportion = 1,
+                   flag = wx.EXPAND)
+        hsizer.Add(item = valuesizer, proportion = 1,
+                   flag = wx.EXPAND)
+        
+        self.close_onapply = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                         label = _("Close dialog on apply"))
+        self.close_onapply.SetValue(True)
+ 
+        pagesizer.Add(item = databaseboxsizer,
+                      flag = wx.ALL | wx.EXPAND, border = 5)
+        pagesizer.Add(item = hsizer, proportion = 1,
+                      flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
+        # pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5)
+        # pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5)
+        pagesizer.Add(item = buttonsizer2, proportion = 0,
+                      flag = wx.ALIGN_CENTER_HORIZONTAL)
+        pagesizer.Add(item = sqlboxsizer, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
+        pagesizer.Add(item = buttonsizer, proportion = 0,
+                      flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
+        pagesizer.Add(item = self.close_onapply, proportion = 0,
+                      flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
+
+        #
+        # bindings
+        #
+        self.btn_unique.Bind(wx.EVT_BUTTON,       self.OnUniqueValues)
+        self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues)
+        
+        for key, value in self.btn_lv.iteritems():
+            self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
+        
+        self.btn_close.Bind(wx.EVT_BUTTON,       self.OnClose)
+        self.btn_clear.Bind(wx.EVT_BUTTON,       self.OnClear)
+        self.btn_verify.Bind(wx.EVT_BUTTON,      self.OnVerify)
+        self.btn_apply.Bind(wx.EVT_BUTTON,       self.OnApply)
+
+        self.list_columns.Bind(wx.EVT_LISTBOX,   self.OnAddColumn)
+        self.list_values.Bind(wx.EVT_LISTBOX,    self.OnAddValue)
+        
+        self.text_sql.Bind(wx.EVT_TEXT,          self.OnText)
+        
+        self.panel.SetAutoLayout(True)
+        self.panel.SetSizer(pagesizer)
+        pagesizer.Fit(self.panel)
+        
+        self.Layout()
+        self.SetMinSize((660, 525))
+        self.SetClientSize(self.panel.GetSize())
+        self.CenterOnParent()
+        
+    def OnUniqueValues(self, event, justsample = False):
+        """!Get unique values"""
+        vals = []
+        try:
+            idx = self.list_columns.GetSelections()[0]
+            column = self.list_columns.GetString(idx)
+        except:
+            self.list_values.Clear()
+            return
+        
+        self.list_values.Clear()
+        
+        querystring = "SELECT %s FROM %s" % (column, self.tablename)
+        
+        data = grass.db_select(table = self.tablename,
+                               sql = querystring,
+                               database = self.database,
+                               driver = self.driver)
+        if not data:
+            return
+
+        desc = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]
+        
+        i = 0
+        for item in sorted(map(desc['ctype'], data)):
+            if justsample and i < 256 or \
+               not justsample:
+                if desc['type'] != 'character':
+                    item = str(item)
+                self.list_values.Append(item)
+            else:
+                break
+            i += 1
+        
+    def OnSampleValues(self, event):
+        """!Get sample values"""
+        self.OnUniqueValues(None, True)
+
+    def OnAddColumn(self, event):
+        """!Add column name to the query"""
+        idx = self.list_columns.GetSelections()
+        for i in idx:
+            column = self.list_columns.GetString(i)
+            self._add(element = 'column', value = column)
+        
+        if not self.btn_uniquesample.IsEnabled():
+            self.btn_uniquesample.Enable(True)
+            self.btn_unique.Enable(True)
+        
+    def OnAddValue(self, event):
+        """!Add value"""
+        selection = self.list_values.GetSelections()
+        if not selection:
+            event.Skip()
+            return
+
+        idx = selection[0]
+        value = self.list_values.GetString(idx)
+        idx = self.list_columns.GetSelections()[0]
+        column = self.list_columns.GetString(idx)
+        
+        ctype = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]['type']
+        
+        if ctype == 'character':
+            value = "'%s'" % value
+        
+        self._add(element = 'value', value = value)
+
+    def OnAddMark(self, event):
+        """!Add mark"""
+        mark = None
+        for key, value in self.btn_lv.iteritems():
+            if event.GetId() == value[1]:
+                mark = value[0]
+                break
+        
+        self._add(element = 'mark', value = mark)
+
+    def _add(self, element, value):
+        """!Add element to the query
+
+        @param element element to add (column, value)
+        """
+        sqlstr = self.text_sql.GetValue()
+        newsqlstr = ''
+        if element == 'column':
+            if self.radio_cv.GetSelection() == 0: # -> column
+                idx1 = len('select')
+                idx2 = sqlstr.lower().find('from')
+                colstr = sqlstr[idx1:idx2].strip()
+                if colstr == '*':
+                    cols = []
+                else:
+                    cols = colstr.split(',')
+                if value in cols:
+                        cols.remove(value)
+                else:
+                    cols.append(value)
+                
+                if len(cols) < 1:
+                    cols = ['*',]
+                
+                newsqlstr = 'SELECT ' + ','.join(cols) + ' ' + sqlstr[idx2:]
+            else: # -> where
+                newsqlstr = sqlstr
+                if sqlstr.lower().find('where') < 0:
+                    newsqlstr += ' WHERE'
+                
+                newsqlstr += ' ' + value
+        
+        elif element == 'value':
+            newsqlstr = sqlstr + ' ' + value
+        elif element == 'mark':
+            newsqlstr = sqlstr + ' ' + value
+        
+        if newsqlstr:
+            self.text_sql.SetValue(newsqlstr)
+
+    def GetSQLStatement(self):
+        """!Return SQL statement"""
+        return self.text_sql.GetValue().strip().replace("\n"," ")
+    
+    def CloseOnApply(self):
+        """!Return True if the dialog will be close on apply"""
+        return self.close_onapply.IsChecked()
+    
+    def OnText(self, event):
+        """Query string changed"""
+        if len(self.text_sql.GetValue()) > 0:
+            self.btn_verify.Enable(True)
+        else:
+            self.btn_verify.Enable(False)
+        
+    def OnApply(self, event):
+        """Apply button pressed"""
+        if self.evtHeader:
+            self.evtHeader(event = 'apply')
+
+        if self.close_onapply.IsChecked():
+            self.Destroy()
+            
+        event.Skip()
+    
+    def OnVerify(self, event):
+        """!Verify button pressed"""
+        ret, msg = RunCommand('db.select',
+                              getErrorMsg = True,
+                              table = self.tablename,
+                              sql = self.text_sql.GetValue(),
+                              flags = 't',
+                              driver = self.driver,
+                              database = self.database)
+        
+        if ret != 0 and msg:
+            self.statusbar.SetStatusText(_("SQL statement is not valid"), 0)
+            GError(parent = self,
+                   message = _("SQL statement is not valid.\n\n%s") % msg)
+        else:
+            self.statusbar.SetStatusText(_("SQL statement is valid"), 0)
+                        
+    def OnClear(self, event):
+        """!Clear button pressed"""
+        if self.qtype.lower() == "select":
+            self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
+        else:
+            self.text_sql.SetValue("")
+    
+    def OnClose(self, event):
+        """!Close button pressed"""
+        if self.evtHeader:
+            self.evtHeader(event = 'close')
+        
+        self.Destroy()
+        
+        event.Skip()
+        
+if __name__ == "__main__":
+    if len(sys.argv) != 2:
+        print >>sys.stderr, __doc__
+        sys.exit()
+    
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
+    
+    app = wx.App(0)
+    sqlb = SQLFrame(parent = None, title = _('SQL Builder'), vectmap = sys.argv[1])
+    sqlb.Show()
+    
+    app.MainLoop()

Copied: grass/trunk/gui/wxpython/dbm/vinfo.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/dbm_base.py)
===================================================================
--- grass/trunk/gui/wxpython/dbm/vinfo.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/dbm/vinfo.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,163 @@
+"""
+ at package dbm.vinfo
+
+ at brief Support classes for Database Manager
+
+List of classes:
+ - VectorDBInfo
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import types
+
+import wx
+
+from gui_core.gselect import VectorDBInfo
+from core.gcmd        import RunCommand
+from core.settings    import UserSettings
+
+import grass.script as grass
+
+def unicodeValue(value):
+    """!Encode value"""
+    if type(value) == types.UnicodeType:
+        return value
+    
+    enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
+    if enc:
+        value = unicode(value, enc)
+    elif 'GRASS_DB_ENCODING' in os.environ:
+        value = unicode(value, os.environ['GRASS_DB_ENCODING'])
+    else:
+        try:
+            value = unicode(value, 'ascii')
+        except UnicodeDecodeError:
+            value = _("Unable to decode value. Set encoding in GUI preferences ('Attributes').")
+    
+    return value
+
+def createDbInfoDesc(panel, mapDBInfo, layer):
+    """!Create database connection information content"""
+    infoFlexSizer = wx.FlexGridSizer (cols = 2, hgap = 1, vgap = 1)
+    infoFlexSizer.AddGrowableCol(1)
+    
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = "Driver:"))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = mapDBInfo.layers[layer]['driver']))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = "Database:"))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = mapDBInfo.layers[layer]['database']))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = "Table:"))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = mapDBInfo.layers[layer]['table']))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = "Key:"))
+    infoFlexSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = mapDBInfo.layers[layer]['key']))
+    
+    return infoFlexSizer
+        
+class VectorDBInfo(VectorDBInfo):
+    """!Class providing information about attribute tables
+    linked to the vector map"""
+    def __init__(self, map):
+        VectorDBInfo.__init__(self, map)
+        
+    def GetColumns(self, table):
+        """!Return list of columns names (based on their index)"""
+        try:
+            names = [''] * len(self.tables[table].keys())
+        except KeyError:
+            return []
+        
+        for name, desc in self.tables[table].iteritems():
+            names[desc['index']] = name
+        
+        return names
+
+    def SelectByPoint(self, queryCoords, qdist):
+        """!Get attributes by coordinates (all available layers)
+
+        Return line id or None if no line is found"""
+        line = None
+        nselected = 0
+
+        data = grass.vector_what(map = self.map,
+                                 coord = (float(queryCoords[0]), float(queryCoords[1])),
+                                 distance = float(qdist))
+
+        if len(data) < 1 or 'Table' not in data[0]:
+            return None
+        
+        # process attributes
+        table = data[0]['Table']
+        for key, value in data[0]['Attributes'].iteritems():
+            if len(value) < 1:
+                value = None
+            else:
+                if self.tables[table][key]['ctype'] != types.StringType:
+                    value = self.tables[table][key]['ctype'] (value)
+                else:
+                    value = unicodeValue(value)
+            self.tables[table][key]['values'].append(value)
+        
+        ret = dict()
+        for key, value in data[0].iteritems():
+            if key == 'Attributes':
+                continue
+            ret[key] = list()
+            ret[key].append(value)
+        
+        return ret
+    
+    def SelectFromTable(self, layer, cols = '*', where = None):
+        """!Select records from the table
+
+        Return number of selected records, -1 on error
+        """
+        if layer <= 0:
+            return -1
+
+        nselected = 0
+
+        table = self.layers[layer]["table"] # get table desc
+        # select values (only one record)
+        if where is None or where is '':
+            sql = "SELECT %s FROM %s" % (cols, table)
+        else:
+            sql = "SELECT %s FROM %s WHERE %s" % (cols, table, where)
+        
+        ret = RunCommand('db.select',
+                         parent = self,
+                         read = True,
+                         quiet = True,
+                         flags = 'v',
+                         sql= sql,
+                         database = self.layers[layer]["database"],
+                         driver = self.layers[layer]["driver"])
+        
+        # self.tables[table][key][1] = str(cat)
+        if ret:
+            for line in ret.splitlines():
+                name, value = line.split('|')
+                # casting ...
+                if value:
+                    if self.tables[table][name]['ctype'] != type(''):
+                        value = self.tables[table][name]['ctype'] (value)
+                    else:
+                        value = unicodeValue(value)
+                else:
+                    value = None
+                self.tables[table][name]['values'].append(value)
+                nselected = 1
+
+        return nselected

Copied: grass/trunk/gui/wxpython/gcp/manager.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/gcpmanager.py)
===================================================================
--- grass/trunk/gui/wxpython/gcp/manager.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gcp/manager.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,2781 @@
+"""!
+ at package gcp.manager
+
+ at brief Georectification module for GRASS GIS. Includes ground control
+point management and interactive point and click GCP creation
+
+Classes:
+ - GCPWizard
+ - LocationPage
+ - GroupPage
+ - DispMapPage
+ - GCP
+ - GCPList
+ - VectGroup
+ - EditGCP
+ - GrSettingsDialog
+
+(C) 2006-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Michael Barton
+ at author Updated by Martin Landa <landa.martin gmail.com>
+ at author Markus Metz redesign georectfier -> GCP Manager
+"""
+
+import os
+import sys
+import shutil
+
+import wx
+from wx.lib.mixins.listctrl import CheckListCtrlMixin, ColumnSorterMixin, ListCtrlAutoWidthMixin
+import wx.lib.colourselect as csel
+import wx.wizard           as wiz
+
+import grass.script as grass
+
+from core              import globalvar
+from core              import utils
+from core.render       import Map
+from gui_core.gselect  import Select, LocationSelect, MapsetSelect
+from gui_core.gdialogs import GroupDialog
+from core.gcmd         import RunCommand, GMessage, GError, GWarning
+from core.settings     import UserSettings
+from gcp.mapdisp       import MapFrame
+from mapdisp.window    import BufferedWindow
+
+from location_wizard.wizard   import TitledPage as TitledPage
+
+#
+# global variables
+#
+global src_map
+global tgt_map
+global maptype
+
+src_map = ''
+tgt_map = ''
+maptype = 'cell'
+
+def getSmallUpArrowImage():
+    stream = open(os.path.join(globalvar.ETCIMGDIR, 'small_up_arrow.png'), 'rb')
+    try:
+        img = wx.ImageFromStream(stream)
+    finally:
+        stream.close()
+    return img
+
+def getSmallDnArrowImage():
+    stream = open(os.path.join(globalvar.ETCIMGDIR, 'small_down_arrow.png'), 'rb')
+    try:
+        img = wx.ImageFromStream(stream)
+    finally:
+        stream.close()
+    stream.close()
+    return img
+
+class GCPWizard(object):
+    """
+    Start wizard here and finish wizard here
+    """
+
+    def __init__(self, parent):
+        self.parent = parent # GMFrame
+
+        #
+        # get environmental variables
+        #
+        self.grassdatabase = grass.gisenv()['GISDBASE']
+        
+        #
+        # read original environment settings
+        #
+        self.target_gisrc = os.environ['GISRC']
+        self.gisrc_dict = {}
+        try:
+            f = open(self.target_gisrc, 'r')
+            for line in f.readlines():
+                line = line.replace('\n', '').strip()
+                if len(line) < 1:
+                    continue
+                key, value = line.split(':', 1)
+                self.gisrc_dict[key.strip()] = value.strip()
+        finally:
+            f.close()
+            
+        self.currentlocation = self.gisrc_dict['LOCATION_NAME']
+        self.currentmapset = self.gisrc_dict['MAPSET']
+        # location for xy map to georectify
+        self.newlocation = ''
+        # mapset for xy map to georectify
+        self.newmapset = '' 
+
+        global maptype
+        global src_map
+        global tgt_map
+
+        src_map = ''
+        tgt_map = ''
+        maptype = 'cell'
+
+        # GISRC file for source location/mapset of map(s) to georectify
+        self.source_gisrc = ''
+        self.src_maps = []
+
+        #
+        # define wizard pages
+        #
+        self.wizard = wiz.Wizard(parent=parent, id=wx.ID_ANY, title=_("Setup for georectification"))
+        self.startpage = LocationPage(self.wizard, self)
+        self.grouppage = GroupPage(self.wizard, self)
+        self.mappage = DispMapPage(self.wizard, self)
+
+        #
+        # set the initial order of the pages
+        #
+        self.startpage.SetNext(self.grouppage)
+        self.grouppage.SetPrev(self.startpage)
+        self.grouppage.SetNext(self.mappage)
+        self.mappage.SetPrev(self.grouppage)
+
+        #
+        # do pages layout
+        #
+        self.startpage.DoLayout()
+        self.grouppage.DoLayout()
+        self.mappage.DoLayout()
+        self.wizard.FitToPage(self.startpage)
+
+        # self.Bind(wx.EVT_CLOSE,    self.Cleanup)
+        # self.parent.Bind(wx.EVT_ACTIVATE, self.OnGLMFocus)
+
+        success = False
+
+        #
+        # run wizard
+        #
+        if self.wizard.RunWizard(self.startpage):
+            success = self.OnWizFinished()
+            if success == False:
+                GMessage(parent = self.parent,
+                         message = _("Georectifying setup canceled."))
+                self.Cleanup()
+        else:
+            GMessage(parent = self.parent,
+                     message = _("Georectifying setup canceled."))
+            self.Cleanup()
+
+        #
+        # start GCP display
+        #
+        if success != False:
+            # instance of render.Map to be associated with display
+            self.SwitchEnv('source')
+            self.SrcMap = Map(gisrc=self.source_gisrc) 
+            self.SwitchEnv('target')
+            self.TgtMap = Map(gisrc=self.target_gisrc)
+            self.Map = self.SrcMap
+            
+            #
+            # add layer to source map
+            #
+            if maptype == 'cell':
+                rendertype = 'raster'
+                cmdlist = ['d.rast', 'map=%s' % src_map]
+            else: # -> vector layer
+                rendertype = 'vector'
+                cmdlist = ['d.vect', 'map=%s' % src_map]
+            
+            self.SwitchEnv('source')
+            name, found = utils.GetLayerNameFromCmd(cmdlist)
+            self.SrcMap.AddLayer(type=rendertype, command=cmdlist, l_active=True,
+                                 name=name, l_hidden=False, l_opacity=1.0, l_render=False)
+
+            if tgt_map:
+                #
+                # add layer to target map
+                #
+                if maptype == 'cell':
+                    rendertype = 'raster'
+                    cmdlist = ['d.rast', 'map=%s' % tgt_map]
+                else: # -> vector layer
+                    rendertype = 'vector'
+                    cmdlist = ['d.vect', 'map=%s' % tgt_map]
+                
+                self.SwitchEnv('target')
+                name, found = utils.GetLayerNameFromCmd(cmdlist)
+                self.TgtMap.AddLayer(type=rendertype, command=cmdlist, l_active=True,
+                                     name=name, l_hidden=False, l_opacity=1.0, l_render=False)
+            
+            #
+            # start GCP Manager
+            #
+            self.gcpmgr = GCP(self.parent, grwiz=self, size=globalvar.MAP_WINDOW_SIZE,
+                                               toolbars=["gcpdisp"],
+                                               Map=self.SrcMap, lmgr=self.parent)
+
+            # load GCPs
+            self.gcpmgr.InitMapDisplay()
+            self.gcpmgr.CenterOnScreen()
+            self.gcpmgr.Show()
+            # need to update AUI here for wingrass
+            self.gcpmgr._mgr.Update()
+        else:
+            self.Cleanup()
+                            
+    def SetSrcEnv(self, location, mapset):
+        """!Create environment to use for location and mapset
+        that are the source of the file(s) to georectify
+
+        @param location source location
+        @param mapset source mapset
+
+        @return False on error
+        @return True on success
+        """
+        
+        self.newlocation = location
+        self.newmapset = mapset
+        
+        # check to see if we are georectifying map in current working location/mapset
+        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
+            return False
+        
+        self.gisrc_dict['LOCATION_NAME'] = location
+        self.gisrc_dict['MAPSET'] = mapset
+        
+        self.source_gisrc = utils.GetTempfile()
+
+        try:
+            f = open(self.source_gisrc, mode='w')        
+            for line in self.gisrc_dict.items():
+                f.write(line[0] + ": " + line[1] + "\n")
+        finally:
+            f.close()
+
+        return True
+
+    def SwitchEnv(self, grc):
+        """
+        Switches between original working location/mapset and
+        location/mapset that is source of file(s) to georectify
+        """
+        # check to see if we are georectifying map in current working location/mapset
+        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
+            return False
+
+        if grc == 'target':
+            os.environ['GISRC'] = str(self.target_gisrc)
+        elif grc == 'source':
+            os.environ['GISRC'] = str(self.source_gisrc)
+
+        return True
+    
+    def OnWizFinished(self):
+        # self.Cleanup()
+
+        return True
+        
+    def OnGLMFocus(self, event):
+        """!Layer Manager focus"""
+        # self.SwitchEnv('target')
+        
+        event.Skip()
+
+    def Cleanup(self):
+        """!Return to current location and mapset"""
+        self.SwitchEnv('target')
+        self.parent.gcpmanagement = None
+
+        self.wizard.Destroy()
+
+class LocationPage(TitledPage):
+    """
+    Set map type (raster or vector) to georectify and
+    select location/mapset of map(s) to georectify.
+    """
+    def __init__(self, wizard, parent):
+        TitledPage.__init__(self, wizard, _("Select map type and location/mapset"))
+
+        self.parent = parent
+        self.grassdatabase = self.parent.grassdatabase
+        
+        self.xylocation = ''
+        self.xymapset = ''
+        
+        #
+        # layout
+        #
+        self.sizer.AddGrowableCol(2)
+        # map type
+        self.rb_maptype = wx.RadioBox(parent=self, id=wx.ID_ANY,
+                                      label=' %s ' % _("Map type to georectify"),
+                                      choices=[_('raster'), _('vector')],
+                                      majorDimension=wx.RA_SPECIFY_COLS)
+        self.sizer.Add(item=self.rb_maptype,
+                       flag=wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, border=5,
+                       pos=(1, 1), span=(1, 2))
+
+        # location
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select source location:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 1))
+        self.cb_location = LocationSelect(parent = self, gisdbase = self.grassdatabase)
+        self.sizer.Add(item=self.cb_location,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 2))
+
+        # mapset
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select source mapset:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(3, 1))
+        self.cb_mapset = MapsetSelect(parent = self, gisdbase = self.grassdatabase,
+                                              setItems = False)
+        self.sizer.Add(item=self.cb_mapset,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(3,2))
+
+        #
+        # bindings
+        #
+        self.Bind(wx.EVT_RADIOBOX, self.OnMaptype, self.rb_maptype)
+        self.Bind(wx.EVT_COMBOBOX, self.OnLocation, self.cb_location)
+        self.Bind(wx.EVT_COMBOBOX, self.OnMapset, self.cb_mapset)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
+        # self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
+
+    def OnMaptype(self,event):
+        """!Change map type"""
+        global maptype
+
+        if event.GetInt() == 0:
+            maptype = 'cell'
+        else:
+            maptype = 'vector'
+        
+    def OnLocation(self, event):
+        """!Sets source location for map(s) to georectify"""
+        self.xylocation = event.GetString()
+        
+        #create a list of valid mapsets
+        tmplist = os.listdir(os.path.join(self.grassdatabase, self.xylocation))
+        self.mapsetList = []
+        for item in tmplist:
+            if os.path.isdir(os.path.join(self.grassdatabase, self.xylocation, item)) and \
+                os.path.exists(os.path.join(self.grassdatabase, self.xylocation, item, 'WIND')):
+                if item != 'PERMANENT':
+                    self.mapsetList.append(item)
+
+        self.xymapset = 'PERMANENT'
+        utils.ListSortLower(self.mapsetList)
+        self.mapsetList.insert(0, 'PERMANENT')
+        self.cb_mapset.SetItems(self.mapsetList)
+        self.cb_mapset.SetStringSelection(self.xymapset)
+        
+        if not wx.FindWindowById(wx.ID_FORWARD).IsEnabled():
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+
+    def OnMapset(self, event):
+        """!Sets source mapset for map(s) to georectify"""
+        if self.xylocation == '':
+            GMessage(_('You must select a valid location '
+                       'before selecting a mapset'),
+                     parent = self)
+            return
+
+        self.xymapset = event.GetString()
+        
+        if not wx.FindWindowById(wx.ID_FORWARD).IsEnabled():
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+
+    def OnPageChanging(self, event=None):
+        if event.GetDirection() and \
+               (self.xylocation == '' or self.xymapset == ''):
+            GMessage(_('You must select a valid location '
+                            'and mapset in order to continue'),
+                          parent = self)
+            event.Veto()
+            return
+        
+        self.parent.SetSrcEnv(self.xylocation, self.xymapset)
+        
+    def OnEnterPage(self, event=None):
+        if self.xylocation == '' or self.xymapset == '':
+            wx.FindWindowById(wx.ID_FORWARD).Enable(False)
+        else:
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+
+class GroupPage(TitledPage):
+    """
+    Set group to georectify. Create group if desired.
+    """
+    def __init__(self, wizard, parent):
+        TitledPage.__init__(self, wizard, _("Select image/map group to georectify"))
+
+        self.parent = parent
+        
+        self.grassdatabase = self.parent.grassdatabase
+        self.groupList = []
+        
+        self.xylocation = ''
+        self.xymapset = ''
+        self.xygroup = ''
+
+        # default extension
+        self.extension = '.georect' + str(os.getpid())
+
+        #
+        # layout
+        #
+        self.sizer.AddGrowableCol(2)
+        # group
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select group:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(1, 1))
+        self.cb_group = wx.ComboBox(parent=self, id=wx.ID_ANY,
+                                    choices=self.groupList, size=(350, -1),
+                                    style=wx.CB_DROPDOWN | wx.CB_READONLY)
+        self.sizer.Add(item=self.cb_group,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(1, 2))
+        
+        # create group               
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Create group if none exists')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 1))
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.btn_mkgroup = wx.Button(parent=self, id=wx.ID_ANY, label=_("Create/edit group..."))
+        self.btn_vgroup = wx.Button(parent=self, id=wx.ID_ANY, label=_("Add vector map to group..."))
+        btnSizer.Add(item=self.btn_mkgroup,
+                     flag=wx.RIGHT, border=5)
+
+        btnSizer.Add(item=self.btn_vgroup,
+                     flag=wx.LEFT, border=5)
+        
+        self.sizer.Add(item=btnSizer,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 2))
+        
+        # extension
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Extension for output maps:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(3, 1))
+        self.ext_txt = wx.TextCtrl(parent=self, id=wx.ID_ANY, value="", size=(350,-1))
+        self.ext_txt.SetValue(self.extension)
+        self.sizer.Add(item=self.ext_txt,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(3, 2))
+
+        #
+        # bindings
+        #
+        self.Bind(wx.EVT_COMBOBOX, self.OnGroup, self.cb_group)
+        self.Bind(wx.EVT_TEXT, self.OnExtension, self.ext_txt)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
+        self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
+
+        # hide vector group button by default
+        self.btn_vgroup.Hide()
+
+    def OnGroup(self, event):        
+        self.xygroup = event.GetString()
+        
+    def OnMkGroup(self, event):
+        """!Create new group in source location/mapset"""
+        dlg = GroupDialog(parent = self, defaultGroup = self.xygroup)
+
+        dlg.ShowModal()
+        gr = dlg.GetSelectedGroup()
+        if gr in dlg.GetExistGroups():
+            self.xygroup = gr
+        else:
+            gr = ''
+        dlg.Destroy()
+        
+        self.OnEnterPage()
+        self.Update()
+        
+    def OnVGroup(self, event):
+        """!Add vector maps to group"""
+        dlg = VectGroup(parent = self,
+                        id = wx.ID_ANY,
+                        grassdb = self.grassdatabase,
+                        location = self.xylocation,
+                        mapset = self.xymapset,
+                        group = self.xygroup)
+
+        if dlg.ShowModal() != wx.ID_OK:
+            return
+
+        dlg.MakeVGroup()
+        self.OnEnterPage()
+        
+    def OnExtension(self, event):
+        self.extension = event.GetString()
+
+    def OnPageChanging(self, event=None):
+        if event.GetDirection() and self.xygroup == '':
+            GMessage(_('You must select a valid image/map '
+                       'group in order to continue'),
+                     parent = self)
+            event.Veto()
+            return
+
+        if event.GetDirection() and self.extension == '':
+            GMessage(_('You must enter an map name '
+                       'extension in order to continue'),
+                     parent = self)
+            event.Veto()
+            return
+
+    def OnEnterPage(self, event=None):
+        global maptype
+        
+        self.groupList = []
+
+        self.xylocation = self.parent.gisrc_dict['LOCATION_NAME']
+        self.xymapset = self.parent.gisrc_dict['MAPSET']
+
+        # create a list of groups in selected mapset
+        if os.path.isdir(os.path.join(self.grassdatabase,
+                                      self.xylocation,
+                                      self.xymapset,
+                                      'group')):
+            tmplist = os.listdir(os.path.join(self.grassdatabase,
+                                              self.xylocation,
+                                              self.xymapset,
+                                              'group'))
+            for item in tmplist:
+                if os.path.isdir(os.path.join(self.grassdatabase,
+                                              self.xylocation,
+                                              self.xymapset,
+                                              'group',
+                                              item)):
+                    self.groupList.append(item)
+        
+        if maptype == 'cell':
+            self.btn_vgroup.Hide()
+            self.Bind(wx.EVT_BUTTON, self.OnMkGroup, self.btn_mkgroup)
+
+        elif maptype == 'vector':
+            self.btn_vgroup.Show()
+            self.Bind(wx.EVT_BUTTON, self.OnMkGroup, self.btn_mkgroup)
+            self.Bind(wx.EVT_BUTTON, self.OnVGroup, self.btn_vgroup)
+        
+        utils.ListSortLower(self.groupList)
+        self.cb_group.SetItems(self.groupList)
+        
+        if len(self.groupList) > 0:
+            if self.xygroup and self.xygroup in self.groupList:
+                self.cb_group.SetStringSelection(self.xygroup)
+            else:
+                self.cb_group.SetSelection(0)
+                self.xygroup = self.groupList[0]
+        
+        if self.xygroup == '' or \
+                self.extension == '':
+            wx.FindWindowById(wx.ID_FORWARD).Enable(False)
+        else:
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+        
+        # switch to source
+        self.parent.SwitchEnv('source')
+    
+class DispMapPage(TitledPage):
+    """
+    Select ungeoreferenced map to display for interactively
+    setting ground control points (GCPs).
+    """
+    def __init__(self, wizard, parent):
+        TitledPage.__init__(self, wizard,
+                            _("Select maps to display for ground control point (GCP) creation"))
+
+        self.parent = parent
+        global maptype
+
+        #
+        # layout
+        #
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select source map to display:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(1, 1))
+        
+        self.srcselection = Select(self, id=wx.ID_ANY,
+                                   size=globalvar.DIALOG_GSELECT_SIZE, type=maptype, updateOnPopup = False)
+        
+        self.sizer.Add(item=self.srcselection,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(1, 2))
+
+        self.sizer.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_('Select target map to display:')),
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 1))
+
+        self.tgtselection = Select(self, id=wx.ID_ANY,
+                                   size=globalvar.DIALOG_GSELECT_SIZE, type=maptype, updateOnPopup = False)
+        
+        self.sizer.Add(item=self.tgtselection,
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5,
+                       pos=(2, 2))
+
+        #
+        # bindings
+        #
+        self.srcselection.Bind(wx.EVT_TEXT, self.OnSrcSelection)
+        self.tgtselection.Bind(wx.EVT_TEXT, self.OnTgtSelection)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
+        self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnEnterPage)
+        self.Bind(wx.EVT_CLOSE, self.parent.Cleanup)
+
+    def OnSrcSelection(self,event):
+        """!Source map to display selected"""
+        global src_map
+        global maptype
+
+        src_map = event.GetString()
+
+        if src_map == '':
+            wx.FindWindowById(wx.ID_FORWARD).Enable(False)
+        else:
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+
+        try:
+        # set computational region to match selected map and zoom display to region
+            if maptype == 'cell':
+                p = RunCommand('g.region', 'rast=src_map')
+            elif maptype == 'vector':
+                p = RunCommand('g.region', 'vect=src_map')
+            
+            if p.returncode == 0:
+                print 'returncode = ', str(p.returncode)
+                self.parent.Map.region = self.parent.Map.GetRegion()
+        except:
+            pass
+
+    def OnTgtSelection(self,event):
+        """!Source map to display selected"""
+        global tgt_map
+
+        tgt_map = event.GetString()
+
+    def OnPageChanging(self, event=None):
+        global src_map
+        global tgt_map
+
+        if event.GetDirection() and (src_map == ''):
+            GMessage(_('You must select a source map '
+                       'in order to continue'),
+                     parent = self)
+            event.Veto()
+            return
+
+        self.parent.SwitchEnv('target')
+        
+    def OnEnterPage(self, event=None):
+        global maptype
+        global src_map
+        global tgt_map
+
+        self.srcselection.SetElementList(maptype)
+        ret = RunCommand('i.group',
+                         parent = self,
+                         read = True,
+                         group = self.parent.grouppage.xygroup,
+                         flags = 'g')            
+
+        if ret:
+            self.parent.src_maps = ret.splitlines()
+        else:
+            GError(parent = self,
+                   message = _('No maps in selected group <%s>.\n'
+                               'Please edit group or select another group.') %
+                   self.parent.grouppage.xygroup)
+            return
+
+        # filter out all maps not in group
+        self.srcselection.tcp.GetElementList(elements = self.parent.src_maps)
+        src_map = self.parent.src_maps[0]
+        self.srcselection.SetValue(src_map)
+
+        self.parent.SwitchEnv('target')
+        self.tgtselection.SetElementList(maptype)
+        self.tgtselection.GetElementList()
+        self.parent.SwitchEnv('source')
+
+        if src_map == '':
+            wx.FindWindowById(wx.ID_FORWARD).Enable(False)
+        else:
+            wx.FindWindowById(wx.ID_FORWARD).Enable(True)
+
+class GCP(MapFrame, ColumnSorterMixin):
+    """!
+    Manages ground control points for georectifying. Calculates RMS statics.
+    Calls i.rectify or v.transform to georectify map.
+    """
+    def __init__(self, parent, grwiz = None, id = wx.ID_ANY,
+                 title = _("Manage Ground Control Points"),
+                 size = (700, 300), toolbars = ["gcpdisp"], Map = None, lmgr = None):
+
+        self.grwiz = grwiz # GR Wizard
+
+        if tgt_map == '':
+            self.show_target = False
+        else:
+            self.show_target = True
+        
+        #wx.Frame.__init__(self, parent, id, title, size = size, name = "GCPFrame")
+        MapFrame.__init__(self, parent = parent, title = title, size = size,
+                            Map = Map, toolbars = toolbars, lmgr = lmgr, name = 'GCPMapWindow')
+
+        #
+        # init variables
+        #
+        self.parent = parent # GMFrame
+        self.parent.gcpmanagement = self
+
+        self.grassdatabase = self.grwiz.grassdatabase
+
+        self.currentlocation = self.grwiz.currentlocation
+        self.currentmapset = self.grwiz.currentmapset
+
+        self.newlocation = self.grwiz.newlocation
+        self.newmapset = self.grwiz.newmapset
+
+        self.xylocation = self.grwiz.gisrc_dict['LOCATION_NAME']
+        self.xymapset = self.grwiz.gisrc_dict['MAPSET']
+        self.xygroup = self.grwiz.grouppage.xygroup
+        self.src_maps = self.grwiz.src_maps
+        self.extension = self.grwiz.grouppage.extension
+        self.outname = ''
+        self.VectGRList = []
+
+        self.file = {
+            'points' : os.path.join(self.grassdatabase,
+                                    self.xylocation,
+                                    self.xymapset,
+                                    'group',
+                                    self.xygroup,
+                                    'POINTS'),
+            'points_bak' : os.path.join(self.grassdatabase,
+                                    self.xylocation,
+                                    self.xymapset,
+                                    'group',
+                                    self.xygroup,
+                                    'POINTS_BAK'),
+            'rgrp' : os.path.join(self.grassdatabase,
+                                  self.xylocation,
+                                  self.xymapset,
+                                  'group',
+                                  self.xygroup,
+                                  'REF'),
+            'vgrp' : os.path.join(self.grassdatabase,
+                                  self.xylocation,
+                                  self.xymapset,
+                                  'group',
+                                  self.xygroup,
+                                  'VREF'),
+            'target' : os.path.join(self.grassdatabase,
+                                    self.xylocation,
+                                    self.xymapset,
+                                    'group',
+                                    self.xygroup,
+                                    'TARGET'),
+            }
+
+        # make a backup of the current points file
+        if os.path.exists(self.file['points']):
+            shutil.copy(self.file['points'], self.file['points_bak'])
+
+        # polynomial order transformation for georectification
+        self.gr_order = 1 
+        # interpolation method for georectification
+        self.gr_method = 'nearest'
+        # region clipping for georectified map
+        self.clip_to_region = False
+        # number of GCPs selected to be used for georectification (checked)
+        self.GCPcount = 0
+        # forward RMS error
+        self.fwd_rmserror = 0.0
+        # backward RMS error
+        self.bkw_rmserror = 0.0
+        # list map coords and ID of map display they came from
+        self.mapcoordlist = []
+        self.mapcoordlist.append([ 0,        # GCP number
+                                   0.0,      # source east
+                                   0.0,      # source north
+                                   0.0,      # target east
+                                   0.0,      # target north
+                                   0.0,      # forward error
+                                   0.0 ] )   # backward error
+
+        # init vars to highlight high RMS errors
+        self.highest_only = True
+        self.show_unused =  True
+        self.highest_key = -1
+        self.rmsthresh = 0
+        self.rmsmean = 0
+        self.rmssd = 0
+
+        self.SetTarget(self.xygroup, self.currentlocation, self.currentmapset)
+
+        self.itemDataMap = None
+
+        # images for column sorting
+        # CheckListCtrlMixin must set an ImageList first
+        self.il = self.list.GetImageList(wx.IMAGE_LIST_SMALL)
+
+        SmallUpArrow = wx.BitmapFromImage(getSmallUpArrowImage())            
+        SmallDnArrow = wx.BitmapFromImage(getSmallDnArrowImage())            
+        self.sm_dn = self.il.Add(SmallDnArrow)
+        self.sm_up = self.il.Add(SmallUpArrow)
+
+        # set mouse characteristics
+        self.mapwin = self.SrcMapWindow
+        self.mapwin.mouse['box'] = 'point'
+        self.mapwin.mouse["use"] == "pointer"
+        self.mapwin.zoomtype = 0
+        self.mapwin.pen = wx.Pen(colour='black', width=2, style=wx.SOLID)
+        self.mapwin.SetCursor(self.cursors["cross"])
+
+        self.mapwin = self.TgtMapWindow
+        
+        # set mouse characteristics
+        self.mapwin.mouse['box'] = 'point'
+        self.mapwin.mouse["use"] == "pointer"
+        self.mapwin.zoomtype = 0
+        self.mapwin.pen = wx.Pen(colour='black', width=2, style=wx.SOLID)
+        self.mapwin.SetCursor(self.cursors["cross"])
+
+        #
+        # show new display & draw map
+        #
+        if self.show_target:
+            self.MapWindow = self.TgtMapWindow
+            self.Map = self.TgtMap
+            self.OnZoomToMap(None)
+
+        self.MapWindow = self.SrcMapWindow
+        self.Map = self.SrcMap
+        self.OnZoomToMap(None)
+
+        #
+        # bindings
+        #
+        self.Bind(wx.EVT_ACTIVATE, self.OnFocus)
+        self.Bind(wx.EVT_CLOSE, self.OnQuit)
+
+    def __del__(self):
+        """!Disable GCP manager mode"""
+        self.parent.gcpmanagement = None
+        
+    def CreateGCPList(self):
+        """!Create GCP List Control"""
+
+        return GCPList(parent=self, gcp=self)
+
+    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+    def GetListCtrl(self):
+        return self.list
+        
+    def GetMapCoordList(self):
+        return self.mapcoordlist
+
+    # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
+    def GetSortImages(self):
+        return (self.sm_dn, self.sm_up)
+
+    def GetFwdError(self):
+        return self.fwd_rmserror
+        
+    def GetBkwError(self):
+        return self.bkw_rmserror
+                
+    def InitMapDisplay(self):
+        self.list.LoadData()
+        
+        # initialize column sorter
+        self.itemDataMap = self.mapcoordlist
+        ncols = self.list.GetColumnCount()
+        ColumnSorterMixin.__init__(self, ncols)
+        # init to ascending sort on first click
+        self._colSortFlag = [1] * ncols
+
+    def SetTarget(self, tgroup, tlocation, tmapset):
+        """
+        Sets rectification target to current location and mapset
+        """
+        # check to see if we are georectifying map in current working location/mapset
+        if self.newlocation == self.currentlocation and self.newmapset == self.currentmapset:
+            RunCommand('i.target',
+                       parent = self,
+                       flags = 'c',
+                       group = tgroup)
+        else:
+            self.grwiz.SwitchEnv('source')
+            RunCommand('i.target',
+                       parent = self,
+                       group = tgroup,
+                       location = tlocation,
+                       mapset = tmapset)
+            self.grwiz.SwitchEnv('target')
+
+    def AddGCP(self, event):
+        """
+        Appends an item to GCP list
+        """
+        keyval = self.list.AddGCPItem() + 1
+        # source east, source north, target east, target north, forward error, backward error
+        self.mapcoordlist.append([ keyval,             # GCP number
+                                   0.0,                # source east
+                                   0.0,                # source north
+                                   0.0,                # target east
+                                   0.0,                # target north
+                                   0.0,                # forward error
+                                   0.0 ] )             # backward error
+
+        if self.statusbarManager.GetMode() == 8: # go to
+            self.StatusbarUpdate()
+
+    def DeleteGCP(self, event):
+        """
+        Deletes selected item in GCP list
+        """
+        minNumOfItems = self.OnGROrder(None)
+
+        if self.list.GetItemCount() <= minNumOfItems:
+            GMessage(parent = self,
+                     message=_("At least %d GCPs required. Operation cancelled.") % minNumOfItems)
+            return
+
+        key = self.list.DeleteGCPItem()
+        del self.mapcoordlist[key]
+
+        # update key and GCP number
+        for newkey in range(key, len(self.mapcoordlist)):
+            index = self.list.FindItemData(-1, newkey + 1)
+            self.mapcoordlist[newkey][0] = newkey
+            self.list.SetStringItem(index, 0, str(newkey))
+            self.list.SetItemData(index, newkey)
+
+        # update selected
+        if self.list.GetItemCount() > 0:
+            if self.list.selected < self.list.GetItemCount():
+                self.list.selectedkey = self.list.GetItemData(self.list.selected)
+            else:
+                self.list.selected = self.list.GetItemCount() - 1
+                self.list.selectedkey = self.list.GetItemData(self.list.selected)
+                
+            self.list.SetItemState(self.list.selected,
+                              wx.LIST_STATE_SELECTED,
+                              wx.LIST_STATE_SELECTED)
+        else:
+            self.list.selected = wx.NOT_FOUND
+            self.list.selectedkey = -1
+
+        self.UpdateColours()
+
+        if self.statusbarManager.GetMode() == 8: # go to
+            self.StatusbarUpdate()
+            if self.list.selectedkey > 0:
+                self.statusbarManager.SetProperty('gotoGCP', self.list.selectedkey)
+
+    def ClearGCP(self, event):
+        """
+        Clears all values in selected item of GCP list and unchecks it
+        """
+        index = self.list.GetSelected()
+
+        for i in range(4):
+            self.list.SetStringItem(index, i, '0.0')
+        self.list.SetStringItem(index, 4, '')
+        self.list.SetStringItem(index, 5, '')
+        self.list.CheckItem(index, False)
+        key = self.list.GetItemData(index)
+
+        # GCP number, source E, source N, target E, target N, fwd error, bkwd error
+        self.mapcoordlist[key] = [key, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+
+    def DrawGCP(self, coordtype):
+        """
+        Updates GCP and map coord maps and redraws
+        active (checked) GCP markers
+        """
+        self.highest_only = UserSettings.Get(group='gcpman', key='rms', subkey='highestonly')
+
+        self.show_unused =  UserSettings.Get(group='gcpman', key='symbol', subkey='unused')
+        col = UserSettings.Get(group='gcpman', key='symbol', subkey='color')
+        wxLowCol = wx.Colour(col[0], col[1], col[2], 255)
+        col = UserSettings.Get(group='gcpman', key='symbol', subkey='hcolor')
+        wxHiCol = wx.Colour(col[0], col[1], col[2], 255)
+        col = UserSettings.Get(group='gcpman', key='symbol', subkey='scolor')
+        wxSelCol = wx.Colour(col[0], col[1], col[2], 255)
+        col = UserSettings.Get(group='gcpman', key='symbol', subkey='ucolor')
+        wxUnCol = wx.Colour(col[0], col[1], col[2], 255)
+        spx = UserSettings.Get(group='gcpman', key='symbol', subkey='size')
+        wpx = UserSettings.Get(group='gcpman', key='symbol', subkey='width')
+        font = self.GetFont()
+        font.SetPointSize(int(spx) + 2)
+
+        penOrig = polypenOrig = None
+
+        mapWin = None
+        
+        if coordtype == 'source':
+            mapWin = self.SrcMapWindow
+            e_idx = 1
+            n_idx = 2
+        elif coordtype == 'target':
+            mapWin = self.TgtMapWindow
+            e_idx = 3
+            n_idx = 4
+
+        if not mapWin:
+            GError(parent = self,
+                   message="%s%s." % (_("mapwin not defined for "),
+                                      str(idx)))
+            return
+
+        #for gcp in self.mapcoordlist:
+        for idx in range(self.list.GetItemCount()):
+
+            key = self.list.GetItemData(idx)
+            gcp = self.mapcoordlist[key]
+
+            if not self.list.IsChecked(idx):
+                if self.show_unused:
+                    wxCol = wxUnCol
+                else:
+                    continue
+            else:
+                if self.highest_only == True:
+                    if key == self.highest_key:
+                        wxCol = wxHiCol
+                    else:
+                        wxCol = wxLowCol
+                elif self.rmsthresh > 0:
+                    if (gcp[5] > self.rmsthresh):
+                        wxCol = wxHiCol
+                    else:
+                        wxCol = wxLowCol
+
+            if idx == self.list.selected:
+                wxCol = wxSelCol
+
+            if not penOrig:
+                penOrig = mapWin.pen
+                polypenOrig = mapWin.polypen
+                mapWin.pen = wx.Pen(colour=wxCol, width=wpx, style=wx.SOLID)
+                mapWin.polypen = wx.Pen(colour=wxCol, width=wpx, style=wx.SOLID) # ?
+
+            mapWin.pen.SetColour(wxCol)
+            mapWin.polypen.SetColour(wxCol)
+
+            coord = mapWin.Cell2Pixel((gcp[e_idx], gcp[n_idx]))
+            mapWin.DrawCross(pdc=mapWin.pdcTmp, coords=coord,
+                             size=spx, text={ 'text' : '%s' % str(gcp[0]),
+                                            'active' : True,
+                                            'font' : font,
+                                            'color': wxCol,
+                                            'coords': [coord[0] + 5,
+                                                       coord[1] + 5,
+                                                       5,
+                                                       5]})
+            
+        if penOrig:
+            mapWin.pen = penOrig
+            mapWin.polypen = polypenOrig
+        
+    def SetGCPData(self, coordtype, coord, mapdisp=None, confirm=False):
+        """
+        Inserts coordinates from file, mouse click on map, or after editing
+        into selected item of GCP list and checks it for use
+        """
+        
+        index = self.list.GetSelected()
+        if index == wx.NOT_FOUND:
+            return
+
+        coord0 = coord[0]
+        coord1 = coord[1]
+
+        key = self.list.GetItemData(index)
+        if confirm:
+            if self.MapWindow == self.SrcMapWindow:
+                currloc = _("source")
+            else:
+                currloc = _("target")
+            ret = wx.MessageBox(parent=self,
+                                caption=_("Set GCP coordinates"),
+                                message=_('Set %(coor)s coordinates for GCP No. %(key)s? \n\n'
+                                          'East: %(coor0)s \n'
+                                          'North: %(coor1)s') % \
+                                    { 'coor' : currloc,
+                                      'key' : str(key),
+                                      'coor0' : str(coord0),
+                                      'coor1' : str(coord1) },
+                                style=wx.ICON_QUESTION | wx.YES_NO | wx.CENTRE)
+
+            # for wingrass
+            if os.name == 'nt':
+                self.MapWindow.SetFocus()
+            if ret == wx.NO:
+                return
+            
+        if coordtype == 'source':
+            self.list.SetStringItem(index, 1, str(coord0))
+            self.list.SetStringItem(index, 2, str(coord1))
+            self.mapcoordlist[key][1] = coord[0]
+            self.mapcoordlist[key][2] = coord[1]
+        elif coordtype == 'target':
+            self.list.SetStringItem(index, 3, str(coord0))
+            self.list.SetStringItem(index, 4, str(coord1))
+            self.mapcoordlist[key][3] = coord[0]
+            self.mapcoordlist[key][4] = coord[1]
+            
+        self.list.SetStringItem(index, 5, '0')
+        self.list.SetStringItem(index, 6, '0')
+        self.mapcoordlist[key][5] = 0.0
+        self.mapcoordlist[key][6] = 0.0
+
+        # self.list.ResizeColumns()
+
+    def SaveGCPs(self, event):
+        """
+        Make a POINTS file or save GCP coordinates to existing POINTS file
+        """
+
+        self.GCPcount = 0
+        try:
+            f = open(self.file['points'], mode='w')
+            # use os.linesep or '\n' here ???
+            f.write('# Ground Control Points File\n')
+            f.write("# \n")
+            f.write("# target location: " + self.currentlocation + '\n')
+            f.write("# target mapset: " + self.currentmapset + '\n')
+            f.write("#\tsource\t\ttarget\t\tstatus\n")
+            f.write("#\teast\tnorth\teast\tnorth\t(1=ok, 0=ignore)\n")
+            f.write("#-----------------------     -----------------------     ---------------\n")
+
+            for index in range(self.list.GetItemCount()):
+                if self.list.IsChecked(index) == True:
+                    check = "1"
+                    self.GCPcount += 1
+                else:
+                    check = "0"
+                coord0 = self.list.GetItem(index, 1).GetText()
+                coord1 = self.list.GetItem(index, 2).GetText()
+                coord2 = self.list.GetItem(index, 3).GetText()
+                coord3 = self.list.GetItem(index, 4).GetText()
+                f.write(coord0 + ' ' + coord1 + '     ' + coord2 + ' ' + coord3 + '     ' + check + '\n')
+
+        except IOError, err:
+            GError(parent = self,
+                   message="%s <%s>. %s%s" % (_("Writing POINTS file failed"),
+                                              self.file['points'], os.linesep, err))
+            return
+
+        f.close()
+
+        # if event != None save also to backup file
+        if event:
+            shutil.copy(self.file['points'], self.file['points_bak'])
+            self.parent.goutput.WriteLog(_('POINTS file saved for group <%s>') % self.xygroup)
+            #self.SetStatusText(_('POINTS file saved'))
+
+    def ReadGCPs(self):
+        """
+        Reads GCPs and georectified coordinates from POINTS file
+        """
+        
+        self.GCPcount = 0
+
+        sourceMapWin = self.SrcMapWindow
+        targetMapWin = self.TgtMapWindow
+        #targetMapWin = self.parent.curr_page.maptree.mapdisplay.MapWindow
+
+        if not sourceMapWin:
+            GError(parent = self,
+                   message = "%s. %s%s" % (_("source mapwin not defined"),
+                                           os.linesep, err))
+        
+        if not targetMapWin:
+            GError(parent = self,
+                   message="%s. %s%s" % (_("target mapwin not defined"),
+                                         os.linesep, err))
+        
+        try:
+            f = open(self.file['points'], 'r')
+            GCPcnt = 0
+            
+            for line in f.readlines():
+                if line[0] == '#' or line =='':
+                    continue
+                line = line.replace('\n', '').strip()
+                coords = map(float, line.split())
+                if coords[4] == 1:
+                    check = True
+                    self.GCPcount +=1
+                else:
+                    check = False
+
+                self.AddGCP(event=None)
+                self.SetGCPData('source', (coords[0], coords[1]), sourceMapWin)
+                self.SetGCPData('target', (coords[2], coords[3]), targetMapWin)
+                index = self.list.GetSelected()
+                if index != wx.NOT_FOUND:
+                    self.list.CheckItem(index, check)
+                GCPcnt += 1
+
+        except IOError, err:
+            GError(parent = self,
+                   message = "%s <%s>. %s%s" % (_("Reading POINTS file failed"),
+                                                self.file['points'], os.linesep, err))
+            return
+
+        f.close()
+
+        if GCPcnt == 0:
+            # 3 gcp is minimum
+            for i in range(3):
+                self.AddGCP(None)
+
+        if self.CheckGCPcount():
+            # calculate RMS
+            self.RMSError(self.xygroup, self.gr_order)
+
+    def ReloadGCPs(self, event):
+        """!Reload data from file"""
+
+        # use backup
+        shutil.copy(self.file['points_bak'], self.file['points'])
+
+        # delete all items in mapcoordlist
+        self.mapcoordlist = []
+        self.mapcoordlist.append([ 0,        # GCP number
+                                   0.0,      # source east
+                                   0.0,      # source north
+                                   0.0,      # target east
+                                   0.0,      # target north
+                                   0.0,      # forward error
+                                   0.0 ] )   # backward error
+
+        self.list.LoadData()
+        self.itemDataMap = self.mapcoordlist
+
+        if self._col != -1:
+            self.list.ClearColumnImage(self._col)
+        self._colSortFlag = [1] * self.list.GetColumnCount()
+
+        # draw GCPs (source and target)
+        sourceMapWin = self.SrcMapWindow
+        sourceMapWin.UpdateMap(render=False, renderVector=False)
+        if self.show_target:
+            targetMapWin = self.TgtMapWindow
+            targetMapWin.UpdateMap(render=False, renderVector=False)
+    
+    def OnFocus(self, event):
+        # self.grwiz.SwitchEnv('source')
+        pass
+        
+    def OnRMS(self, event):
+        """
+        RMS button handler
+        """
+        self.RMSError(self.xygroup,self.gr_order)
+
+        sourceMapWin = self.SrcMapWindow
+        sourceMapWin.UpdateMap(render=False, renderVector=False)
+        if self.show_target:
+            targetMapWin = self.TgtMapWindow
+            targetMapWin.UpdateMap(render=False, renderVector=False)
+        
+    def CheckGCPcount(self, msg=False):
+        """
+        Checks to make sure that the minimum number of GCPs have been defined and
+        are active for the selected transformation order
+        """
+        if (self.GCPcount < 3 and self.gr_order == 1) or \
+            (self.GCPcount < 6 and self.gr_order == 2) or \
+            (self.GCPcount < 10 and self.gr_order == 3):
+            if msg:
+                GWarning(parent = self,
+                         message=_('Insufficient points defined and active (checked) '
+                                   'for selected rectification method.\n'
+                                   '3+ points needed for 1st order,\n'
+                                   '6+ points for 2nd order, and\n'
+                                   '10+ points for 3rd order.'))
+                return False
+        else:
+            return True
+
+    def OnGeorect(self, event):
+        """
+        Georectifies map(s) in group using i.rectify or v.transform
+        """
+        global maptype
+        self.SaveGCPs(None)
+        
+        if self.CheckGCPcount(msg=True) == False:
+            return
+
+        if maptype == 'cell':
+            self.grwiz.SwitchEnv('source')
+
+            if self.clip_to_region:
+                flags = "ac"
+            else:
+                flags = "a"
+
+            busy = wx.BusyInfo(message=_("Rectifying images, please wait..."),
+                               parent=self)
+            wx.Yield()
+
+            ret, msg = RunCommand('i.rectify',
+                                  parent = self,
+                                  getErrorMsg = True,
+                                  quiet = True,
+                                  group = self.xygroup,
+                                  extension = self.extension,
+                                  order = self.gr_order,
+                                  method=self.gr_method,
+                                  flags = flags)
+
+            busy.Destroy()
+
+            # provide feedback on failure
+            if ret != 0:
+                print >> sys.stderr, msg
+                
+        elif maptype == 'vector':
+            outmsg = ''
+            # loop through all vectors in VREF
+            # and move resulting vector to target location
+            
+            # make sure current mapset has a vector folder
+            if not os.path.isdir(os.path.join(self.grassdatabase,
+                                              self.currentlocation,
+                                              self.currentmapset,
+                                              'vector')):
+                os.mkdir(os.path.join(self.grassdatabase,
+                                      self.currentlocation,
+                                      self.currentmapset,
+                                      'vector'))
+
+            self.grwiz.SwitchEnv('source')
+            
+            # make list of vectors to georectify from VREF
+            f = open(self.file['vgrp'])
+            vectlist = []
+            try:
+                for vect in f.readlines():
+                    vect = vect.strip('\n')
+                    if len(vect) < 1:
+                        continue
+                    vectlist.append(vect)
+            finally:
+                f.close()
+                               
+            # georectify each vector in VREF using v.transform
+            for vect in vectlist:
+                self.outname = vect + '_' + self.extension
+                self.parent.goutput.WriteLog(text = _('Transforming <%s>...') % vect,
+                                             switchPage = True)
+                msg = err = ''
+                
+                ret, out, err = RunCommand('v.transform',
+                                           overwrite = True,
+                                           input = vect,
+                                           output = self.outname,
+                                           pointsfile = self.file['points'],
+                                           getErrorMsg = True, read = True) 
+                
+                if ret == 0:
+                    self.VectGRList.append(self.outname)
+                    # note: WriteLog doesn't handle GRASS_INFO_PERCENT well, so using a print here
+                    # self.parent.goutput.WriteLog(text = _(err), switchPage = True)
+                    self.parent.goutput.WriteLog(text = out, switchPage = True)
+                else:
+                    self.parent.goutput.WriteError(_('Georectification of vector map <%s> failed') %
+                                                   self.outname)
+                    self.parent.goutput.WriteError(err)
+                
+                # FIXME
+                # Copying database information not working. 
+                # Does not copy from xy location to current location
+                # TODO: replace $GISDBASE etc with real paths
+                #                xyLayer = []
+                #                for layer in grass.vector_db(map = vect).itervalues():
+                #                    xyLayer.append((layer['driver'],
+                #                                    layer['database'],
+                #                                    layer['table']))
+                    
+                    
+                    #                dbConnect = grass.db_connection()
+                    #                print 'db connection =', dbConnect
+                    #                for layer in xyLayer:     
+                    #                    self.parent.goutput.RunCmd(['db.copy',
+                    #                                                '--q',
+                    #                                                '--o',
+                    #                                                'from_driver=%s' % layer[0],
+                    #                                                'from_database=%s' % layer[1],
+                    #                                                'from_table=%s' % layer[2],
+                    #                                                'to_driver=%s' % dbConnect['driver'],
+                    #                                                'to_database=%s' % dbConnect['database'],
+                    #                                                'to_table=%s' % layer[2] + '_' + self.extension])
+
+            # copy all georectified vectors from source location to current location
+            for name in self.VectGRList:
+                xyvpath = os.path.join(self.grassdatabase,
+                                       self.xylocation,
+                                       self.xymapset,
+                                       'vector',
+                                       name)
+                vpath = os.path.join(self.grassdatabase,
+                                     self.currentlocation,
+                                     self.currentmapset,
+                                     'vector',
+                                     name)
+                                    
+                if os.path.isdir(vpath):
+                    self.parent.goutput.WriteWarning(_('Vector map <%s> already exists. '
+                                                       'Change extension name and '
+                                                       'georectify again.') % self.outname)
+                    break
+                else:
+                    # use shutil.copytree() because shutil.move() deletes src dir
+                    shutil.copytree(xyvpath, vpath)
+
+                # TODO: connect vectors to copied tables with v.db.connect
+                                                   
+            GMessage(_('For all vector maps georectified successfully,') + '\n' +
+                     _('you will need to copy any attribute tables') + '\n' +
+                     _('and reconnect them to the georectified vectors'),
+                     parent = self)
+        
+        self.grwiz.SwitchEnv('target')
+
+    def OnGeorectDone(self, **kargs):
+        """!Print final message"""
+        global maptype
+        if maptype == 'cell':
+            return
+        
+        returncode = kargs['returncode']
+        
+        if returncode == 0:
+            self.VectGRList.append(self.outname)
+            print '*****vector list = ' + str(self.VectGRList)
+        else:
+            self.parent.goutput.WriteError(_('Georectification of vector map <%s> failed') %
+                                                   self.outname)
+
+         
+    def OnSettings(self, event):
+        """!GCP Manager settings"""
+        dlg = GrSettingsDialog(parent=self, id=wx.ID_ANY, title=_('GCP Manager settings'))
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            pass
+        
+        dlg.Destroy()
+
+    def UpdateColours(self, srcrender=False, srcrenderVector=False,
+                            tgtrender=False, tgtrenderVector=False):
+        """!update colours"""
+        highest_fwd_err = 0.0
+        self.highest_key = 0
+        highest_idx = 0
+
+        for index in range(self.list.GetItemCount()):
+            if self.list.IsChecked(index):
+                key = self.list.GetItemData(index)
+                fwd_err = self.mapcoordlist[key][5]
+
+                if self.highest_only == True:
+                    self.list.SetItemTextColour(index, wx.BLACK)
+                    if highest_fwd_err < fwd_err:
+                        highest_fwd_err = fwd_err
+                        self.highest_key = key
+                        highest_idx = index
+                elif self.rmsthresh > 0:
+                    if (fwd_err > self.rmsthresh):
+                        self.list.SetItemTextColour(index, wx.RED)
+                    else:
+                        self.list.SetItemTextColour(index, wx.BLACK)
+            else:
+                self.list.SetItemTextColour(index, wx.BLACK)
+        
+        if self.highest_only and highest_fwd_err > 0.0:
+            self.list.SetItemTextColour(highest_idx, wx.RED)
+
+        sourceMapWin = self.SrcMapWindow
+        sourceMapWin.UpdateMap(render=srcrender, renderVector=srcrenderVector)
+        if self.show_target:
+            targetMapWin = self.TgtMapWindow
+            targetMapWin.UpdateMap(render=tgtrender, renderVector=tgtrenderVector)
+
+    def OnQuit(self, event):
+        """!Quit georectifier"""
+        ret = wx.MessageBox(parent=self,
+                      caption=_("Quit GCP Manager"),
+                      message=_('Save ground control points?'),
+                      style=wx.ICON_QUESTION | wx.YES_NO | wx.CANCEL | wx.CENTRE)
+
+        if ret != wx.CANCEL:
+            if ret == wx.YES:
+                self.SaveGCPs(None)
+            elif ret == wx.NO:
+                # restore POINTS file from backup
+                if os.path.exists(self.file['points_bak']):
+                    shutil.copy(self.file['points_bak'], self.file['points'])
+
+            if os.path.exists(self.file['points_bak']):
+                os.unlink(self.file['points_bak'])
+
+            self.SrcMap.Clean()
+            self.TgtMap.Clean()
+
+            self.grwiz.Cleanup()
+
+            self.Destroy()
+
+        #event.Skip()
+
+    def OnGROrder(self, event):
+        """
+        sets transformation order for georectifying
+        """
+        if event:
+            self.gr_order = event.GetInt() + 1
+
+        numOfItems = self.list.GetItemCount()
+        minNumOfItems = numOfItems
+        
+        if self.gr_order == 1:
+            minNumOfItems = 3
+            # self.SetStatusText(_('Insufficient points, 3+ points needed for 1st order'))
+
+        elif self.gr_order == 2:
+            minNumOfItems = 6
+            diff = 6 - numOfItems
+            # self.SetStatusText(_('Insufficient points, 6+ points needed for 2nd order'))
+
+        elif self.gr_order == 3:
+            minNumOfItems = 10
+            # self.SetStatusText(_('Insufficient points, 10+ points needed for 3rd order'))
+
+        for i in range(minNumOfItems - numOfItems):
+            self.AddGCP(None)
+
+        return minNumOfItems
+    
+    def RMSError(self, xygroup, order):
+        """
+        Uses m.transform to calculate forward and backward error for each used GCP
+        in POINTS file and insert error values into GCP list.
+        Calculates total forward and backward RMS error for all used points
+        """
+        # save GCPs to points file to make sure that all checked GCPs are used
+        self.SaveGCPs(None)
+        #self.SetStatusText('')
+        
+        if self.CheckGCPcount(msg=True) == False:
+            return
+        
+        # get list of forward and reverse rms error values for each point
+        self.grwiz.SwitchEnv('source')
+        
+        ret = RunCommand('m.transform',
+                         parent = self,
+                         read = True,
+                         group = xygroup,
+                         order = order)
+        
+        self.grwiz.SwitchEnv('target')
+
+        if ret:
+            errlist = ret.splitlines()
+        else:
+            GError(parent = self,
+                   message=_('Could not calculate RMS Error.\n'
+                             'Possible error with m.transform.'))
+            return
+        
+        # insert error values into GCP list for checked items
+        sdfactor = float(UserSettings.Get(group='gcpman', key='rms', subkey='sdfactor'))
+        GCPcount = 0
+        sumsq_fwd_err = 0.0
+        sumsq_bkw_err = 0.0
+        sum_fwd_err = 0.0
+        highest_fwd_err = 0.0
+        self.highest_key = 0
+        highest_idx = 0
+        
+        for index in range(self.list.GetItemCount()):
+            key = self.list.GetItemData(index)
+            if self.list.IsChecked(index):
+                fwd_err, bkw_err = errlist[GCPcount].split()
+                self.list.SetStringItem(index, 5, fwd_err)
+                self.list.SetStringItem(index, 6, bkw_err)
+                self.mapcoordlist[key][5] = float(fwd_err)
+                self.mapcoordlist[key][6] = float(bkw_err)
+                self.list.SetItemTextColour(index, wx.BLACK)
+                if self.highest_only:
+                    if highest_fwd_err < float(fwd_err):
+                        highest_fwd_err = float(fwd_err)
+                        self.highest_key = key
+                        highest_idx = index
+                        
+                sumsq_fwd_err += float(fwd_err)**2
+                sumsq_bkw_err += float(bkw_err)**2
+                sum_fwd_err += float(fwd_err)
+                GCPcount += 1
+            else:
+                self.list.SetStringItem(index, 5, '')
+                self.list.SetStringItem(index, 6, '')
+                self.mapcoordlist[key][5] = 0.0
+                self.mapcoordlist[key][6] = 0.0
+                self.list.SetItemTextColour(index, wx.BLACK)
+
+        # SD
+        if GCPcount > 0:
+            sum_fwd_err /= GCPcount
+            self.rmsmean = sum_fwd_err /GCPcount
+            self.rmssd = (((sumsq_fwd_err/GCPcount) - self.rmsmean**2)**0.5)
+            self.rmsthresh = self.rmsmean + sdfactor * self.rmssd
+        else:
+            self.rmsthresh = 0
+            self.rmsmean = 0
+            self.rmssd = 0
+
+        if self.highest_only and highest_fwd_err > 0.0:
+            self.list.SetItemTextColour(highest_idx, wx.RED)
+        elif GCPcount > 0 and self.rmsthresh > 0 and not self.highest_only:
+            for index in range(self.list.GetItemCount()):
+                if self.list.IsChecked(index):
+                    key = self.list.GetItemData(index)
+                    if (self.mapcoordlist[key][5] > self.rmsthresh):
+                        self.list.SetItemTextColour(index, wx.RED)
+            
+        # calculate global RMS error (geometric mean)
+        self.fwd_rmserror = round((sumsq_fwd_err/GCPcount)**0.5,4)
+        self.bkw_rmserror = round((sumsq_bkw_err/GCPcount)**0.5,4)
+        self.list.ResizeColumns()
+
+    def GetNewExtent(self, region, map = None):
+
+        coord_file = utils.GetTempfile()
+        newreg = { 'n' : 0.0, 's' : 0.0, 'e' : 0.0, 'w' : 0.0,}
+
+        try:
+            f = open(coord_file, mode='w')
+            # NW corner        
+            f.write(str(region['e']) + " " + str(region['n']) + "\n")
+            # NE corner        
+            f.write(str(region['e']) + " " + str(region['s']) + "\n")
+            # SW corner        
+            f.write(str(region['w']) + " " + str(region['n']) + "\n")
+            # SE corner        
+            f.write(str(region['w']) + " " + str(region['s']) + "\n")
+        finally:
+            f.close()
+
+        # save GCPs to points file to make sure that all checked GCPs are used
+        self.SaveGCPs(None)
+
+        order = self.gr_order
+        self.gr_order = 1
+
+        if self.CheckGCPcount(msg=True) == False:
+            self.gr_order = order
+            return
+        
+        self.gr_order = order
+
+        # get list of forward and reverse rms error values for each point
+        self.grwiz.SwitchEnv('source')
+        
+        if map == 'source':
+            ret = RunCommand('m.transform',
+                             parent = self,
+                             read = True,
+                             group = self.xygroup,
+                             order = 1,
+                             format = 'dst',
+                             coords = coord_file)
+
+        elif map == 'target':
+            ret = RunCommand('m.transform',
+                             parent = self,
+                             read = True,
+                             group = self.xygroup,
+                             order = 1,
+                             flags = 'r',
+                             format = 'src',
+                             coords = coord_file)
+
+        os.unlink(coord_file)
+        
+        self.grwiz.SwitchEnv('target')
+
+        if ret:
+            errlist = ret.splitlines()
+        else:
+            GError(parent = self,
+                   message=_('Could not calculate new extends.\n'
+                             'Possible error with m.transform.'))
+            return
+
+        # fist corner
+        e, n = errlist[0].split()
+        fe = float(e)
+        fn = float(n)
+        newreg['n'] = fn
+        newreg['s'] = fn
+        newreg['e'] = fe
+        newreg['w'] = fe
+        # other three corners
+        for i in range(1, 4):
+            e, n = errlist[i].split()
+            fe = float(e)
+            fn = float(n)
+            if fe < newreg['w']:
+                newreg['w'] = fe
+            if fe > newreg['e']:
+                newreg['e'] = fe
+            if fn < newreg['s']:
+                newreg['s'] = fn
+            if fn > newreg['n']:
+                newreg['n'] = fn
+
+        return newreg
+
+    def OnHelp(self, event):
+        """!Show GCP Manager manual page"""
+        cmdlist = ['g.manual', 'entry=wxGUI.GCP_Manager']
+        self.parent.goutput.RunCmd(cmdlist, compReg=False,
+                                       switchPage=False)
+
+    def OnUpdateActive(self, event):
+
+        if self.activemap.GetSelection() == 0:
+            self.MapWindow = self.SrcMapWindow
+            self.Map = self.SrcMap
+        else:
+            self.MapWindow = self.TgtMapWindow
+            self.Map = self.TgtMap
+
+        self.UpdateActive(self.MapWindow)
+        # for wingrass
+        if os.name == 'nt':
+            self.MapWindow.SetFocus()
+
+    def UpdateActive(self, win):
+
+        # optionally disable tool zoomback tool
+        self.GetMapToolbar().Enable('zoomback', enable = (len(self.MapWindow.zoomhistory) > 1))
+
+        if self.activemap.GetSelection() != (win == self.TgtMapWindow):
+            self.activemap.SetSelection(win == self.TgtMapWindow)
+        self.StatusbarUpdate()
+
+    def AdjustMap(self, newreg):
+        """!Adjust map window to new extents
+        """
+
+        # adjust map window
+        self.Map.region['n'] = newreg['n']
+        self.Map.region['s'] = newreg['s']
+        self.Map.region['e'] = newreg['e']
+        self.Map.region['w'] = newreg['w']
+
+        self.MapWindow.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
+                 self.Map.region['e'], self.Map.region['w'])
+
+        # LL locations
+        if self.Map.projinfo['proj'] == 'll':
+            if newreg['n'] > 90.0:
+                newreg['n'] = 90.0
+            if newreg['s'] < -90.0:
+                newreg['s'] = -90.0
+        
+        ce = newreg['w'] + (newreg['e'] - newreg['w']) / 2
+        cn = newreg['s'] + (newreg['n'] - newreg['s']) / 2
+        
+        # calculate new center point and display resolution
+        self.Map.region['center_easting'] = ce
+        self.Map.region['center_northing'] = cn
+        self.Map.region["ewres"] = (newreg['e'] - newreg['w']) / self.Map.width
+        self.Map.region["nsres"] = (newreg['n'] - newreg['s']) / self.Map.height
+        self.Map.AlignExtentFromDisplay()
+
+        self.MapWindow.ZoomHistory(self.Map.region['n'], self.Map.region['s'],
+                 self.Map.region['e'], self.Map.region['w'])
+
+        if self.MapWindow.redrawAll is False:
+            self.MapWindow.redrawAll = True
+
+        self.MapWindow.UpdateMap()
+        self.StatusbarUpdate()
+
+    def OnZoomToSource(self, event):
+        """!Set target map window to match extents of source map window
+        """
+
+        if not self.MapWindow == self.TgtMapWindow:
+            self.MapWindow = self.TgtMapWindow
+            self.Map = self.TgtMap
+            self.UpdateActive(self.TgtMapWindow)
+
+        # get new N, S, E, W for target
+        newreg = self.GetNewExtent(self.SrcMap.region, 'source')
+        if newreg:
+            self.AdjustMap(newreg)
+
+    def OnZoomToTarget(self, event):
+        """!Set source map window to match extents of target map window
+        """
+
+        if not self.MapWindow == self.SrcMapWindow:
+            self.MapWindow = self.SrcMapWindow
+            self.Map = self.SrcMap
+            self.UpdateActive(self.SrcMapWindow)
+
+        # get new N, S, E, W for target
+        newreg = self.GetNewExtent(self.TgtMap.region, 'target')
+        if newreg:
+            self.AdjustMap(newreg)
+
+    def OnZoomMenuGCP(self, event):
+        """!Popup Zoom menu
+        """
+        point = wx.GetMousePosition()
+        zoommenu = wx.Menu()
+        # Add items to the menu
+
+        zoomsource = wx.MenuItem(zoommenu, wx.ID_ANY, _('Adjust source display to target display'))
+        zoommenu.AppendItem(zoomsource)
+        self.Bind(wx.EVT_MENU, self.OnZoomToTarget, zoomsource)
+
+        zoomtarget = wx.MenuItem(zoommenu, wx.ID_ANY, _('Adjust target display to source display'))
+        zoommenu.AppendItem(zoomtarget)
+        self.Bind(wx.EVT_MENU, self.OnZoomToSource, zoomtarget)
+
+        # Popup the menu. If an item is selected then its handler
+        # will be called before PopupMenu returns.
+        self.PopupMenu(zoommenu)
+        zoommenu.Destroy()
+        
+    def OnDispResize(self, event):
+        """!GCP Map Display resized, adjust Map Windows
+        """
+        if self.GetMapToolbar():
+            srcwidth, srcheight = self.SrcMapWindow.GetSize()
+            tgtwidth, tgtheight = self.TgtMapWindow.GetSize()
+            srcwidth = (srcwidth + tgtwidth) / 2
+            self._mgr.GetPane("target").Hide()
+            self._mgr.Update()
+            self._mgr.GetPane("source").BestSize((srcwidth, srcheight))
+            self._mgr.GetPane("target").BestSize((srcwidth, tgtheight))
+            if self.show_target:
+                self._mgr.GetPane("target").Show()
+            self._mgr.Update()
+        pass
+
+class GCPList(wx.ListCtrl,
+              CheckListCtrlMixin,
+              ListCtrlAutoWidthMixin):
+              
+    def __init__(self, parent, gcp, id=wx.ID_ANY,
+                 pos=wx.DefaultPosition, size=wx.DefaultSize,
+                 style=wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_HRULES |
+                 wx.LC_SINGLE_SEL):
+
+        wx.ListCtrl.__init__(self, parent, id, pos, size, style)
+
+        self.gcp = gcp # GCP class
+        self.render = True
+
+        # Mixin settings
+        CheckListCtrlMixin.__init__(self)
+        ListCtrlAutoWidthMixin.__init__(self)
+        # TextEditMixin.__init__(self)
+
+        # tracks whether list items are checked or not
+        self.CheckList = [] 
+
+        self._Create()
+
+        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
+        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
+        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
+
+        self.selected = wx.NOT_FOUND
+        self.selectedkey = -1
+
+    def _Create(self):
+
+        if 0:
+            # normal, simple columns
+            idx_col = 0
+            for col in (_('use'),
+                _('source E'),
+                _('source N'),
+                _('target E'),
+                _('target N'),
+                _('Forward error'),
+                _('Backward error')):
+                self.InsertColumn(idx_col, col)
+                idx_col += 1
+        else:
+            # the hard way: we want images on the column header
+            info = wx.ListItem()
+            info.SetMask(wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT)
+            info.SetImage(-1)
+            info.m_format = wx.LIST_FORMAT_LEFT
+
+            idx_col = 0
+            for lbl in (_('use'),
+                _('source E'),
+                _('source N'),
+                _('target E'),
+                _('target N'),
+                _('Forward error'),
+                _('Backward error')):
+                info.SetText(lbl)
+                self.InsertColumnInfo(idx_col, info)
+                idx_col += 1
+
+    def LoadData(self):
+        """!Load data into list"""
+        self.DeleteAllItems()
+
+        self.render = False
+        if os.path.isfile(self.gcp.file['points']):
+            self.gcp.ReadGCPs()
+        else:
+            # 3 gcp is minimum
+            for i in range(3):
+                self.gcp.AddGCP(None)
+
+        # select first point by default
+        self.selected = 0
+        self.selectedkey = self.GetItemData(self.selected)
+        self.SetItemState(self.selected,
+                          wx.LIST_STATE_SELECTED,
+                          wx.LIST_STATE_SELECTED)
+
+        self.ResizeColumns()
+        self.render = True
+
+    def OnCheckItem(self, index, flag):
+        """!Item is checked/unchecked"""
+
+        if self.render:
+            # redraw points
+            sourceMapWin = self.gcp.SrcMapWindow
+            sourceMapWin.UpdateMap(render=False, renderVector=False)
+            if self.gcp.show_target:
+                targetMapWin = self.gcp.TgtMapWindow
+                targetMapWin.UpdateMap(render=False, renderVector=False)
+
+        pass
+    
+    def AddGCPItem(self):
+        """
+        Appends an item to GCP list
+        """
+        self.selectedkey = self.GetItemCount() + 1
+
+        self.Append([str(self.selectedkey),    # GCP number
+                     '0.0',                # source E
+                     '0.0',                # source N
+                     '0.0',                # target E
+                     '0.0',                # target N
+                     '',                   # forward error
+                     ''])                  # backward error
+
+        self.selected = self.GetItemCount() - 1
+        self.SetItemData(self.selected, self.selectedkey)
+
+        self.SetItemState(self.selected,
+                          wx.LIST_STATE_SELECTED,
+                          wx.LIST_STATE_SELECTED)
+
+        self.ResizeColumns()
+
+        return self.selected
+
+    def DeleteGCPItem(self):
+        """
+        Deletes selected item in GCP list
+        """
+        if self.selected == wx.NOT_FOUND:
+            return
+
+        key = self.GetItemData(self.selected)
+        self.DeleteItem(self.selected)
+
+        return key
+        
+    def ResizeColumns(self):
+        """!Resize columns"""
+        minWidth = [90, 120]
+        for i in range(self.GetColumnCount()):
+            self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
+            # first column is checkbox, don't set to minWidth
+            if i > 0 and self.GetColumnWidth(i) < minWidth[i > 4]:
+                self.SetColumnWidth(i, minWidth[i > 4])
+
+        self.SendSizeEvent()
+
+    def GetSelected(self):
+        """!Get index of selected item"""
+        return self.selected
+
+    def OnItemSelected(self, event):
+        """
+        Item selected
+        """
+
+        if self.render and self.selected != event.GetIndex():
+            self.selected = event.GetIndex()
+            self.selectedkey = self.GetItemData(self.selected)
+            sourceMapWin = self.gcp.SrcMapWindow
+            sourceMapWin.UpdateMap(render=False, renderVector=False)
+            if self.gcp.show_target:
+                targetMapWin = self.gcp.TgtMapWindow
+                targetMapWin.UpdateMap(render=False, renderVector=False)
+
+        event.Skip()
+
+    def OnItemActivated(self, event):
+        """
+        When item double clicked, open editor to update coordinate values
+        """
+        coords = []
+        index = event.GetIndex()
+        key = self.GetItemData(index)
+        changed = False
+
+        for i in range(1, 5):
+            coords.append(self.GetItem(index, i).GetText())
+
+        dlg = EditGCP(parent=self, id=wx.ID_ANY, data=coords, gcpno=key)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            values = dlg.GetValues() # string
+            
+            if len(values) == 0:
+                GError(parent = self,
+                       message=_("Invalid coordinate value. Operation cancelled."))
+            else:
+                for i in range(len(values)):
+                    if values[i] != coords[i]:
+                        self.SetStringItem(index, i + 1, values[i])
+                        changed = True
+
+                if changed:
+                    # reset RMS and update mapcoordlist
+                    self.SetStringItem(index, 5, '')
+                    self.SetStringItem(index, 6, '')
+                    key = self.GetItemData(index)
+                    self.gcp.mapcoordlist[key] = [key,
+                                                  float(values[0]),
+                                                  float(values[1]),
+                                                  float(values[2]),
+                                                  float(values[3]),
+                                                  0.0,
+                                                  0.0]
+                    self.gcp.UpdateColours()
+        
+    def OnColClick(self, event):
+        """!ListCtrl forgets selected item..."""
+        self.selected = self.FindItemData(-1, self.selectedkey)
+        self.SetItemState(self.selected,
+                          wx.LIST_STATE_SELECTED,
+                          wx.LIST_STATE_SELECTED)
+        event.Skip()
+
+class VectGroup(wx.Dialog):
+    """
+    Dialog to create a vector group (VREF file) for georectifying
+
+    @todo Replace by g.group
+    """
+    def __init__(self, parent, id, grassdb, location, mapset, group,
+                 style=wx.DEFAULT_DIALOG_STYLE):
+        
+        wx.Dialog.__init__(self, parent, id, style=style,
+                           title = _("Create vector map group"))
+        
+        self.grassdatabase = grassdb
+        self.xylocation = location
+        self.xymapset = mapset
+        self.xygroup = group
+        
+        #
+        # get list of valid vector directories
+        #
+        vectlist = os.listdir(os.path.join(self.grassdatabase,
+                                           self.xylocation,
+                                           self.xymapset,
+                                           'vector'))
+        for dir in vectlist:
+            if not os.path.isfile(os.path.join(self.grassdatabase,
+                                           self.xylocation,
+                                           self.xymapset,
+                                           'vector',
+                                           dir,
+                                           'coor')):
+                vectlist.remove(dir)
+        
+        utils.ListSortLower(vectlist)
+        
+        # path to vref file
+        self.vgrpfile = os.path.join(self.grassdatabase,
+                                     self.xylocation,
+                                     self.xymapset,
+                                     'group',
+                                     self.xygroup,
+                                     'VREF')
+        
+        #
+        # buttons
+        #
+        self.btnCancel = wx.Button(parent = self,
+                                   id = wx.ID_CANCEL)
+        self.btnOK = wx.Button(parent = self,
+                                   id = wx.ID_OK)
+        self.btnOK.SetDefault()
+
+
+        #
+        # list of vector maps
+        #
+        self.listMap = wx.CheckListBox(parent = self, id = wx.ID_ANY,
+                                      choices = vectlist)
+        
+        if os.path.isfile(self.vgrpfile):
+            f = open(self.vgrpfile)
+            try:
+                checked = []
+                for line in f.readlines():
+                    line = line.replace('\n', '')
+                    if len(line) < 1:
+                        continue
+                    checked.append(line)
+                self.listMap.SetCheckedStrings(checked)
+            finally:
+                f.close()
+                
+        line = wx.StaticLine(parent = self,
+                             id = wx.ID_ANY, size = (20, -1),
+                             style = wx.LI_HORIZONTAL)
+
+        #
+        # layout
+        #
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        box.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                     label = _('Select vector map(s) to add to group:')),
+                flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT,
+                border = 5)
+
+        box.Add(item = self.listMap,
+                flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT,
+                border = 5)
+
+        
+        sizer.Add(box, flag = wx.ALIGN_RIGHT | wx.ALL,
+                  border = 3)
+        
+        sizer.Add(item = line, proportion = 0,
+                  flag = wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT,
+                  border = 5)
+        
+        # buttons
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOK)
+        btnSizer.Realize()
+
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER,
+                  border = 5)
+        
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        self.Layout()
+        
+    def MakeVGroup(self):
+        """!Create VREF file"""
+        vgrouplist = []
+        for item in range(self.listMap.GetCount()):
+            if not self.listMap.IsChecked(item):
+                continue
+            vgrouplist.append(self.listMap.GetString(item))
+        
+        f = open(self.vgrpfile, mode='w')
+        try:
+            for vect in vgrouplist:
+                f.write(vect + '\n')
+        finally:
+            f.close()
+        
+class EditGCP(wx.Dialog):
+    def __init__(self, parent, data, gcpno, id=wx.ID_ANY,
+                 title=_("Edit GCP"),
+                 style=wx.DEFAULT_DIALOG_STYLE):
+        """!Dialog for editing GPC and map coordinates in list control"""
+
+        wx.Dialog.__init__(self, parent, id, title=title, style=style)
+
+        panel = wx.Panel(parent=self)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        box = wx.StaticBox (parent=panel, id=wx.ID_ANY,
+                            label=" %s %s " % (_("Ground Control Point No."), str(gcpno)))
+        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+        # source coordinates
+        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
+       
+        self.xcoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
+        self.ycoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
+        self.ecoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
+        self.ncoord = wx.TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
+
+        # swap source N, target E
+        tmp_coord = data[1]
+        data[1] = data[2]
+        data[2] = tmp_coord
+        
+        row = 0
+        col = 0
+        idx = 0
+        for label, win in ((_("source E:"), self.xcoord),
+                           (_("target E:"), self.ecoord),
+                           (_("source N:"), self.ycoord),
+                           (_("target N:"), self.ncoord)):
+            label = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                  label=label)
+            gridSizer.Add(item=label,
+                          flag=wx.ALIGN_CENTER_VERTICAL,
+                          pos=(row, col))
+
+            col += 1
+            win.SetValue(str(data[idx]))
+
+            gridSizer.Add(item=win,
+                          pos=(row, col))
+
+            col += 1
+            idx += 1
+
+            if col > 3:
+                row += 1
+                col = 0
+
+        boxSizer.Add(item=gridSizer, proportion=1,
+                  flag=wx.EXPAND | wx.ALL, border=5)
+
+        sizer.Add(item=boxSizer, proportion=1,
+                  flag=wx.EXPAND | wx.ALL, border=5)
+
+        #
+        # buttons
+        #
+        self.btnCancel = wx.Button(panel, wx.ID_CANCEL)
+        self.btnOk = wx.Button(panel, wx.ID_OK)
+        self.btnOk.SetDefault()
+
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOk)
+        btnSizer.Realize()
+
+        sizer.Add(item=btnSizer, proportion=0,
+                  flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
+
+        panel.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def GetValues(self, columns=None):
+        """!Return list of values (as strings).
+        """
+        valuelist = []
+        try:
+            float(self.xcoord.GetValue())
+            float(self.ycoord.GetValue())
+            float(self.ecoord.GetValue())
+            float(self.ncoord.GetValue())
+        except ValueError:
+            return valuelist
+
+        valuelist.append(self.xcoord.GetValue())
+        valuelist.append(self.ycoord.GetValue())
+        valuelist.append(self.ecoord.GetValue())
+        valuelist.append(self.ncoord.GetValue())
+
+        return valuelist
+
+class GrSettingsDialog(wx.Dialog):
+    def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize,
+                 style=wx.DEFAULT_DIALOG_STYLE):
+        wx.Dialog.__init__(self, parent, id, title, pos, size, style)
+        """
+        Dialog to set profile text options: font, title
+        and font size, axis labels and font size
+        """
+        #
+        # initialize variables
+        #
+        self.parent = parent
+        self.new_src_map = src_map
+        self.new_tgt_map = tgt_map
+        self.sdfactor = 0
+
+        self.symbol = {}
+        
+        self.methods = ["nearest",
+                        "bilinear",
+                        "bilinear_f",
+                        "cubic", 
+                        "cubic_f",
+                        "lanczos",
+                        "lanczos_f"]
+
+        # notebook
+        notebook = wx.Notebook(parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT)
+        self.__CreateSymbologyPage(notebook)
+        self.__CreateRectificationPage(notebook)
+
+        # buttons
+        btnSave = wx.Button(self, wx.ID_SAVE)
+        btnApply = wx.Button(self, wx.ID_APPLY)
+        btnClose = wx.Button(self, wx.ID_CLOSE)
+        btnApply.SetDefault()
+
+        # bindings
+        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
+        btnApply.SetToolTipString(_("Apply changes for the current session"))
+        btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
+        btnSave.SetToolTipString(_("Apply and save changes to user settings file (default for next sessions)"))
+        btnClose.Bind(wx.EVT_BUTTON, self.OnClose)
+        btnClose.SetToolTipString(_("Close dialog"))
+
+        # sizers
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(btnApply, flag=wx.LEFT | wx.RIGHT, border=5)
+        btnSizer.Add(btnSave, flag=wx.LEFT | wx.RIGHT, border=5)
+        btnSizer.Add(btnClose, flag=wx.LEFT | wx.RIGHT, border=5)
+        
+        # sizers
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item=notebook, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
+        mainSizer.Add(item=btnSizer, proportion=0,
+                       flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
+        #              flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+        
+    def __CreateSymbologyPage(self, notebook):
+        """!Create notebook page with symbology settings"""
+
+        panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
+        notebook.AddPage(page=panel, text=_("Symbology"))
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        rmsgridSizer = wx.GridBagSizer(vgap=5, hgap=5)
+        rmsgridSizer.AddGrowableCol(1)
+
+        # highlight only highest forward RMS error
+        self.highlighthighest = wx.CheckBox(parent=panel, id=wx.ID_ANY,
+                                label=_("Highlight highest RMS error only"))
+        hh = UserSettings.Get(group='gcpman', key='rms', subkey='highestonly')
+        self.highlighthighest.SetValue(hh)
+        rmsgridSizer.Add(item=self.highlighthighest, flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 0))
+
+        # RMS forward error threshold
+        rmslabel = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Highlight RMS error > M + SD * factor:"))
+        rmslabel.SetToolTip(wx.ToolTip(_("Highlight GCPs with an RMS error larger than \n"
+                              "mean + standard deviation * given factor. \n"
+                              "Recommended values for this factor are between 1 and 2.")))
+        rmsgridSizer.Add(item=rmslabel, flag=wx.ALIGN_CENTER_VERTICAL, pos=(1, 0))
+        sdfactor = UserSettings.Get(group='gcpman', key='rms', subkey='sdfactor')
+        self.rmsWin = wx.TextCtrl(parent=panel, id=wx.ID_ANY,
+                       size=(70,-1), style=wx.TE_NOHIDESEL)
+        self.rmsWin.SetValue("%s" % str(sdfactor))
+        if (self.parent.highest_only == True):
+           self.rmsWin.Disable()
+
+        self.symbol['sdfactor'] = self.rmsWin.GetId()
+        rmsgridSizer.Add(item=self.rmsWin, flag=wx.ALIGN_RIGHT, pos=(1, 1))
+        sizer.Add(item=rmsgridSizer, flag=wx.EXPAND | wx.ALL, border=5)
+
+        box = wx.StaticBox(parent=panel, id=wx.ID_ANY,
+                           label=" %s " % _("Symbol settings"))
+        boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
+        gridSizer.AddGrowableCol(1)
+
+        #
+        # general symbol color
+        #
+        row = 0
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Color:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        col = UserSettings.Get(group='gcpman', key='symbol', subkey='color')
+        colWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
+                                   colour=wx.Colour(col[0],
+                                                    col[1],
+                                                    col[2],
+                                                    255))
+        self.symbol['color'] = colWin.GetId()
+        gridSizer.Add(item=colWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+
+        #
+        # symbol color for high forward RMS error
+        #
+        row += 1
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Color for high RMS error:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        hcol = UserSettings.Get(group='gcpman', key='symbol', subkey='hcolor')
+        hcolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
+                                   colour=wx.Colour(hcol[0],
+                                                    hcol[1],
+                                                    hcol[2],
+                                                    255))
+        self.symbol['hcolor'] = hcolWin.GetId()
+        gridSizer.Add(item=hcolWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+
+        #
+        # symbol color for selected GCP
+        #
+        row += 1
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Color for selected GCP:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        scol = UserSettings.Get(group='gcpman', key='symbol', subkey='scolor')
+        scolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
+                                   colour=wx.Colour(scol[0],
+                                                    scol[1],
+                                                    scol[2],
+                                                    255))
+        self.symbol['scolor'] = scolWin.GetId()
+        gridSizer.Add(item=scolWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+
+        #
+        # symbol color for unused GCP
+        #
+        row += 1
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Color for unused GCPs:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        ucol = UserSettings.Get(group='gcpman', key='symbol', subkey='ucolor')
+        ucolWin = csel.ColourSelect(parent=panel, id=wx.ID_ANY,
+                                   colour=wx.Colour(ucol[0],
+                                                    ucol[1],
+                                                    ucol[2],
+                                                    255))
+        self.symbol['ucolor'] = ucolWin.GetId()
+        gridSizer.Add(item=ucolWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+
+        # show unused GCPs
+        row += 1
+        self.showunused = wx.CheckBox(parent=panel, id=wx.ID_ANY,
+                                label=_("Show unused GCPs"))
+        shuu = UserSettings.Get(group='gcpman', key='symbol', subkey='unused')
+        self.showunused.SetValue(shuu)
+        gridSizer.Add(item=self.showunused, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+
+        #
+        # symbol size
+        #
+        row += 1
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Symbol size:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        symsize = int(UserSettings.Get(group='gcpman', key='symbol', subkey='size'))
+        sizeWin = wx.SpinCtrl(parent=panel, id=wx.ID_ANY,
+                             min=1, max=20)
+        sizeWin.SetValue(symsize)
+        self.symbol['size'] = sizeWin.GetId()
+        gridSizer.Add(item=sizeWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+        
+        #
+        # symbol width
+        #
+        row += 1
+        label = wx.StaticText(parent=panel, id=wx.ID_ANY, label=_("Line width:"))
+        gridSizer.Add(item=label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, 0))
+        width = int(UserSettings.Get(group='gcpman', key='symbol', subkey='width'))
+        widWin = wx.SpinCtrl(parent=panel, id=wx.ID_ANY,
+                             min=1, max=10)
+        widWin.SetValue(width)
+        self.symbol['width'] = widWin.GetId()
+        gridSizer.Add(item=widWin,
+                      flag=wx.ALIGN_RIGHT,
+                      pos=(row, 1))
+        
+        boxSizer.Add(item=gridSizer, flag=wx.EXPAND)
+        sizer.Add(item=boxSizer, flag=wx.EXPAND | wx.ALL, border=5)
+
+        #
+        # maps to display
+        #
+        # source map to display
+        self.srcselection = Select(panel, id=wx.ID_ANY,
+                                   size=globalvar.DIALOG_GSELECT_SIZE, type='cell', updateOnPopup = False)
+        self.parent.grwiz.SwitchEnv('source')
+        self.srcselection.SetElementList(maptype)
+        # filter out all maps not in group
+        self.srcselection.tcp.GetElementList(elements = self.parent.src_maps)
+
+        # target map to display
+        self.tgtselection = Select(panel, id=wx.ID_ANY,
+                                   size=globalvar.DIALOG_GSELECT_SIZE, type='cell', updateOnPopup = False)
+        self.parent.grwiz.SwitchEnv('target')
+        self.tgtselection.SetElementList(maptype)
+        self.tgtselection.GetElementList()
+
+        sizer.Add(item=wx.StaticText(parent=panel, id=wx.ID_ANY, label=_('Select source map to display:')),
+                       proportion=0, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        sizer.Add(item=self.srcselection, proportion=0, 
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        self.srcselection.SetValue(src_map)
+        sizer.Add(item=wx.StaticText(parent=panel, id=wx.ID_ANY, label=_('Select target map to display:')),
+                       proportion=0, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        sizer.Add(item=self.tgtselection, proportion=0, 
+                       flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        self.tgtselection.SetValue(tgt_map)
+
+        # bindings
+        self.highlighthighest.Bind(wx.EVT_CHECKBOX, self.OnHighlight)
+        self.rmsWin.Bind(wx.EVT_TEXT, self.OnSDFactor)
+        self.srcselection.Bind(wx.EVT_TEXT, self.OnSrcSelection)
+        self.tgtselection.Bind(wx.EVT_TEXT, self.OnTgtSelection)
+
+        panel.SetSizer(sizer)
+        
+        return panel
+
+    def __CreateRectificationPage(self, notebook):
+        """!Create notebook page with symbology settings"""
+
+        panel = wx.Panel(parent=notebook, id=wx.ID_ANY)
+        notebook.AddPage(page=panel, text=_("Rectification"))
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        # transformation order
+        self.rb_grorder = wx.RadioBox(parent=panel, id=wx.ID_ANY,
+                                       label=" %s " % _("Select rectification order"),
+                                       choices=[_('1st order'), _('2nd order'), _('3rd order')],
+                                       majorDimension=wx.RA_SPECIFY_COLS)
+        sizer.Add(item=self.rb_grorder, proportion=0,
+                       flag=wx.EXPAND | wx.ALL, border=5)
+        self.rb_grorder.SetSelection(self.parent.gr_order - 1)
+
+        # interpolation method
+        gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
+        gridSizer.AddGrowableCol(1)
+        gridSizer.Add(item=wx.StaticText(parent=panel, id=wx.ID_ANY, label=_('Select interpolation method:')),
+                       pos=(0,0), flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        self.grmethod = wx.Choice(parent=panel, id=wx.ID_ANY,
+                                  choices = self.methods)
+        gridSizer.Add(item=self.grmethod, pos=(0,1),
+                       flag=wx.ALIGN_RIGHT, border=5)
+        self.grmethod.SetStringSelection(self.parent.gr_method)
+        sizer.Add(item=gridSizer, flag=wx.EXPAND | wx.ALL, border=5)
+
+        # clip to region
+        self.check = wx.CheckBox(parent=panel, id=wx.ID_ANY,
+                                label=_("clip to computational region in target location"))
+        sizer.Add(item=self.check, proportion=0,
+                       flag=wx.EXPAND | wx.ALL, border=5)
+        self.check.SetValue(self.parent.clip_to_region)
+
+        # extension
+        sizer.Add(item=wx.StaticText(parent=panel, id=wx.ID_ANY, label=_('Extension for output maps:')),
+                       proportion=0, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+        self.ext_txt = wx.TextCtrl(parent=panel, id=wx.ID_ANY, value="", size=(350,-1))
+        self.ext_txt.SetValue(self.parent.extension)
+        sizer.Add(item=self.ext_txt,
+                       proportion=0, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
+
+        # bindings
+        self.ext_txt.Bind(wx.EVT_TEXT, self.OnExtension)
+        self.Bind(wx.EVT_RADIOBOX, self.parent.OnGROrder, self.rb_grorder)
+        self.Bind(wx.EVT_CHOICE, self.OnMethod, self.grmethod)
+        self.Bind(wx.EVT_CHECKBOX, self.OnClipRegion, self.check)
+
+        panel.SetSizer(sizer)
+        
+        return panel
+
+    def OnHighlight(self, event):
+        """!Checkbox 'highlighthighest' checked/unchecked"""
+        if self.highlighthighest.IsChecked():
+            self.parent.highest_only = True
+            self.rmsWin.Disable()
+        else:
+            self.parent.highest_only = False
+            self.rmsWin.Enable()
+
+    def OnSDFactor(self,event):
+        """!New factor for RMS threshold = M + SD * factor"""
+
+        self.sdfactor = float(event.GetString())
+
+        if self.sdfactor <= 0:
+            GError(parent = self,
+                   message=_('RMS threshold factor must be > 0'))
+        elif self.sdfactor < 1:
+            GError(parent = self,
+                   message=_('RMS threshold factor is < 1\n'
+                             'Too many points might be highlighted'))
+        
+    def OnSrcSelection(self,event):
+        """!Source map to display selected"""
+        global src_map
+
+        tmp_map = event.GetString()
+
+        if not tmp_map == '' and not tmp_map == src_map:
+            self.new_src_map = tmp_map
+
+    def OnTgtSelection(self,event):
+        """!Target map to display selected"""
+        global tgt_map
+
+        tmp_map = event.GetString()
+
+        if not tmp_map == tgt_map:
+            self.new_tgt_map = tmp_map
+
+    def OnMethod(self, event):
+        self.parent.gr_method = self.methods[event.GetSelection()]
+
+    def OnClipRegion(self, event):
+        self.parent.clip_to_region = event.IsChecked()
+        
+    def OnExtension(self, event):
+        self.parent.extension = event.GetString()
+
+    def UpdateSettings(self):
+        global src_map
+        global tgt_map
+
+        layers = None
+
+        UserSettings.Set(group='gcpman', key='rms', subkey='highestonly',
+                         value=self.highlighthighest.GetValue())
+        if self.sdfactor > 0:
+            UserSettings.Set(group='gcpman', key='rms', subkey='sdfactor',
+                             value=self.sdfactor)
+
+            self.parent.sdfactor = self.sdfactor
+            if self.parent.rmsthresh > 0:
+                self.parent.rmsthresh = self.parent.mean + self.parent.sdfactor * self.parent.rmssd
+
+        UserSettings.Set(group='gcpman', key='symbol', subkey='color',
+                         value=tuple(wx.FindWindowById(self.symbol['color']).GetColour()))
+        UserSettings.Set(group='gcpman', key='symbol', subkey='hcolor',
+                         value=tuple(wx.FindWindowById(self.symbol['hcolor']).GetColour()))
+        UserSettings.Set(group='gcpman', key='symbol', subkey='scolor',
+                         value=tuple(wx.FindWindowById(self.symbol['scolor']).GetColour()))
+        UserSettings.Set(group='gcpman', key='symbol', subkey='ucolor',
+                         value=tuple(wx.FindWindowById(self.symbol['ucolor']).GetColour()))
+        UserSettings.Set(group='gcpman', key='symbol', subkey='unused',
+                         value=self.showunused.GetValue())
+        UserSettings.Set(group='gcpman', key='symbol', subkey='size',
+                         value=wx.FindWindowById(self.symbol['size']).GetValue())
+        UserSettings.Set(group='gcpman', key='symbol', subkey='width',
+                         value=wx.FindWindowById(self.symbol['width']).GetValue())
+
+        srcrender = False
+        srcrenderVector = False
+        tgtrender = False
+        tgtrenderVector = False
+        if self.new_src_map != src_map:
+            # remove old layer
+            layers = self.parent.grwiz.SrcMap.GetListOfLayers()
+            self.parent.grwiz.SrcMap.DeleteLayer(layers[0])
+            
+            src_map = self.new_src_map
+            cmdlist = ['d.rast', 'map=%s' % src_map]
+            self.parent.grwiz.SwitchEnv('source')
+            name, found = utils.GetLayerNameFromCmd(cmdlist),
+            self.parent.grwiz.SrcMap.AddLayer(type='raster', command=cmdlist, l_active=True,
+                              name=name, l_hidden=False, l_opacity=1.0, l_render=False)
+
+            self.parent.grwiz.SwitchEnv('target')
+            srcrender = True
+
+        if self.new_tgt_map != tgt_map:
+            # remove old layer
+            layers = self.parent.grwiz.TgtMap.GetListOfLayers()
+            if layers:
+                self.parent.grwiz.TgtMap.DeleteLayer(layers[0])
+            tgt_map = self.new_tgt_map
+
+            if tgt_map != '':
+                cmdlist = ['d.rast', 'map=%s' % tgt_map]
+                name, found = utils.GetLayerNameFromCmd(cmdlist)
+                self.parent.grwiz.TgtMap.AddLayer(type='raster', command=cmdlist, l_active=True,
+                                  name=name, l_hidden=False, l_opacity=1.0, l_render=False)
+
+                tgtrender = True
+                if self.parent.show_target == False:
+                    self.parent.show_target = True
+                    self.parent._mgr.GetPane("target").Show()
+                    self.parent._mgr.Update()
+                    self.parent.GetMapToolbar().Enable('zoommenu', enable = True)
+                    self.parent.activemap.Enable()
+                    self.parent.TgtMapWindow.ZoomToMap(layers = self.parent.TgtMap.GetListOfLayers())
+            else: # tgt_map == ''
+                if self.parent.show_target == True:
+                    self.parent.show_target = False
+                    self.parent._mgr.GetPane("target").Hide()
+                    self.parent._mgr.Update()
+                    self.parent.activemap.SetSelection(0)
+                    self.parent.activemap.Enable(False)
+                    self.parent.GetMapToolbar().Enable('zoommenu', enable = False)
+
+        self.parent.UpdateColours(srcrender, srcrenderVector, tgtrender, tgtrenderVector)
+
+    def OnSave(self, event):
+        """!Button 'Save' pressed"""
+        self.UpdateSettings()
+        fileSettings = {}
+        UserSettings.ReadSettingsFile(settings=fileSettings)
+        fileSettings['gcpman'] = UserSettings.Get(group='gcpman')
+        file = UserSettings.SaveToFile(fileSettings)
+        self.parent.parent.goutput.WriteLog(_('GCP Manager settings saved to file \'%s\'.') % file)
+        #self.Close()
+
+    def OnApply(self, event):
+        """!Button 'Apply' pressed"""
+        self.UpdateSettings()
+        #self.Close()
+
+    def OnClose(self, event):
+        """!Button 'Cancel' pressed"""
+        self.Close()

Copied: grass/trunk/gui/wxpython/gcp/mapdisp.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/gcpmapdisp.py)
===================================================================
--- grass/trunk/gui/wxpython/gcp/mapdisp.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gcp/mapdisp.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,629 @@
+"""!
+ at package gcp.mapdisp
+
+ at brief Display to manage ground control points with two toolbars, one
+for various display management functions, one for manipulating GCPs.
+
+Classes:
+- MapFrame
+
+(C) 2006-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Markus Metz
+"""
+
+import os
+import math
+import platform
+
+from core import globalvar
+import wx
+import wx.aui
+
+from core.render      import EVT_UPDATE_PRGBAR
+from mapdisp.toolbars import MapToolbar
+from gcp.toolbars     import GCPDisplayToolbar, GCPManToolbar
+from mapdisp.gprint   import PrintOptions
+from core.gcmd        import GMessage
+from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
+from gui_core.mapdisp import MapFrameBase
+from core.settings    import UserSettings
+from mapdisp.window   import BufferedWindow
+
+import mapdisp.statusbar as sb
+
+# for standalone app
+cmdfilename = None
+
+class MapFrame(MapFrameBase):
+    """!Main frame for map display window. Drawing takes place in
+    child double buffered drawing window.
+    """
+    def __init__(self, parent=None, title=_("GRASS GIS Manage Ground Control Points"),
+                 toolbars=["gcpdisp"], tree=None, notebook=None, lmgr=None,
+                 page=None, Map=None, auimgr=None, name = 'GCPMapWindow', **kwargs):
+        """!Main map display window with toolbars, statusbar and
+        DrawWindow
+
+        @param toolbars array of activated toolbars, e.g. ['map', 'digit']
+        @param tree reference to layer tree
+        @param notebook control book ID in Layer Manager
+        @param lmgr Layer Manager
+        @param page notebook page with layer tree
+        @param Map instance of render.Map
+        @param auimgs AUI manager
+        @param kwargs wx.Frame attribures
+        """
+        
+        MapFrameBase.__init__(self, parent = parent, title = title, toolbars = toolbars,
+                              Map = Map, auimgr = auimgr, name = name, **kwargs)
+        
+        self._layerManager = lmgr   # Layer Manager object
+        self.tree       = tree      # Layer Manager layer tree object
+        self.page       = page      # Notebook page holding the layer tree
+        self.layerbook  = notebook  # Layer Manager layer tree notebook
+        #
+        # Add toolbars
+        #
+        for toolb in toolbars:
+            self.AddToolbar(toolb)
+
+        self.activemap = self.toolbars['gcpdisp'].togglemap
+        self.activemap.SetSelection(0)
+        
+        self.SrcMap        = self.grwiz.SrcMap       # instance of render.Map
+        self.TgtMap        = self.grwiz.TgtMap       # instance of render.Map
+        self._mgr.SetDockSizeConstraint(0.5, 0.5)
+
+        #
+        # Add statusbar
+        #
+        
+        # items for choice
+        self.statusbarItems = [sb.SbCoordinates,
+                               sb.SbRegionExtent,
+                               sb.SbCompRegionExtent,
+                               sb.SbShowRegion,
+                               sb.SbResolution,
+                               sb.SbDisplayGeometry,
+                               sb.SbMapScale,
+                               sb.SbProjection,
+                               sb.SbGoToGCP,
+                               sb.SbRMSError]
+                            
+        
+        # create statusbar and its manager
+        statusbar = self.CreateStatusBar(number = 4, style = 0)
+        statusbar.SetStatusWidths([-5, -2, -1, -1])
+        self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar)
+        
+        # fill statusbar manager
+        self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar)
+        self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2))
+        self.statusbarManager.AddStatusbarItem(sb.SbRender(self, statusbar = statusbar, position = 3))
+        
+        self.statusbarManager.SetMode(8) # goto GCP
+        self.statusbarManager.Update()
+        
+
+        #
+        # Init map display (buffered DC & set default cursor)
+        #
+        self.grwiz.SwitchEnv('source')
+        self.SrcMapWindow = BufferedWindow(self, id=wx.ID_ANY,
+                                          Map=self.SrcMap, tree=self.tree, lmgr=self._layerManager)
+
+        self.grwiz.SwitchEnv('target')
+        self.TgtMapWindow = BufferedWindow(self, id=wx.ID_ANY,
+                                          Map=self.TgtMap, tree=self.tree, lmgr=self._layerManager)
+        self.MapWindow = self.SrcMapWindow
+        self.Map = self.SrcMap
+        self.SrcMapWindow.SetCursor(self.cursors["cross"])
+        self.TgtMapWindow.SetCursor(self.cursors["cross"])
+
+        #
+        # initialize region values
+        #
+        self._initMap(map = self.SrcMap) 
+        self._initMap(map = self.TgtMap) 
+
+        #
+        # Bind various events
+        #
+        self.Bind(wx.EVT_ACTIVATE, self.OnFocus)
+        self.Bind(EVT_UPDATE_PRGBAR, self.OnUpdateProgress)
+        self.Bind(wx.EVT_SIZE,     self.OnDispResize)
+        self.activemap.Bind(wx.EVT_CHOICE, self.OnUpdateActive)
+        
+        #
+        # Update fancy gui style
+        #
+        # AuiManager wants a CentrePane, workaround to get two equally sized windows
+        self.list = self.CreateGCPList()
+
+        #self.SrcMapWindow.SetSize((300, 300))
+        #self.TgtMapWindow.SetSize((300, 300))
+        self.list.SetSize((100, 150))
+        self._mgr.AddPane(self.list, wx.aui.AuiPaneInfo().
+                  Name("gcplist").Caption(_("GCP List")).LeftDockable(False).
+                  RightDockable(False).PinButton().FloatingSize((600,200)).
+                  CloseButton(False).DestroyOnClose(True).
+                  Top().Layer(1).MinSize((200,100)))
+        self._mgr.AddPane(self.SrcMapWindow, wx.aui.AuiPaneInfo().
+                  Name("source").Caption(_("Source Display")).Dockable(False).
+                  CloseButton(False).DestroyOnClose(True).Floatable(False).
+                  Centre())
+        self._mgr.AddPane(self.TgtMapWindow, wx.aui.AuiPaneInfo().
+                  Name("target").Caption(_("Target Display")).Dockable(False).
+                  CloseButton(False).DestroyOnClose(True).Floatable(False).
+                  Right().Layer(0))
+
+        srcwidth, srcheight = self.SrcMapWindow.GetSize()
+        tgtwidth, tgtheight = self.TgtMapWindow.GetSize()
+        srcwidth = (srcwidth + tgtwidth) / 2
+        self._mgr.GetPane("target").Hide()
+        self._mgr.Update()
+        self._mgr.GetPane("source").BestSize((srcwidth, srcheight))
+        self._mgr.GetPane("target").BestSize((srcwidth, srcheight))
+        if self.show_target:
+            self._mgr.GetPane("target").Show()
+        else:
+            self.activemap.Enable(False)
+        # needed by Mac OS, does not harm on Linux, breaks display on Windows
+        if platform.system() != 'Windows':
+            self._mgr.Update()
+
+        #
+        # Init print module and classes
+        #
+        self.printopt = PrintOptions(self, self.MapWindow)
+        
+        #
+        # Initialization of digitization tool
+        #
+        self.digit = None
+
+        # set active map
+        self.MapWindow = self.SrcMapWindow
+        self.Map = self.SrcMap
+        
+        # do not init zoom history here, that happens when zooming to map(s)
+
+        #
+        # Re-use dialogs
+        #
+        self.dialogs = {}
+        self.dialogs['attributes'] = None
+        self.dialogs['category'] = None
+        self.dialogs['barscale'] = None
+        self.dialogs['legend'] = None
+
+        self.decorationDialog = None # decoration/overlays
+
+    def AddToolbar(self, name):
+        """!Add defined toolbar to the window
+        
+        Currently known toolbars are:
+         - 'map'     - basic map toolbar
+         - 'vdigit'  - vector digitizer
+         - 'gcpdisp' - GCP Manager, Display
+         - 'gcpman'  - GCP Manager, points management
+         - 'nviz'    - 3D view mode
+        """
+        # default toolbar
+        if name == "map":
+            self.toolbars['map'] = MapToolbar(self, self.Map)
+
+            self._mgr.AddPane(self.toolbars['map'],
+                              wx.aui.AuiPaneInfo().
+                              Name("maptoolbar").Caption(_("Map Toolbar")).
+                              ToolbarPane().Top().
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(False).TopDockable(True).
+                              CloseButton(False).Layer(2).
+                              BestSize((self.toolbars['map'].GetSize())))
+
+        # GCP display
+        elif name == "gcpdisp":
+            self.toolbars['gcpdisp'] = GCPDisplayToolbar(self)
+
+            self._mgr.AddPane(self.toolbars['gcpdisp'],
+                              wx.aui.AuiPaneInfo().
+                              Name("gcpdisplaytoolbar").Caption(_("GCP Display toolbar")).
+                              ToolbarPane().Top().
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(False).TopDockable(True).
+                              CloseButton(False).Layer(2))
+
+            if self.show_target == False:
+                self.toolbars['gcpdisp'].Enable('zoommenu', enable = False)
+
+            self.toolbars['gcpman'] = GCPManToolbar(self)
+
+            self._mgr.AddPane(self.toolbars['gcpman'],
+                              wx.aui.AuiPaneInfo().
+                              Name("gcpmanagertoolbar").Caption(_("GCP Manager toolbar")).
+                              ToolbarPane().Top().Row(1).
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(False).TopDockable(True).
+                              CloseButton(False).Layer(2))
+            
+        self._mgr.Update()
+
+    def OnUpdateProgress(self, event):
+        """
+        Update progress bar info
+        """
+        self.GetProgressBar().SetValue(event.value)
+        
+        event.Skip()
+        
+    def OnFocus(self, event):
+        """
+        Change choicebook page to match display.
+        Or set display for georectifying
+        """
+        if self._layerManager and \
+                self._layerManager.gcpmanagement:
+            # in GCP Management, set focus to current MapWindow for mouse actions
+            self.OnPointer(event)
+            self.MapWindow.SetFocus()
+        else:
+            # change bookcontrol page to page associated with display
+            # GCP Manager: use bookcontrol?
+            if self.page:
+                pgnum = self.layerbook.GetPageIndex(self.page)
+                if pgnum > -1:
+                    self.layerbook.SetSelection(pgnum)
+        
+        event.Skip()
+
+    def OnDraw(self, event):
+        """!Re-display current map composition
+        """
+        self.MapWindow.UpdateMap(render = False)
+        
+    def OnRender(self, event):
+        """!Re-render map composition (each map layer)
+        """
+        # delete tmp map layers (queries)
+        qlayer = self.Map.GetListOfLayers(l_name=globalvar.QUERYLAYER)
+        for layer in qlayer:
+            self.Map.DeleteLayer(layer)
+
+        self.SrcMapWindow.UpdateMap(render=True)
+        if self.show_target:
+            self.TgtMapWindow.UpdateMap(render=True)
+        
+        # update statusbar
+        self.StatusbarUpdate()
+
+    def OnPointer(self, event):
+        """!Pointer button clicked
+        """
+        # change the cursor
+        self.SrcMapWindow.SetCursor(self.cursors["cross"])
+        self.SrcMapWindow.mouse['use'] = "pointer"
+        self.SrcMapWindow.mouse['box'] = "point"
+        self.TgtMapWindow.SetCursor(self.cursors["cross"])
+        self.TgtMapWindow.mouse['use'] = "pointer"
+        self.TgtMapWindow.mouse['box'] = "point"
+
+    def OnZoomIn(self, event):
+        """
+        Zoom in the map.
+        Set mouse cursor, zoombox attributes, and zoom direction
+        """
+        if self.GetToolbar('map'):
+            self.toolbars['map'].OnTool(event)
+            self.toolbars['map'].action['desc'] = ''
+        
+        self.MapWindow.mouse['use'] = "zoom"
+        self.MapWindow.mouse['box'] = "box"
+        self.MapWindow.zoomtype = 1
+        self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
+        
+        # change the cursor
+        self.MapWindow.SetCursor(self.cursors["cross"])
+
+        if self.MapWindow == self.SrcMapWindow:
+            win = self.TgtMapWindow
+        elif self.MapWindow == self.TgtMapWindow:
+            win = self.SrcMapWindow
+
+        win.mouse['use'] = "zoom"
+        win.mouse['box'] = "box"
+        win.zoomtype = 1
+        win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
+        
+        # change the cursor
+        win.SetCursor(self.cursors["cross"])
+
+    def OnZoomOut(self, event):
+        """
+        Zoom out the map.
+        Set mouse cursor, zoombox attributes, and zoom direction
+        """
+        if self.GetToolbar('map'):
+            self.toolbars['map'].OnTool(event)
+            self.toolbars['map'].action['desc'] = ''
+        
+        self.MapWindow.mouse['use'] = "zoom"
+        self.MapWindow.mouse['box'] = "box"
+        self.MapWindow.zoomtype = -1
+        self.MapWindow.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
+        
+        # change the cursor
+        self.MapWindow.SetCursor(self.cursors["cross"])
+
+        if self.MapWindow == self.SrcMapWindow:
+            win = self.TgtMapWindow
+        elif self.MapWindow == self.TgtMapWindow:
+            win = self.SrcMapWindow
+
+        win.mouse['use'] = "zoom"
+        win.mouse['box'] = "box"
+        win.zoomtype = -1
+        win.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH)
+        
+        # change the cursor
+        win.SetCursor(self.cursors["cross"])
+
+    def OnPan(self, event):
+        """
+        Panning, set mouse to drag
+        """
+        if self.GetToolbar('map'):
+            self.toolbars['map'].OnTool(event)
+            self.toolbars['map'].action['desc'] = ''
+        
+        self.MapWindow.mouse['use'] = "pan"
+        self.MapWindow.mouse['box'] = "pan"
+        self.MapWindow.zoomtype = 0
+        
+        # change the cursor
+        self.MapWindow.SetCursor(self.cursors["hand"])
+
+        if self.MapWindow == self.SrcMapWindow:
+            win = self.TgtMapWindow
+        elif self.MapWindow == self.TgtMapWindow:
+            win = self.SrcMapWindow
+
+        win.mouse['use'] = "pan"
+        win.mouse['box'] = "pan"
+        win.zoomtype = 0
+        
+        # change the cursor
+        win.SetCursor(self.cursors["hand"])
+
+    def OnErase(self, event):
+        """
+        Erase the canvas
+        """
+        self.MapWindow.EraseMap()
+
+        if self.MapWindow == self.SrcMapWindow:
+            win = self.TgtMapWindow
+        elif self.MapWindow == self.TgtMapWindow:
+            win = self.SrcMapWindow
+
+        win.EraseMap()
+
+    def OnZoomRegion(self, event):
+        """
+        Zoom to region
+        """
+        self.Map.getRegion()
+        self.Map.getResolution()
+        self.UpdateMap()
+        # event.Skip()
+
+    def OnAlignRegion(self, event):
+        """
+        Align region
+        """
+        if not self.Map.alignRegion:
+            self.Map.alignRegion = True
+        else:
+            self.Map.alignRegion = False
+        # event.Skip()
+    
+    def SaveToFile(self, event):
+        """!Save map to image
+        """
+        img = self.MapWindow.img
+        if not img:
+            GMessage(parent = self,
+                     message = _("Nothing to render (empty map). Operation canceled."))
+            return
+        filetype, ltype = GetImageHandlers(img)
+
+        # get size
+        dlg = ImageSizeDialog(self)
+        dlg.CentreOnParent()
+        if dlg.ShowModal() != wx.ID_OK:
+            dlg.Destroy()
+            return
+        width, height = dlg.GetValues()
+        dlg.Destroy()
+        
+        # get filename
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Choose a file name to save the image "
+                                        "(no need to add extension)"),
+                            wildcard = filetype,
+                            style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            if not path:
+                dlg.Destroy()
+                return
+            
+            base, ext = os.path.splitext(path)
+            fileType = ltype[dlg.GetFilterIndex()]['type']
+            extType  = ltype[dlg.GetFilterIndex()]['ext']
+            if ext != extType:
+                path = base + '.' + extType
+            
+            self.MapWindow.SaveToFile(path, fileType,
+                                      width, height)
+            
+        dlg.Destroy()
+
+    def PrintMenu(self, event):
+        """
+        Print options and output menu for map display
+        """
+        point = wx.GetMousePosition()
+        printmenu = wx.Menu()
+        # Add items to the menu
+        setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup'))
+        printmenu.AppendItem(setup)
+        self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
+
+        preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview'))
+        printmenu.AppendItem(preview)
+        self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
+
+        doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display'))
+        printmenu.AppendItem(doprint)
+        self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
+
+        # Popup the menu.  If an item is selected then its handler
+        # will be called before PopupMenu returns.
+        self.PopupMenu(printmenu)
+        printmenu.Destroy()
+    
+    
+    def FormatDist(self, dist):
+        """!Format length numbers and units in a nice way,
+        as a function of length. From code by Hamish Bowman
+        Grass Development Team 2006"""
+
+        mapunits = self.Map.projinfo['units']
+        if mapunits == 'metres': mapunits = 'meters'
+        outunits = mapunits
+        dist = float(dist)
+        divisor = 1.0
+
+        # figure out which units to use
+        if mapunits == 'meters':
+            if dist > 2500.0:
+                outunits = 'km'
+                divisor = 1000.0
+            else: outunits = 'm'
+        elif mapunits == 'feet':
+            # nano-bug: we match any "feet", but US Survey feet is really
+            #  5279.9894 per statute mile, or 10.6' per 1000 miles. As >1000
+            #  miles the tick markers are rounded to the nearest 10th of a
+            #  mile (528'), the difference in foot flavours is ignored.
+            if dist > 5280.0:
+                outunits = 'miles'
+                divisor = 5280.0
+            else:
+                outunits = 'ft'
+        elif 'degree' in mapunits:
+            if dist < 1:
+                outunits = 'min'
+                divisor = (1/60.0)
+            else:
+                outunits = 'deg'
+
+        # format numbers in a nice way
+        if (dist/divisor) >= 2500.0:
+            outdist = round(dist/divisor)
+        elif (dist/divisor) >= 1000.0:
+            outdist = round(dist/divisor,1)
+        elif (dist/divisor) > 0.0:
+            outdist = round(dist/divisor,int(math.ceil(3-math.log10(dist/divisor))))
+        else:
+            outdist = float(dist/divisor)
+
+        return (outdist, outunits)
+
+    def OnZoomToRaster(self, event):
+        """!
+        Set display extents to match selected raster map (ignore NULLs)
+        """
+        self.MapWindow.ZoomToMap(ignoreNulls = True)
+        
+    def OnZoomToSaved(self, event):
+        """!Set display geometry to match extents in
+        saved region file
+        """
+        self.MapWindow.ZoomToSaved()
+        
+    def OnDisplayToWind(self, event):
+        """!Set computational region (WIND file) to match display
+        extents
+        """
+        self.MapWindow.DisplayToWind()
+ 
+    def SaveDisplayRegion(self, event):
+        """!Save display extents to named region file.
+        """
+        self.MapWindow.SaveDisplayRegion()
+        
+    def OnZoomMenu(self, event):
+        """!Popup Zoom menu
+        """
+        point = wx.GetMousePosition()
+        zoommenu = wx.Menu()
+        # Add items to the menu
+
+        zoomwind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to computational region (set with g.region)'))
+        zoommenu.AppendItem(zoomwind)
+        self.Bind(wx.EVT_MENU, self.OnZoomToWind, zoomwind)
+
+        zoomdefault = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to default region'))
+        zoommenu.AppendItem(zoomdefault)
+        self.Bind(wx.EVT_MENU, self.OnZoomToDefault, zoomdefault)
+
+        zoomsaved = wx.MenuItem(zoommenu, wx.ID_ANY, _('Zoom to saved region'))
+        zoommenu.AppendItem(zoomsaved)
+        self.Bind(wx.EVT_MENU, self.OnZoomToSaved, zoomsaved)
+
+        savewind = wx.MenuItem(zoommenu, wx.ID_ANY, _('Set computational region from display'))
+        zoommenu.AppendItem(savewind)
+        self.Bind(wx.EVT_MENU, self.OnDisplayToWind, savewind)
+
+        savezoom = wx.MenuItem(zoommenu, wx.ID_ANY, _('Save display geometry to named region'))
+        zoommenu.AppendItem(savezoom)
+        self.Bind(wx.EVT_MENU, self.SaveDisplayRegion, savezoom)
+
+        # Popup the menu. If an item is selected then its handler
+        # will be called before PopupMenu returns.
+        self.PopupMenu(zoommenu)
+        zoommenu.Destroy()
+        
+        
+    def IsStandalone(self):
+        """!Check if Map display is standalone"""
+        if self._layerManager:
+            return False
+        
+        return True
+    
+    def GetLayerManager(self):
+        """!Get reference to Layer Manager
+
+        @return window reference
+        @return None (if standalone)
+        """
+        return self._layerManager
+    
+    def GetSrcWindow(self):
+        return self.SrcMapWindow
+        
+    def GetTgtWindow(self):
+        return self.TgtMapWindow
+    
+    def GetShowTarget(self):
+        return self.show_target
+        
+    def GetMapToolbar(self):
+        """!Returns toolbar with zooming tools"""
+        return self.toolbars['gcpdisp']

Added: grass/trunk/gui/wxpython/gcp/toolbars.py
===================================================================
--- grass/trunk/gui/wxpython/gcp/toolbars.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gcp/toolbars.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,127 @@
+"""!
+ at package gcp.toolbars
+
+ at brief Georectification module - toolbars
+
+Classes:
+ - GCPMapToolbar
+ - GCPDisplayToolbar
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Markus Metz
+"""
+
+import os
+import sys
+
+import wx
+
+from core              import globalvar
+from gui_core.toolbars import BaseToolbar
+
+sys.path.append(os.path.join(globalvar.ETCWXDIR, "icons"))
+from icon              import Icons
+    
+class GCPManToolbar(BaseToolbar):
+    """!Toolbar for managing ground control points
+
+    @param parent reference to GCP widget
+    """
+    def __init__(self, parent):
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+        
+        # realize the toolbar
+        self.Realize()
+
+    def _toolbarData(self):
+        icons = Icons['georectify']
+        return self._getToolbarData((('gcpSave', icons["gcpSave"],
+                                      self.parent.SaveGCPs),
+                                     ('gcpReload', icons["gcpReload"],
+                                      self.parent.ReloadGCPs),
+                                     (None, ),
+                                     ('gcpAdd', icons["gcpAdd"],
+                                      self.parent.AddGCP),
+                                     ('gcpDelete', icons["gcpDelete"],
+                                      self.parent.DeleteGCP),
+                                     ('gcpClear', icons["gcpClear"],
+                                      self.parent.ClearGCP),
+                                     (None, ),
+                                     ('rms', icons["gcpRms"],
+                                      self.parent.OnRMS),
+                                     ('georect', icons["georectify"],
+                                      self.parent.OnGeorect))
+                                    )
+    
+class GCPDisplayToolbar(BaseToolbar):
+    """!GCP Display toolbar
+    """
+    def __init__(self, parent):
+        """!GCP Display toolbar constructor
+        """
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+        
+        # add tool to toggle active map window
+        self.togglemapid = wx.NewId()
+        self.togglemap = wx.Choice(parent = self, id = self.togglemapid,
+                                   choices = [_('source'), _('target')],
+                                   style = wx.CB_READONLY)
+
+        self.InsertControl(10, self.togglemap)
+
+        self.SetToolShortHelp(self.togglemapid, '%s %s %s' % (_('Set map canvas for '),
+                                                              Icons['displayWindow']["zoomBack"].GetLabel(),
+                                                              _(' / Zoom to map')))
+
+        # realize the toolbar
+        self.Realize()
+        
+        self.action = { 'id' : self.gcpset }
+        self.defaultAction = { 'id' : self.gcpset,
+                               'bind' : self.parent.OnPointer }
+        
+        self.OnTool(None)
+        
+        self.EnableTool(self.zoomback, False)
+        
+    def _toolbarData(self):
+        """!Toolbar data"""
+        icons = Icons['displayWindow']
+        return self._getToolbarData((("displaymap", icons["display"],
+                                      self.parent.OnDraw),
+                                     ("rendermap", icons["render"],
+                                      self.parent.OnRender),
+                                     ("erase", icons["erase"],
+                                      self.parent.OnErase),
+                                     (None, ),
+                                     ("gcpset", Icons["georectify"]["gcpSet"],
+                                      self.parent.OnPointer),
+                                     ("pan", icons["pan"],
+                                      self.parent.OnPan),
+                                     ("zoomin", icons["zoomIn"],
+                                      self.parent.OnZoomIn),
+                                     ("zoomout", icons["zoomOut"],
+                                      self.parent.OnZoomOut),
+                                     ("zoommenu", icons["zoomMenu"],
+                                      self.parent.OnZoomMenuGCP),
+                                     (None, ),
+                                     ("zoomback", icons["zoomBack"],
+                                      self.parent.OnZoomBack),
+                                     ("zoomtomap", icons["zoomExtent"],
+                                      self.parent.OnZoomToMap),
+                                     (None, ),
+                                     ('settings', Icons["georectify"]["settings"],
+                                      self.parent.OnSettings),
+                                     ('help', Icons["misc"]["help"],
+                                      self.parent.OnHelp),
+                                     (None, ),
+                                     ('quit', Icons["georectify"]["quit"],
+                                      self.parent.OnQuit))
+                                    )


Property changes on: grass/trunk/gui/wxpython/gcp/toolbars.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/gmodeler/dialogs.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/dialogs.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gmodeler/dialogs.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,628 @@
+"""!
+ at package gmodeler.dialogs
+
+ at brief wxGUI Graphical Modeler - dialogs
+
+Classes:
+ - ModelDataDialog
+ - ModelSearchDialog
+ - ModelRelationDialog
+ - ModelParamDialog
+ - ModelItemDialog
+ - ModelLoopDialog
+ - ModelConditionDialog
+
+(C) 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+
+import wx
+
+from core                 import globalvar
+from gui_core.widgets     import GNotebook
+from core.gcmd            import GError, EncodeString
+from gui_core.dialogs     import ElementDialog, MapLayersDialog
+from gui_core.ghelp       import SearchModuleWindow
+
+from grass.script import task as gtask
+
+class ModelDataDialog(ElementDialog):
+    """!Data item properties dialog"""
+    def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Data properties"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
+        self.parent = parent
+        self.shape = shape
+        
+        label, etype = self._getLabel()
+        ElementDialog.__init__(self, parent, title, label = label, etype = etype)
+                
+        self.element = gselect.Select(parent = self.panel,
+                                      type = prompt)
+        self.element.SetValue(shape.GetValue())
+        
+        self.Bind(wx.EVT_BUTTON, self.OnOK,     self.btnOK)
+        self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
+        
+        self.PostInit()
+        
+        if shape.GetValue():
+            self.btnOK.Enable()
+        
+        self._layout()
+        self.SetMinSize(self.GetSize())
+        
+    def _getLabel(self):
+        etype = False
+        prompt = self.shape.GetPrompt()
+        if prompt == 'raster':
+            label = _('Name of raster map:')
+        elif prompt == 'vector':
+            label = _('Name of vector map:')
+        else:
+            etype = True
+            label = _('Name of element:')
+
+        return label, etype
+    
+    def _layout(self):
+        """!Do layout"""
+        self.dataSizer.Add(self.element, proportion=0,
+                      flag=wx.EXPAND | wx.ALL, border=1)
+        
+        self.panel.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+
+    def OnOK(self, event):
+        """!Ok pressed"""
+        self.shape.SetValue(self.GetElement())
+        if self.etype:
+            elem = self.GetType()
+            if elem == 'rast':
+                self.shape.SetPrompt('raster')
+            elif elem == 'vect':
+                self.shape.SetPrompt('raster')
+        
+        self.parent.canvas.Refresh()
+        self.parent.SetStatusText('', 0)
+        self.shape.SetPropDialog(None)
+        
+        if self.IsModal():
+            event.Skip() 
+        else:
+            self.Destroy()
+    
+    def OnCancel(self, event):
+        """!Cancel pressed"""
+        self.shape.SetPropDialog(None)
+        if self.IsModal():
+            event.Skip()
+        else:
+            self.Destroy()
+class ModelSearchDialog(wx.Dialog):
+    def __init__(self, parent, id = wx.ID_ANY, title = _("Add new GRASS module to the model"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        """!Graphical modeler module search window
+        
+        @param parent parent window
+        @param id window id
+        @param title window title
+        @param kwargs wx.Dialogs' arguments
+        """
+        self.parent = parent
+        
+        wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
+        self.SetName("ModelerDialog")
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.cmdBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                   label=" %s " % _("Command"))
+        
+        self.cmd_prompt = prompt.GPromptSTC(parent = self)
+        self.search = SearchModuleWindow(parent = self.panel, cmdPrompt = self.cmd_prompt, showTip = True)
+        wx.CallAfter(self.cmd_prompt.SetFocus)
+        
+        # get commands
+        items = self.cmd_prompt.GetCommandItems()
+        
+        self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
+        self.btnOk     = wx.Button(self.panel, wx.ID_OK)
+        self.btnOk.SetDefault()
+        self.btnOk.Enable(False)
+
+        self.cmd_prompt.Bind(wx.EVT_KEY_UP, self.OnText)
+        self.search.searchChoice.Bind(wx.EVT_CHOICE, self.OnText)
+        self.Bind(wx.EVT_BUTTON, self.OnOk, self.btnOk)
+        
+        self._layout()
+        
+        self.SetSize((500, 275))
+        
+    def _layout(self):
+        cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL)
+        cmdSizer.Add(item = self.cmd_prompt, proportion = 1,
+                     flag = wx.EXPAND)
+        
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOk)
+        btnSizer.Realize()
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = self.search, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 3)
+        mainSizer.Add(item = cmdSizer, proportion = 1,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.panel.SetSizer(mainSizer)
+        mainSizer.Fit(self.panel)
+        
+        self.Layout()
+
+    def GetPanel(self):
+        """!Get dialog panel"""
+        return self.panel
+
+    def GetCmd(self):
+        """!Get command"""
+        line = self.cmd_prompt.GetCurLine()[0].strip()
+        if len(line) == 0:
+            list()
+        
+        try:
+            cmd = utils.split(str(line))
+        except UnicodeError:
+            cmd = utils.split(EncodeString((line)))
+            
+        return cmd
+    
+    def OnOk(self, event):
+        """!Button 'OK' pressed"""
+        self.btnOk.SetFocus()
+        cmd = self.GetCmd()
+        
+        if len(cmd) < 1:
+            GError(parent = self,
+                   message = _("Command not defined.\n\n"
+                               "Unable to add new action to the model."))
+            return
+        
+        if cmd[0] not in globalvar.grassCmd['all']:
+            GError(parent = self,
+                   message = _("'%s' is not a GRASS module.\n\n"
+                               "Unable to add new action to the model.") % cmd[0])
+            return
+        
+        self.EndModal(wx.ID_OK)
+        
+    def OnText(self, event):
+        """!Text in prompt changed"""
+        if self.cmd_prompt.AutoCompActive():
+            event.Skip()
+            return
+        
+        if isinstance(event, wx.KeyEvent):
+            entry = self.cmd_prompt.GetTextLeft()
+        elif isinstance(event, wx.stc.StyledTextEvent):
+            entry = event.GetText()
+        else:
+            entry = event.GetString()
+        
+        if entry:
+            self.btnOk.Enable()
+        else:
+            self.btnOk.Enable(False)
+            
+        event.Skip()
+        
+    def Reset(self):
+        """!Reset dialog"""
+        self.search.Reset()
+        self.cmd_prompt.OnCmdErase(None)
+        self.btnOk.Enable(False)
+        self.cmd_prompt.SetFocus()
+
+class ModelRelationDialog(wx.Dialog):
+    """!Relation properties dialog"""
+    def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Relation properties"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        self.parent = parent
+        self.shape = shape
+        
+        options = self._getOptions()
+        if not options:
+            self.valid = False
+            return
+        
+        self.valid = True
+        wx.Dialog.__init__(self, parent, id, title, style = style, **kwargs)
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.fromBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                    label = " %s " % _("From"))
+        self.toBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                  label = " %s " % _("To"))
+        
+        self.option = wx.ComboBox(parent = self.panel, id = wx.ID_ANY,
+                                  style = wx.CB_READONLY,
+                                  choices = options)
+        self.option.Bind(wx.EVT_COMBOBOX, self.OnOption)
+        
+        self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
+        self.btnOk     = wx.Button(self.panel, wx.ID_OK)
+        self.btnOk.Enable(False)
+        
+        self._layout()
+
+    def _layout(self):
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+        fromSizer = wx.StaticBoxSizer(self.fromBox, wx.VERTICAL)
+        self._layoutShape(shape = self.shape.GetFrom(), sizer = fromSizer)
+        toSizer = wx.StaticBoxSizer(self.toBox, wx.VERTICAL)
+        self._layoutShape(shape = self.shape.GetTo(), sizer = toSizer)
+
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOk)
+        btnSizer.Realize()
+        
+        mainSizer.Add(item = fromSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        mainSizer.Add(item = toSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.panel.SetSizer(mainSizer)
+        mainSizer.Fit(self.panel)
+        
+        self.Layout()
+        self.SetSize(self.GetBestSize())
+        
+    def _layoutShape(self, shape, sizer):
+        if isinstance(shape, ModelData):
+            sizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                           label = _("Data: %s") % shape.GetLog()),
+                      proportion = 1, flag = wx.EXPAND | wx.ALL,
+                      border = 5)
+        elif isinstance(shape, ModelAction):
+            gridSizer = wx.GridBagSizer (hgap = 5, vgap = 5)
+            gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                               label = _("Command:")),
+                          pos = (0, 0))
+            gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                               label = shape.GetName()),
+                          pos = (0, 1))
+            gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                               label = _("Option:")),
+                          flag = wx.ALIGN_CENTER_VERTICAL,
+                          pos = (1, 0))
+            gridSizer.Add(item = self.option,
+                          pos = (1, 1))
+            sizer.Add(item = gridSizer,
+                      proportion = 1, flag = wx.EXPAND | wx.ALL,
+                      border = 5)
+            
+    def _getOptions(self):
+        """!Get relevant options"""
+        items = []
+        fromShape = self.shape.GetFrom()
+        if not isinstance(fromShape, ModelData):
+            GError(parent = self.parent,
+                   message = _("Relation doesn't start with data item.\n"
+                               "Unable to add relation."))
+            return items
+        
+        toShape = self.shape.GetTo()
+        if not isinstance(toShape, ModelAction):
+            GError(parent = self.parent,
+                   message = _("Relation doesn't point to GRASS command.\n"
+                               "Unable to add relation."))
+            return items
+        
+        prompt = fromShape.GetPrompt()
+        task = toShape.GetTask()
+        for p in task.get_options()['params']:
+            if p.get('prompt', '') == prompt and \
+                    'name' in p:
+                items.append(p['name'])
+        
+        if not items:
+            GError(parent = self.parent,
+                   message = _("No relevant option found.\n"
+                               "Unable to add relation."))
+        return items
+    
+    def GetOption(self):
+        """!Get selected option"""
+        return self.option.GetStringSelection()
+    
+    def IsValid(self):
+        """!Check if relation is valid"""
+        return self.valid
+    
+    def OnOption(self, event):
+        """!Set option"""
+        if event.GetString():
+            self.btnOk.Enable()
+        else:
+            self.btnOk.Enable(False)
+
+class ModelParamDialog(wx.Dialog):
+    def __init__(self, parent, params, id = wx.ID_ANY, title = _("Model parameters"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        """!Model parameters dialog
+        """
+        self.parent = parent
+        self.params = params
+        self.tasks  = list() # list of tasks/pages
+        
+        wx.Dialog.__init__(self, parent = parent, id = id, title = title, style = style, **kwargs)
+        
+        self.notebook = GNotebook(parent = self, 
+                                  style = globalvar.FNPageDStyle)
+        
+        panel = self._createPages()
+        wx.CallAfter(self.notebook.SetSelection, 0)
+        
+        self.btnCancel = wx.Button(parent = self, id = wx.ID_CANCEL)
+        self.btnRun    = wx.Button(parent = self, id = wx.ID_OK,
+                                   label = _("&Run"))
+        self.btnRun.SetDefault()
+        
+        self._layout()
+        
+        size = self.GetBestSize()
+        self.SetMinSize(size)
+        self.SetSize((size.width, size.height +
+                      panel.constrained_size[1] -
+                      panel.panelMinHeight))
+                
+    def _layout(self):
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnRun)
+        btnSizer.Realize()
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = self.notebook, proportion = 1,
+                      flag = wx.EXPAND)
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+        
+    def _createPages(self):
+        """!Create for each parameterized module its own page"""
+        nameOrdered = [''] * len(self.params.keys())
+        for name, params in self.params.iteritems():
+            nameOrdered[params['idx']] = name
+        for name in nameOrdered:
+            params = self.params[name]
+            panel = self._createPage(name, params)
+            if name == 'variables':
+                name = _('Variables')
+            self.notebook.AddPage(page = panel, text = name)
+        
+        return panel
+    
+    def _createPage(self, name, params):
+        """!Define notebook page"""
+        if name in globalvar.grassCmd['all']:
+            task = gtask.grassTask(name)
+        else:
+            task = gtask.grassTask()
+        task.flags  = params['flags']
+        task.params = params['params']
+        
+        panel = menuform.cmdPanel(parent = self, id = wx.ID_ANY, task = task)
+        self.tasks.append(task)
+        
+        return panel
+
+    def GetErrors(self):
+        """!Check for errors, get list of messages"""
+        errList = list()
+        for task in self.tasks:
+            errList += task.get_cmd_error()
+        
+        return errList
+
+class ModelItemDialog(wx.Dialog):
+    """!Abstract item properties dialog"""
+    def __init__(self, parent, shape, title, id = wx.ID_ANY,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        self.parent = parent
+        self.shape = shape
+        
+        wx.Dialog.__init__(self, parent, id, title = title, style = style, **kwargs)
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.condBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                    label=" %s " % _("Condition"))
+        self.condText = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
+                                    value = shape.GetText())
+        
+        self.itemList = ItemCheckListCtrl(parent = self.panel,
+                                          window = self,
+                                          columns = [_("ID"), _("Name"),
+                                                     _("Command")],
+                                          shape = shape)
+        self.itemList.Populate(self.parent.GetModel().GetItems())
+        
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.btnOk     = wx.Button(parent = self.panel, id = wx.ID_OK)
+        self.btnOk.SetDefault()
+        
+    def _layout(self):
+        """!Do layout (virtual method)"""
+        pass
+    
+    def GetCondition(self):
+        """!Get loop condition"""
+        return self.condText.GetValue()
+
+class ModelLoopDialog(ModelItemDialog):
+    """!Loop properties dialog"""
+    def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Loop properties"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        ModelItemDialog.__init__(self, parent, shape, title,
+                                 style = style, **kwargs)
+        
+        self.listBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                    label=" %s " % _("List of items in loop"))
+        
+        self.btnSeries = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                   label = _("Series"))
+        self.btnSeries.SetToolTipString(_("Define map series as condition for the loop"))
+        self.btnSeries.Bind(wx.EVT_BUTTON, self.OnSeries)
+        
+        self._layout()
+        self.SetMinSize(self.GetSize())
+        self.SetSize((500, 400))
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        condSizer = wx.StaticBoxSizer(self.condBox, wx.HORIZONTAL)
+        condSizer.Add(item = self.condText, proportion = 1,
+                      flag = wx.ALL, border = 3)
+        condSizer.Add(item = self.btnSeries, proportion = 0,
+                      flag = wx.EXPAND)
+
+        listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
+        listSizer.Add(item = self.itemList, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOk)
+        btnSizer.Realize()
+
+        sizer.Add(item = condSizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALL, border = 3)
+        sizer.Add(item = listSizer, proportion = 1,
+                  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
+        sizer.Add(item = btnSizer, proportion=0,
+                  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
+        
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self.panel)
+        
+        self.Layout()
+        
+    def GetItems(self):
+        """!Get list of selected actions"""
+        return self.itemList.GetItems()
+
+    def OnSeries(self, event):
+        """!Define map series as condition"""
+        dialog = MapLayersDialog(parent = self, title = _("Define series of maps"), modeler = True)
+        if dialog.ShowModal() != wx.ID_OK:
+            dialog.Destroy()
+            return
+        
+        cond = dialog.GetDSeries()
+        if not cond:
+            cond = 'map in %s' % map(lambda x: str(x), dialog.GetMapLayers())
+        
+        self.condText.SetValue(cond)
+                               
+        dialog.Destroy()
+
+class ModelConditionDialog(ModelItemDialog):
+    """!Condition properties dialog"""
+    def __init__(self, parent, shape, id = wx.ID_ANY, title = _("If-else properties"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        ModelItemDialog.__init__(self, parent, shape, title,
+                                 style = style, **kwargs)
+        
+        self.listBoxIf = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                      label=" %s " % _("List of items in 'if' block"))
+        self.itemListIf = self.itemList
+        self.itemListIf.SetName('IfBlockList')
+        
+        self.listBoxElse = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                        label=" %s " % _("List of items in 'else' block"))
+        self.itemListElse = ItemCheckListCtrl(parent = self.panel,
+                                              window = self,
+                                              columns = [_("ID"), _("Name"),
+                                                         _("Command")],
+                                              shape = shape)
+        self.itemListElse.SetName('ElseBlockList')
+        self.itemListElse.Populate(self.parent.GetModel().GetItems())
+        
+        self._layout()
+        self.SetMinSize(self.GetSize())
+        self.SetSize((500, 400))
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        condSizer = wx.StaticBoxSizer(self.condBox, wx.VERTICAL)
+        condSizer.Add(item = self.condText, proportion = 1,
+                      flag = wx.EXPAND)
+        
+        listIfSizer = wx.StaticBoxSizer(self.listBoxIf, wx.VERTICAL)
+        listIfSizer.Add(item = self.itemListIf, proportion = 1,
+                        flag = wx.EXPAND)
+        listElseSizer = wx.StaticBoxSizer(self.listBoxElse, wx.VERTICAL)
+        listElseSizer.Add(item = self.itemListElse, proportion = 1,
+                          flag = wx.EXPAND)
+        
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOk)
+        btnSizer.Realize()
+
+        sizer.Add(item = condSizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALL, border = 3)
+        sizer.Add(item = listIfSizer, proportion = 1,
+                  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
+        sizer.Add(item = listElseSizer, proportion = 1,
+                  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
+        sizer.Add(item = btnSizer, proportion=0,
+                  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
+        
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self.panel)
+        
+        self.Layout()
+
+    def OnCheckItemIf(self, index, flag):
+        """!Item in if-block checked/unchecked"""
+        if flag is False:
+            return
+        
+        aId = int(self.itemListIf.GetItem(index, 0).GetText())
+        if aId in self.itemListElse.GetItems()['checked']:
+            self.itemListElse.CheckItemById(aId, False)
+            
+    def OnCheckItemElse(self, index, flag):
+        """!Item in else-block checked/unchecked"""
+        if flag is False:
+            return
+        
+        aId = int(self.itemListElse.GetItem(index, 0).GetText())
+        if aId in self.itemListIf.GetItems()['checked']:
+            self.itemListIf.CheckItemById(aId, False)
+        
+    def GetItems(self):
+        """!Get items"""
+        return { 'if'   : self.itemListIf.GetItems(),
+                 'else' : self.itemListElse.GetItems() }


Property changes on: grass/trunk/gui/wxpython/gmodeler/dialogs.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Copied: grass/trunk/gui/wxpython/gmodeler/frame.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/gmodeler.py)
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/frame.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gmodeler/frame.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,2656 @@
+"""!
+ at package gmodeler.py
+
+ at brief wxGUI Graphical Modeler for creating, editing, and managing models
+
+Classes:
+ - ModelerData
+ - ModelToolbar
+ - ModelFrame
+ - ModelCanvas
+ - ModelObject
+ - ModelAction
+ - ModelData
+ - ModelEvtHandler
+ - ModelRelation
+ - ModelListCtrl
+ - VariablePanel
+ - ValiableListCtrl
+ - ModelItem
+ - ModelLoop
+ - ItemPanel
+ - ItemListCtrl
+ - ItemCheckListCtrl
+ - ModelCondition
+
+(C) 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import sys
+import time
+import stat
+import textwrap
+import tempfile
+import copy
+import re
+
+import wx
+import wx.lib.ogl             as ogl
+import wx.lib.flatnotebook    as FN
+import wx.lib.mixins.listctrl as listmix
+
+from core                 import globalvar
+from gui_core.widgets     import GNotebook
+from gui_core.goutput     import GMConsole
+from core.debug           import Debug
+from core.gcmd            import GMessage, GException, GWarning, GError, RunCommand
+from gui_core.dialogs     import GetImageHandlers
+from gui_core.preferences import PreferencesBaseDialog
+from core.settings        import UserSettings
+from core.menudata        import MenuData
+from gui_core.toolbars    import BaseToolbar
+
+from grass.script import core as grass
+
+class ModelerData(MenuData):
+    def __init__(self, filename = None):
+        if not filename:
+            gisbase = os.getenv('GISBASE')
+            global etcwxdir
+	    filename = os.path.join(globalvar.ETCWXDIR, 'xml', 'menudata_modeler.xml')
+        
+        MenuData.__init__(self, filename)
+
+class ModelToolbar(BaseToolbar):
+    """!Graphical modeler toolbaro (see gmodeler.py)
+    """
+    def __init__(self, parent):
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+        
+        # realize the toolbar
+        self.Realize()
+        
+    def _toolbarData(self):
+        """!Toolbar data"""
+        icons = Icons['modeler']
+        return self._getToolbarData((('new', icons['new'],
+                                      self.parent.OnModelNew),
+                                     ('open', icons['open'],
+                                      self.parent.OnModelOpen),
+                                     ('save', icons['save'],
+                                      self.parent.OnModelSave),
+                                     ('image', icons['toImage'],
+                                      self.parent.OnExportImage),
+                                     ('python', icons['toPython'],
+                                      self.parent.OnExportPython),
+                                     (None, ),
+                                     ('action', icons['actionAdd'],
+                                      self.parent.OnAddAction),
+                                     ('data', icons['dataAdd'],
+                                      self.parent.OnAddData),
+                                     ('relation', icons['relation'],
+                                      self.parent.OnDefineRelation),
+                                     ('loop', icons['loop'],
+                                      self.parent.OnDefineLoop),
+                                     (None, ),
+                                     ('redraw', icons['redraw'],
+                                      self.parent.OnCanvasRefresh),
+                                     ('validate', icons['validate'],
+                                      self.parent.OnValidateModel),
+                                     ('run', icons['run'],
+                                      self.parent.OnRunModel),
+                                     (None, ),
+                                     ("variables", icons['variables'],
+                                      self.parent.OnVariables),
+                                     ("settings", icons['settings'],
+                                      self.parent.OnPreferences),
+                                     ("help", Icons['misc']['help'],
+                                      self.parent.OnHelp),
+                                     (None, ),
+                                     ('quit', icons['quit'],
+                                      self.parent.OnCloseWindow))
+                                    
+)
+
+class ModelFrame(wx.Frame):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 title = _("GRASS GIS Graphical Modeler"), **kwargs):
+        """!Graphical modeler main window
+        
+        @param parent parent window
+        @param id window id
+        @param title window title
+
+        @param kwargs wx.Frames' arguments
+        """
+        self.parent = parent
+        self.searchDialog = None # module search dialog
+        self.baseTitle = title
+        self.modelFile = None    # loaded model
+        self.modelChanged = False
+        
+        self.cursors = {
+            "default" : wx.StockCursor(wx.CURSOR_ARROW),
+            "cross"   : wx.StockCursor(wx.CURSOR_CROSS),
+            }
+        
+        wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
+        self.SetName("Modeler")
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+        
+        self.menubar = Menu(parent = self, data = ModelerData())
+        
+        self.SetMenuBar(self.menubar)
+        
+        self.toolbar = ModelToolbar(parent = self)
+        self.SetToolBar(self.toolbar)
+        
+        self.statusbar = self.CreateStatusBar(number = 1)
+        
+        self.notebook = GNotebook(parent = self,
+                                  style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
+                                  FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
+        
+        self.canvas = ModelCanvas(self)
+        self.canvas.SetBackgroundColour(wx.WHITE)
+        self.canvas.SetCursor(self.cursors["default"])
+        
+        self.model = Model(self.canvas)
+        
+        self.variablePanel = VariablePanel(parent = self)
+        
+        self.itemPanel = ItemPanel(parent = self)
+        
+        self.goutput = GMConsole(parent = self, notebook = self.notebook)
+        
+        self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
+        self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
+        self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
+        self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
+        wx.CallAfter(self.notebook.SetSelectionByName, 'model')
+        wx.CallAfter(self.ModelChanged, False)
+
+        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        
+        self._layout()
+        self.SetMinSize((475, 300))
+        self.SetSize((640, 480))
+        
+        # fix goutput's pane size
+        if self.goutput:
+            self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        sizer.Add(item = self.notebook, proportion = 1,
+                  flag = wx.EXPAND)
+        
+        self.SetAutoLayout(True)
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        
+        self.Layout()
+
+    def _addEvent(self, item):
+        """!Add event to item"""
+        evthandler = ModelEvtHandler(self.statusbar,
+                                     self)
+        evthandler.SetShape(item)
+        evthandler.SetPreviousHandler(item.GetEventHandler())
+        item.SetEventHandler(evthandler)
+
+    def GetCanvas(self):
+        """!Get canvas"""
+        return self.canvas
+    
+    def GetModel(self):
+        """!Get model"""
+        return self.model
+    
+    def ModelChanged(self, changed = True):
+        """!Update window title"""
+        self.modelChanged = changed
+        
+        if self.modelFile:
+            if self.modelChanged:
+                self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile) + '*')
+            else:
+                self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
+        else:
+            self.SetTitle(self.baseTitle)
+        
+    def OnVariables(self, event):
+        """!Switch to variables page"""
+        self.notebook.SetSelectionByName('variables')
+        
+    def OnRemoveItem(self, event):
+        """!Remove shape
+        """
+        self.GetCanvas().RemoveSelected()
+        
+    def OnCanvasRefresh(self, event):
+        """!Refresh canvas"""
+        self.SetStatusText(_("Redrawing model..."), 0)
+        self.GetCanvas().Refresh()
+        self.SetStatusText("", 0)
+
+    def OnCmdRun(self, event):
+        """!Run command"""
+        try:
+            action = self.GetModel().GetItems()[event.pid]
+            if hasattr(action, "task"):
+                action.Update(running = True)
+        except IndexError:
+            pass
+        
+    def OnCmdPrepare(self, event):
+        """!Prepare for running command"""
+        event.onPrepare(item = event.userData['item'],
+                        params = event.userData['params'])
+        
+    def OnCmdDone(self, event):
+        """!Command done (or aborted)"""
+        try:
+            action = self.GetModel().GetItems()[event.pid]
+            if hasattr(action, "task"):
+                action.Update(running = True)
+        except IndexError:
+            pass
+        
+    def OnCloseWindow(self, event):
+        """!Close window"""
+        if self.modelChanged and \
+                UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
+            if self.modelFile:
+                message = _("Do you want to save changes in the model?")
+            else:
+                message = _("Do you want to store current model settings "
+                            "to model file?")
+            
+            # ask user to save current settings
+            dlg = wx.MessageDialog(self,
+                                   message = message,
+                                   caption=_("Quit Graphical Modeler"),
+                                   style = wx.YES_NO | wx.YES_DEFAULT |
+                                   wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
+            ret = dlg.ShowModal()
+            if ret == wx.ID_YES:
+                if not self.modelFile:
+                        self.OnWorkspaceSaveAs()
+                else:
+                    self.WriteModelFile(self.modelFile)
+            elif ret == wx.ID_CANCEL:
+                dlg.Destroy()
+                return
+            dlg.Destroy()
+        
+        self.Destroy()
+
+    def OnSize(self, event):
+        """Window resized, save to the model"""
+        self.ModelChanged()
+        event.Skip()
+        
+    def OnPreferences(self, event):
+        """!Open preferences dialog"""
+        dlg = PreferencesDialog(parent = self)
+        dlg.CenterOnParent()
+        
+        dlg.ShowModal()
+        self.canvas.Refresh()
+        
+    def OnHelp(self, event):
+        """!Show help"""
+        if self.parent and self.parent.GetName() == 'LayerManager':
+            log = self.parent.GetLogWindow()
+            log.RunCmd(['g.manual',
+                        'entry=wxGUI.Modeler'])
+        else:
+            RunCommand('g.manual',
+                       quiet = True,
+                       entry = 'wxGUI.Modeler')
+        
+    def OnModelProperties(self, event):
+        """!Model properties dialog"""
+        dlg = PropertiesDialog(parent = self)
+        dlg.CentreOnParent()
+        properties = self.model.GetProperties()
+        dlg.Init(properties)
+        if dlg.ShowModal() == wx.ID_OK:
+            self.ModelChanged()
+            for key, value in dlg.GetValues().iteritems():
+                properties[key] = value
+            for action in self.model.GetItems(objType = ModelAction):
+                action.GetTask().set_flag('overwrite', properties['overwrite'])
+        
+        dlg.Destroy()
+        
+    def OnDeleteData(self, event):
+        """!Delete intermediate data"""
+        rast, vect, rast3d, msg = self.model.GetIntermediateData()
+        
+        if not rast and not vect and not rast3d:
+            GMessage(parent = self,
+                     message = _('Nothing to delete.'))
+            return
+        
+        dlg = wx.MessageDialog(parent = self,
+                               message= _("Do you want to permanently delete data?%s" % msg),
+                               caption=_("Delete intermediate data?"),
+                               style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+        
+        ret = dlg.ShowModal()
+        if ret == wx.ID_YES:
+            dlg.Destroy()
+            
+            if rast:
+                self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
+            if rast3d:
+                self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
+            if vect:
+                self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
+            
+            self.SetStatusText(_("%d maps deleted from current mapset") % \
+                                 int(len(rast) + len(rast3d) + len(vect)))
+            return
+        
+        dlg.Destroy()
+                
+    def OnModelNew(self, event):
+        """!Create new model"""
+        Debug.msg(4, "ModelFrame.OnModelNew():")
+        
+        # ask user to save current model
+        if self.modelFile and self.modelChanged:
+            self.OnModelSave()
+        elif self.modelFile is None and \
+                (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
+            dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
+                                                   "Do you want to store current settings "
+                                                   "to model file?"),
+                                   caption=_("Create new model?"),
+                                   style=wx.YES_NO | wx.YES_DEFAULT |
+                                   wx.CANCEL | wx.ICON_QUESTION)
+            ret = dlg.ShowModal()
+            if ret == wx.ID_YES:
+                self.OnModelSaveAs()
+            elif ret == wx.ID_CANCEL:
+                dlg.Destroy()
+                return
+            
+            dlg.Destroy()
+        
+        # delete all items
+        self.canvas.GetDiagram().DeleteAllShapes()
+        self.model.Reset()
+        self.canvas.Refresh()
+        self.itemPanel.Update()
+        self.variablePanel.Reset()
+        
+        # no model file loaded
+        self.modelFile = None
+        self.modelChanged = False
+        self.SetTitle(self.baseTitle)
+        
+    def OnModelOpen(self, event):
+        """!Load model from file"""
+        filename = ''
+        dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
+                            defaultDir = os.getcwd(),
+                            wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+                    
+        if not filename:
+            return
+        
+        Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
+        
+        # close current model
+        self.OnModelClose()
+        
+        self.LoadModelFile(filename)
+        
+        self.modelFile = filename
+        self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
+        self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
+                               { 'items' : self.model.GetNumItems(),
+                                 'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
+        
+    def OnModelSave(self, event = None):
+        """!Save model to file"""
+        if self.modelFile and self.modelChanged:
+            dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
+                                                   "Do you want to overwrite this file?") % \
+                                       self.modelFile,
+                                   caption=_("Save model"),
+                                   style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+            if dlg.ShowModal() == wx.ID_NO:
+                dlg.Destroy()
+            else:
+                Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
+                self.WriteModelFile(self.modelFile)
+                self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
+                self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
+        elif not self.modelFile:
+            self.OnModelSaveAs(None)
+        
+    def OnModelSaveAs(self, event):
+        """!Create model to file as"""
+        filename = ''
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Choose file to save current model"),
+                            defaultDir = os.getcwd(),
+                            wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
+                            style=wx.FD_SAVE)
+        
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+        
+        if not filename:
+            return
+        
+        # check for extension
+        if filename[-4:] != ".gxm":
+            filename += ".gxm"
+        
+        if os.path.exists(filename):
+            dlg = wx.MessageDialog(parent = self,
+                                   message=_("Model file <%s> already exists. "
+                                             "Do you want to overwrite this file?") % filename,
+                                   caption=_("File already exists"),
+                                   style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+            if dlg.ShowModal() != wx.ID_YES:
+                dlg.Destroy()
+                return
+        
+        Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
+        
+        self.WriteModelFile(filename)
+        self.modelFile = filename
+        self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
+        self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
+
+    def OnModelClose(self, event = None):
+        """!Close model file"""
+        Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
+        # ask user to save current model
+        if self.modelFile and self.modelChanged:
+            self.OnModelSave()
+        elif self.modelFile is None and \
+                (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
+            dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
+                                                   "Do you want to store current settings "
+                                                   "to model file?"),
+                                   caption=_("Create new model?"),
+                                   style=wx.YES_NO | wx.YES_DEFAULT |
+                                   wx.CANCEL | wx.ICON_QUESTION)
+            ret = dlg.ShowModal()
+            if ret == wx.ID_YES:
+                self.OnModelSaveAs()
+            elif ret == wx.ID_CANCEL:
+                dlg.Destroy()
+                return
+            
+            dlg.Destroy()
+        
+        self.modelFile = None
+        self.SetTitle(self.baseTitle)
+        
+        self.canvas.GetDiagram().DeleteAllShapes()
+        self.model.Reset()
+        
+        self.canvas.Refresh()
+        
+    def OnRunModel(self, event):
+        """!Run entire model"""
+        self.model.Run(self.goutput, self.OnDone, parent = self)
+        
+    def OnDone(self, cmd, returncode):
+        """!Computation finished"""
+        self.SetStatusText('', 0)
+        # restore original files
+        if hasattr(self.model, "fileInput"):
+            for finput in self.model.fileInput:
+                data = self.model.fileInput[finput]
+                if not data:
+                    continue
+                
+                fd = open(finput, "w")
+                try:
+                    fd.write(data)
+                finally:
+                    fd.close()
+            del self.model.fileInput
+        
+    def OnValidateModel(self, event, showMsg = True):
+        """!Validate entire model"""
+        if self.model.GetNumItems() < 1:
+            GMessage(parent = self, 
+                     message = _('Model is empty. Nothing to validate.'))
+            return
+        
+        
+        self.SetStatusText(_('Validating model...'), 0)
+        errList = self.model.Validate()
+        self.SetStatusText('', 0)
+        
+        if errList:
+            GWarning(parent = self,
+                     message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
+        else:
+            GMessage(parent = self,
+                     message = _('Model is valid.'))
+    
+    def OnExportImage(self, event):
+        """!Export model to image (default image)
+        """
+        xminImg = 0
+        xmaxImg = 0
+        yminImg = 0
+        ymaxImg = 0
+        # get current size of canvas
+        for shape in self.canvas.GetDiagram().GetShapeList():
+            w, h = shape.GetBoundingBoxMax()
+            x    = shape.GetX()
+            y    = shape.GetY()
+            xmin = x - w / 2
+            xmax = x + w / 2
+            ymin = y - h / 2
+            ymax = y + h / 2
+            if xmin < xminImg:
+                xminImg = xmin
+            if xmax > xmaxImg:
+                xmaxImg = xmax
+            if ymin < yminImg:
+                yminImg = ymin
+            if ymax > ymaxImg:
+                ymaxImg = ymax
+        size = wx.Size(int(xmaxImg - xminImg) + 50,
+                       int(ymaxImg - yminImg) + 50)
+        bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
+        
+        filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
+        
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Choose a file name to save the image (no need to add extension)"),
+                            defaultDir = "",
+                            defaultFile = "",
+                            wildcard = filetype,
+                            style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            if not path:
+                dlg.Destroy()
+                return
+            
+            base, ext = os.path.splitext(path)
+            fileType = ltype[dlg.GetFilterIndex()]['type']
+            extType  = ltype[dlg.GetFilterIndex()]['ext']
+            if ext != extType:
+                path = base + '.' + extType
+            
+            dc = wx.MemoryDC(bitmap)
+            dc.SetBackground(wx.WHITE_BRUSH)
+            dc.SetBackgroundMode(wx.SOLID)
+            
+            dc.BeginDrawing()
+            self.canvas.GetDiagram().Clear(dc)
+            self.canvas.GetDiagram().Redraw(dc)
+            dc.EndDrawing()
+            
+            bitmap.SaveFile(path, fileType)
+            self.SetStatusText(_("Model exported to <%s>") % path)
+        
+        dlg.Destroy()
+        
+    def OnExportPython(self, event):
+        """!Export model to Python script"""
+        filename = ''
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Choose file to save"),
+                            defaultDir = os.getcwd(),
+                            wildcard=_("Python script (*.py)|*.py"),
+                            style=wx.FD_SAVE)
+        
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+        
+        if not filename:
+            return
+        
+        # check for extension
+        if filename[-3:] != ".py":
+            filename += ".py"
+        
+        if os.path.exists(filename):
+            dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
+                                                   "Do you want to overwrite this file?") % filename,
+                                   caption=_("Save file"),
+                                   style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+            if dlg.ShowModal() == wx.ID_NO:
+                dlg.Destroy()
+                return
+            
+            dlg.Destroy()
+        
+        fd = open(filename, "w")
+        try:
+            WritePythonFile(fd, self.model)
+        finally:
+            fd.close()
+        
+        # executable file
+        os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
+        
+        self.SetStatusText(_("Model exported to <%s>") % filename)
+
+    def OnDefineRelation(self, event):
+        """!Define relation between data and action items"""
+        self.canvas.SetCursor(self.cursors["cross"])
+        self.defineRelation = { 'from' : None,
+                                'to'   : None }
+        
+    def OnDefineLoop(self, event):
+        """!Define new loop in the model"""
+        self.ModelChanged()
+        
+        width, height = self.canvas.GetSize()
+        loop = ModelLoop(self, x = width/2, y = height/2,
+                         id = self.model.GetNumItems() + 1)
+        self.canvas.diagram.AddShape(loop)
+        loop.Show(True)
+        
+        self._addEvent(loop)
+        self.model.AddItem(loop)
+        
+        self.canvas.Refresh()
+        
+    def OnDefineCondition(self, event):
+        """!Define new condition in the model"""
+        self.ModelChanged()
+        
+        width, height = self.canvas.GetSize()
+        cond = ModelCondition(self, x = width/2, y = height/2,
+                              id = self.model.GetNumItems() + 1)
+        self.canvas.diagram.AddShape(cond)
+        cond.Show(True)
+        
+        self._addEvent(cond)
+        self.model.AddItem(cond)
+        
+        self.canvas.Refresh()
+    
+    def OnAddAction(self, event):
+        """!Add action to model"""
+        if self.searchDialog is None:
+            self.searchDialog = ModelSearchDialog(self)
+            self.searchDialog.CentreOnParent()
+        else:
+            self.searchDialog.Reset()
+            
+        if self.searchDialog.ShowModal() == wx.ID_CANCEL:
+            self.searchDialog.Hide()
+            return
+            
+        cmd = self.searchDialog.GetCmd()
+        self.searchDialog.Hide()
+        
+        self.ModelChanged()
+        
+        # add action to canvas
+        width, height = self.canvas.GetSize()
+        action = ModelAction(self.model, cmd = cmd, x = width/2, y = height/2,
+                             id = self.model.GetNextId())
+        overwrite = self.model.GetProperties().get('overwrite', None)
+        if overwrite is not None:
+            action.GetTask().set_flag('overwrite', overwrite)
+        
+        self.canvas.diagram.AddShape(action)
+        action.Show(True)
+
+        self._addEvent(action)
+        self.model.AddItem(action)
+        
+        self.itemPanel.Update()
+        self.canvas.Refresh()
+        time.sleep(.1)
+        
+        # show properties dialog
+        win = action.GetPropDialog()
+        if not win:
+            if action.IsValid():
+                self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
+                                params = action.GetParams(), propwin = None)
+            else:
+                GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
+                                                             completed = (self.GetOptData, action, action.GetParams()))
+        elif win and not win.IsShown():
+            win.Show()
+        
+        if win:
+            win.Raise()
+        
+    def OnAddData(self, event):
+        """!Add data item to model
+        """
+        # add action to canvas
+        width, height = self.canvas.GetSize()
+        data = ModelData(self, x = width/2, y = height/2)
+       
+        dlg = ModelDataDialog(parent = self, shape = data)
+        data.SetPropDialog(dlg)
+        dlg.CentreOnParent()
+        ret = dlg.ShowModal()
+        dlg.Destroy()
+        if ret != wx.ID_OK:
+            return
+        
+        data.Update()
+        self.canvas.diagram.AddShape(data)
+        data.Show(True)
+        
+        self.ModelChanged()
+        
+        self._addEvent(data)
+        self.model.AddItem(data)
+        
+        self.canvas.Refresh()
+        
+        
+    def OnHelp(self, event):
+        """!Display manual page"""
+        grass.run_command('g.manual',
+                          entry = 'wxGUI.Modeler')
+
+    def OnAbout(self, event):
+        """!Display About window"""
+        info = wx.AboutDialogInfo()
+
+        info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+        info.SetName(_('wxGUI Graphical Modeler'))
+        info.SetWebSite('http://grass.osgeo.org')
+        year = grass.version()['date']
+        info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year + 
+                            '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
+                                                      '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
+        
+        wx.AboutBox(info)
+        
+    def GetOptData(self, dcmd, layer, params, propwin):
+        """!Process action data"""
+        if params: # add data items
+            width, height = self.canvas.GetSize()
+            x = [width/2 + 200, width/2 - 200]
+            for p in params['params']:
+                if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
+                        (p.get('value', None) or \
+                             (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
+                    data = layer.FindData(p.get('name', ''))
+                    if data:
+                        data.SetValue(p.get('value', ''))
+                        data.Update()
+                        continue
+                    
+                    data = self.model.FindData(p.get('value', ''),
+                                               p.get('prompt', ''))
+                    if data:
+                        if p.get('age', 'old') == 'old':
+                            rel = ModelRelation(parent = self, fromShape = data,
+                                                toShape = layer, param = p.get('name', ''))
+                        else:
+                            rel = ModelRelation(parent = self, fromShape = layer,
+                                                toShape = data, param = p.get('name', ''))
+                        layer.AddRelation(rel)
+                        data.AddRelation(rel)
+                        self.AddLine(rel)
+                        data.Update()
+                        continue
+                    
+                    data = ModelData(self, value = p.get('value', ''),
+                                     prompt = p.get('prompt', ''),
+                                     x = x.pop(), y = height/2)
+                    self._addEvent(data)
+                    self.canvas.diagram.AddShape(data)
+                    data.Show(True)
+                                                            
+                    if p.get('age', 'old') == 'old':
+                        rel = ModelRelation(parent = self, fromShape = data,
+                                            toShape = layer, param = p.get('name', ''))
+                    else:
+                        rel = ModelRelation(parent = self, fromShape = layer,
+                                            toShape = data, param = p.get('name', ''))
+                    layer.AddRelation(rel)
+                    data.AddRelation(rel)
+                    self.AddLine(rel)
+                    data.Update()
+            
+            # valid / parameterized ?
+            layer.SetValid(params)
+            
+            self.canvas.Refresh()
+        
+        if dcmd:
+            layer.SetProperties(params, propwin)
+            
+        self.SetStatusText(layer.GetLog(), 0)
+        
+    def AddLine(self, rel):
+        """!Add connection between model objects
+        
+        @param rel relation
+        """
+        fromShape = rel.GetFrom()
+        toShape   = rel.GetTo()
+        
+        rel.SetCanvas(self)
+        rel.SetPen(wx.BLACK_PEN)
+        rel.SetBrush(wx.BLACK_BRUSH)
+        rel.AddArrow(ogl.ARROW_ARROW)
+        points = rel.GetControlPoints()
+        rel.MakeLineControlPoints(2)
+        if points:
+            for x, y in points:
+                rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
+        
+        self._addEvent(rel)
+        try:
+            fromShape.AddLine(rel, toShape)
+        except TypeError:
+            pass # bug when connecting ModelCondition and ModelLoop - to be fixed
+        
+        self.canvas.diagram.AddShape(rel)
+        rel.Show(True)
+        
+    def LoadModelFile(self, filename):
+        """!Load model definition stored in GRASS Model XML file (gxm)
+        """
+        try:
+            self.model.LoadModel(filename)
+        except GException, e:
+            GError(parent = self,
+                   message = _("Reading model file <%s> failed.\n"
+                               "Invalid file, unable to parse XML document.") % filename)
+        
+        self.modelFile = filename
+        self.SetTitle(self.baseTitle + " - " +  os.path.basename(self.modelFile))
+        
+        self.SetStatusText(_("Please wait, loading model..."), 0)
+        
+        # load actions
+        for item in self.model.GetItems(objType = ModelAction):
+            self._addEvent(item)
+            self.canvas.diagram.AddShape(item)
+            item.Show(True)
+            # relations/data
+            for rel in item.GetRelations():
+                if rel.GetFrom() == item:
+                    dataItem = rel.GetTo()
+                else:
+                    dataItem = rel.GetFrom()
+                self._addEvent(dataItem)
+                self.canvas.diagram.AddShape(dataItem)
+                self.AddLine(rel)
+                dataItem.Show(True)
+        
+        # load loops
+        for item in self.model.GetItems(objType = ModelLoop):
+            self._addEvent(item)
+            self.canvas.diagram.AddShape(item)
+            item.Show(True)
+            
+            # connect items in the loop
+            self.DefineLoop(item)
+
+        # load conditions
+        for item in self.model.GetItems(objType = ModelCondition):
+            self._addEvent(item)
+            self.canvas.diagram.AddShape(item)
+            item.Show(True)
+            
+            # connect items in the condition
+            self.DefineCondition(item)
+        
+        # load variables
+        self.variablePanel.Update()
+        self.itemPanel.Update()
+        self.SetStatusText('', 0)
+        
+        # final updates
+        for action in self.model.GetItems(objType = ModelAction):
+            action.SetValid(action.GetParams())
+            action.Update()
+        
+        self.canvas.Refresh(True)
+        
+    def WriteModelFile(self, filename):
+        """!Save model to model file, recover original file on error.
+        
+        @return True on success
+        @return False on failure
+        """
+        self.ModelChanged(False)
+        tmpfile = tempfile.TemporaryFile(mode='w+b')
+        try:
+            WriteModelFile(fd = tmpfile, model = self.model)
+        except StandardError:
+            GError(parent = self,
+                   message = _("Writing current settings to model file failed."))
+            return False
+        
+        try:
+            mfile = open(filename, "w")
+            tmpfile.seek(0)
+            for line in tmpfile.readlines():
+                mfile.write(line)
+        except IOError:
+            wx.MessageBox(parent = self,
+                          message = _("Unable to open file <%s> for writing.") % filename,
+                          caption = _("Error"),
+                          style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            return False
+        
+        mfile.close()
+        
+        return True
+    
+    def DefineLoop(self, loop):
+        """!Define loop with given list of items"""
+        parent = loop
+        items = loop.GetItems()
+        if not items:
+            return
+        
+        # remove defined relations first
+        for rel in loop.GetRelations():
+            self.canvas.GetDiagram().RemoveShape(rel)
+        loop.Clear()
+        
+        for item in items:
+            rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
+            dx = item.GetX() - parent.GetX()
+            dy = item.GetY() - parent.GetY()
+            loop.AddRelation(rel)
+            if dx != 0:
+                rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
+                                      (parent.GetX() + dx, parent.GetY() + dy / 2)))
+            self.AddLine(rel)
+            parent = item
+        
+        # close loop
+        item = loop.GetItems()[-1]
+        rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
+        loop.AddRelation(rel)
+        self.AddLine(rel)
+        dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
+        dy = item.GetHeight() / 2 + 50
+        rel.MakeLineControlPoints(0)
+        rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
+                                                        loop.GetY()))
+        rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
+                                                        item.GetY() + item.GetHeight() / 2))
+        rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
+                                                        item.GetY() + dy))
+        rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
+                                                        item.GetY() + dy))
+        rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
+                                                        loop.GetY()))
+        
+        self.canvas.Refresh()
+
+    def DefineCondition(self, condition):
+        """!Define if-else statement with given list of items"""
+        parent = condition
+        items = condition.GetItems()
+        if not items['if'] and not items['else']:
+            return
+        
+        # remove defined relations first
+        for rel in condition.GetRelations():
+            self.canvas.GetDiagram().RemoveShape(rel)
+        condition.Clear()
+        dxIf   = condition.GetX() + condition.GetWidth() / 2
+        dxElse = condition.GetX() - condition.GetWidth() / 2
+        dy     = condition.GetY()
+        for branch in items.keys():
+            for item in items[branch]:
+                rel = ModelRelation(parent = self, fromShape = parent,
+                                    toShape = item)
+                condition.AddRelation(rel)
+                self.AddLine(rel)
+                rel.MakeLineControlPoints(0)
+                if branch == 'if':
+                    rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
+                    rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
+                else:
+                    rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
+                    rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
+                parent = item
+        
+        self.canvas.Refresh()
+        
+class ModelCanvas(ogl.ShapeCanvas):
+    """!Canvas where model is drawn"""
+    def __init__(self, parent):
+        self.parent = parent
+        ogl.OGLInitialize()
+        ogl.ShapeCanvas.__init__(self, parent)
+        
+        self.diagram = ogl.Diagram()
+        self.SetDiagram(self.diagram)
+        self.diagram.SetCanvas(self)
+        
+        self.SetScrollbars(20, 20, 1000/20, 1000/20)
+        
+        self.Bind(wx.EVT_CHAR,  self.OnChar)
+        
+    def OnChar(self, event):
+        """!Key pressed"""
+        kc = event.GetKeyCode()
+        diagram = self.GetDiagram()
+        if kc == wx.WXK_DELETE:
+            self.RemoveSelected()
+        
+    def RemoveSelected(self):
+        """!Remove selected shapes"""
+        self.parent.ModelChanged()
+        
+        diagram = self.GetDiagram()
+        for shape in diagram.GetShapeList():
+            if not shape.Selected():
+                continue
+            remList, upList = self.parent.GetModel().RemoveItem(shape)
+            shape.Select(False)
+            diagram.RemoveShape(shape)
+            shape.__del__()
+            for item in remList:
+                diagram.RemoveShape(item)
+                item.__del__()
+            
+            for item in upList:
+                item.Update()
+        
+        self.Refresh()
+        
+class ModelObject:
+    def __init__(self, id = -1):
+        self.id   = id
+        self.rels = list() # list of ModelRelations
+        
+        self.isEnabled = True
+        self.inBlock   = list() # list of related loops/conditions
+        
+    def __del__(self):
+        pass
+    
+    def GetId(self):
+        """!Get id"""
+        return self.id
+    
+    def AddRelation(self, rel):
+        """!Record new relation
+        """
+        self.rels.append(rel)
+
+    def GetRelations(self, fdir = None):
+        """!Get list of relations
+        
+        @param fdir True for 'from'
+        """
+        if fdir is None:
+            return self.rels
+        
+        result = list()
+        for rel in self.rels:
+            if fdir == 'from':
+                if rel.GetFrom() == self:
+                    result.append(rel)
+            else:
+                if rel.GetTo() == self:
+                    result.append(rel)
+        
+        return result
+    
+    def IsEnabled(self):
+        """!Get True if action is enabled, otherwise False"""
+        return self.isEnabled
+    
+    def Enable(self, enabled = True):
+        """!Enable/disable action"""
+        self.isEnabled = enabled
+        self.Update()
+
+    def Update(self):
+        pass
+
+    def SetBlock(self, item):
+        """!Add object to the block (loop/condition)
+
+        @param item reference to ModelLoop or ModelCondition which
+        defines loops/condition
+        """
+        if item not in self.inBlock:
+            self.inBlock.append(item)
+        
+    def UnSetBlock(self, item):
+        """!Remove object from the block (loop/consition)
+
+        @param item reference to ModelLoop or ModelCondition which
+        defines loops/codition
+        """
+        if item in self.inBlock:
+            self.inBlock.remove(item)
+        
+    def GetBlock(self):
+        """!Get list of related ModelObject(s) which defines block
+        (loop/condition)
+
+        @return list of ModelObjects
+        """
+        return self.inBlock
+    
+    def GetBlockId(self):
+        """!Get list of related ids which defines block
+
+        @return list of ids
+        """
+        ret = list()
+        for mo in self.inBlock:
+            ret.append(mo.GetId())
+        
+        return ret
+    
+class ModelAction(ModelObject, ogl.RectangleShape):
+    """!Action class (GRASS module)"""
+    def __init__(self, parent, x, y, id = -1, cmd = None, task = None, width = None, height = None):
+        ModelObject.__init__(self, id)
+        
+        self.parent  = parent
+        self.task    = task
+        
+        if not width:
+            width = UserSettings.Get(group='modeler', key='action', subkey=('size', 'width'))
+        if not height:
+            height = UserSettings.Get(group='modeler', key='action', subkey=('size', 'height'))
+        
+        if cmd:
+            self.task = GUI(show = None).ParseCommand(cmd = cmd)
+        else:
+            if task:
+                self.task = task
+            else:
+                self.task = None
+        
+        self.propWin = None
+        
+        self.data = list()   # list of connected data items
+        
+        self.isValid = False
+        self.isParameterized = False
+        
+        if self.parent.GetCanvas():
+            ogl.RectangleShape.__init__(self, width, height)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            self._setPen()
+            self._setBrush()
+            self.SetId(id)
+        
+        if self.task:
+            self.SetValid(self.task.get_options())
+        
+    def _setBrush(self, running = False):
+        """!Set brush"""
+        if running:
+            color = UserSettings.Get(group='modeler', key='action',
+                                     subkey=('color', 'running'))
+        elif not self.isEnabled:
+            color = UserSettings.Get(group='modeler', key='disabled',
+                                     subkey='color')
+        elif self.isValid:
+            color = UserSettings.Get(group='modeler', key='action',
+                                     subkey=('color', 'valid'))
+        else:
+            color = UserSettings.Get(group='modeler', key='action',
+                                     subkey=('color', 'invalid'))
+        
+        wxColor = wx.Color(color[0], color[1], color[2])
+        self.SetBrush(wx.Brush(wxColor))
+        
+    def _setPen(self):
+        """!Set pen"""
+        if self.isParameterized:
+            width = int(UserSettings.Get(group='modeler', key='action',
+                                         subkey=('width', 'parameterized')))
+        else:
+            width = int(UserSettings.Get(group='modeler', key='action',
+                                         subkey=('width', 'default')))
+        pen = self.GetPen()
+        pen.SetWidth(width)
+        self.SetPen(pen)
+
+    def SetId(self, id):
+        """!Set id"""
+        self.id = id
+        cmd = self.task.get_cmd(ignoreErrors = True)
+        if cmd and len(cmd) > 0:
+            self.ClearText()
+            self.AddText('(%d) %s' % (self.id, cmd[0]))
+        else:
+            self.AddText('(%d) <<%s>>' % (self.id, _("unknown")))
+        
+    def SetProperties(self, params, propwin):
+        """!Record properties dialog"""
+        self.task.params = params['params']
+        self.task.flags  = params['flags']
+        self.propWin = propwin
+
+    def GetPropDialog(self):
+        """!Get properties dialog"""
+        return self.propWin
+
+    def GetLog(self, string = True, substitute = None):
+        """!Get logging info
+
+        @param string True to get cmd as a string otherwise a list
+        @param substitute dictionary of parameter to substitute or None
+        """
+        cmd = self.task.get_cmd(ignoreErrors = True, ignoreRequired = True,
+                                ignoreDefault = False)
+        
+        # substitute variables
+        if substitute:
+            variables = []
+            if 'variables' in substitute:
+                for p in substitute['variables']['params']:
+                    variables.append(p.get('name', ''))
+            else:
+                variables = self.parent.GetVariables()
+            for variable in variables:
+                pattern= re.compile('%' + variable)
+                value = ''
+                if substitute and 'variables' in substitute:
+                    for p in substitute['variables']['params']:
+                        if variable == p.get('name', ''):
+                            if p.get('type', 'string') == 'string':
+                                value = p.get('value', '')
+                            else:
+                                value = str(p.get('value', ''))
+                            break
+                    
+                if not value:
+                    value = variables[variable].get('value', '')
+                
+                if not value:
+                    continue
+                
+                for idx in range(len(cmd)):
+                    if pattern.search(cmd[idx]):
+                        cmd[idx] = pattern.sub(value, cmd[idx])
+                        break
+                    idx += 1
+        
+        if string:
+            if cmd is None:
+                return ''
+            else:
+                return ' '.join(cmd)
+        
+        return cmd
+    
+    def GetName(self):
+        """!Get name"""
+        cmd = self.task.get_cmd(ignoreErrors = True)
+        if cmd and len(cmd) > 0:
+            return cmd[0]
+        
+        return _('unknown')
+
+    def GetParams(self, dcopy = False):
+        """!Get dictionary of parameters"""
+        if dcopy:
+            return copy.deepcopy(self.task.get_options())
+        
+        return self.task.get_options()
+
+    def GetTask(self):
+        """!Get grassTask instance"""
+        return self.task
+    
+    def SetParams(self, params):
+        """!Set dictionary of parameters"""
+        self.task.params = params['params']
+        self.task.flags  = params['flags']
+        
+    def MergeParams(self, params):
+        """!Merge dictionary of parameters"""
+        if 'flags' in params:
+            for f in params['flags']:
+                self.task.set_flag(f['name'],
+                                   f.get('value', False))
+        if 'params' in params:
+            for p in params['params']:
+                self.task.set_param(p['name'],
+                                    p.get('value', ''))
+        
+    def SetValid(self, options):
+        """!Set validity for action
+        
+        @param options dictionary with flags and params (gtask)
+        """
+        self.isValid = True
+        self.isParameterized = False
+        
+        for f in options['flags']:
+            if f.get('parameterized', False):
+                self.IsParameterized = True
+                break
+        
+        for p in options['params']:
+            if self.isValid and p.get('required', False) and \
+                    p.get('value', '') == '' and \
+                    p.get('default', '') == '':
+                self.isValid = False
+            if not self.isParameterized and p.get('parameterized', False):
+                self.isParameterized = True
+        
+        if self.parent.GetCanvas():
+            self._setBrush()
+            self._setPen()
+        
+    def IsValid(self):
+        """!Check validity (all required parameters set)"""
+        return self.isValid
+    
+    def IsParameterized(self):
+        """!Check if action is parameterized"""
+        return self.isParameterized
+    
+    def FindData(self, name):
+        """!Find data item by name"""
+        for rel in self.GetRelations():
+            data = rel.GetData()
+            if name == rel.GetName() and name in data.GetName():
+                return data
+        
+        return None
+
+    def Update(self, running = False):
+        """!Update action"""
+        if running:
+            self._setBrush(running = True)
+        else:
+            self._setBrush()
+        self._setPen()
+
+    def OnDraw(self, dc):
+        """!Draw action in canvas"""
+        self._setBrush()
+        self._setPen()
+        ogl.RectangleShape.OnDraw(self, dc)
+
+class ModelData(ModelObject, ogl.EllipseShape):
+    def __init__(self, parent, x, y, value = '', prompt = '', width = None, height = None):
+        """Data item class
+        
+        @param parent window parent
+        @param x, y   position of the shape
+        @param fname, tname list of parameter names from / to
+        @param value  value
+        @param prompt type of GIS element
+        @param width,height dimension of the shape
+        """
+        ModelObject.__init__(self)
+        
+        self.parent  = parent
+        self.value   = value
+        self.prompt  = prompt
+        self.intermediate = False
+        self.propWin = None
+        if not width:
+            width = UserSettings.Get(group='modeler', key='data', subkey=('size', 'width'))
+        if not height:
+            height = UserSettings.Get(group='modeler', key='data', subkey=('size', 'height'))
+        
+        if self.parent.GetCanvas():
+            ogl.EllipseShape.__init__(self, width, height)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            self._setBrush()
+            
+            self._setText()
+            
+    def IsIntermediate(self):
+        """!Checks if data item is intermediate"""
+        return self.intermediate
+    
+    def SetIntermediate(self, im):
+        """!Set intermediate flag"""
+        self.intermediate = im
+  
+    def OnDraw(self, dc):
+        pen = self.GetPen()
+        pen.SetWidth(1)
+        if self.intermediate:
+            pen.SetStyle(wx.SHORT_DASH)
+        else:
+            pen.SetStyle(wx.SOLID)
+        self.SetPen(pen)
+        
+        ogl.EllipseShape.OnDraw(self, dc)
+        
+    def GetLog(self, string = True):
+        """!Get logging info"""
+        name = list()
+        for rel in self.GetRelations():
+            name.append(rel.GetName())
+        if name:
+            return '/'.join(name) + '=' + self.value + ' (' + self.prompt + ')'
+        else:
+            return self.value + ' (' + self.prompt + ')'
+
+    def GetName(self):
+        """!Get list of names"""
+        name = list()
+        for rel in self.GetRelations():
+            name.append(rel.GetName())
+        
+        return name
+    
+    def GetPrompt(self):
+        """!Get prompt"""
+        return self.prompt
+
+    def SetPrompt(self, prompt):
+        """!Set prompt
+        
+        @param prompt
+        """
+        self.prompt = prompt
+        
+    def GetValue(self):
+        """!Get value"""
+        return self.value
+
+    def SetValue(self, value):
+        """!Set value
+
+        @param value
+        """
+        self.value = value
+        self._setText()
+        for direction in ('from', 'to'):
+            for rel in self.GetRelations(direction):
+                if direction == 'from':
+                    action = rel.GetTo()
+                else:
+                    action = rel.GetFrom()
+                
+                task = GUI(show = None).ParseCommand(cmd = action.GetLog(string = False))
+                task.set_param(rel.GetName(), self.value)
+                action.SetParams(params = task.get_options())
+        
+    def GetPropDialog(self):
+        """!Get properties dialog"""
+        return self.propWin
+
+    def SetPropDialog(self, win):
+        """!Get properties dialog"""
+        self.propWin = win
+
+    def _setBrush(self):
+        """!Set brush"""
+        if self.prompt == 'raster':
+            color = UserSettings.Get(group = 'modeler', key = 'data',
+                                     subkey = ('color', 'raster'))
+        elif self.prompt == 'raster3d':
+            color = UserSettings.Get(group = 'modeler', key = 'data',
+                                     subkey = ('color', 'raster3d'))
+        elif self.prompt == 'vector':
+            color = UserSettings.Get(group = 'modeler', key = 'data',
+                                     subkey = ('color', 'vector'))
+        else:
+            color = UserSettings.Get(group = 'modeler', key = 'action',
+                                     subkey = ('color', 'invalid'))
+        wxColor = wx.Color(color[0], color[1], color[2])
+        self.SetBrush(wx.Brush(wxColor))
+        
+    def _setPen(self):
+        """!Set pen"""
+        isParameterized = False
+        for rel in self.GetRelations('from'):
+            if rel.GetTo().IsParameterized():
+                isParameterized = True
+                break
+        if not isParameterized:
+            for rel in self.GetRelations('to'):
+                if rel.GetFrom().IsParameterized():
+                    isParameterized = True
+                    break
+
+        if isParameterized:
+            width = int(UserSettings.Get(group = 'modeler', key = 'action',
+                                         subkey = ('width', 'parameterized')))
+        else:
+            width = int(UserSettings.Get(group = 'modeler', key = 'action',
+                                         subkey = ('width', 'default')))
+        pen = self.GetPen()
+        pen.SetWidth(width)
+        self.SetPen(pen)
+        
+    def _setText(self):
+        """!Update text"""
+        self.ClearText()
+        name = []
+        for rel in self.GetRelations():
+            name.append(rel.GetName())
+        self.AddText('/'.join(name))
+        if self.value:
+            self.AddText(self.value)
+        else:
+            self.AddText(_('<not defined>'))
+        
+    def Update(self):
+        """!Update action"""
+        self._setBrush()
+        self._setPen()
+        self._setText()
+       
+class ModelEvtHandler(ogl.ShapeEvtHandler):
+    """!Model event handler class"""
+    def __init__(self, log, frame):
+        ogl.ShapeEvtHandler.__init__(self)
+        self.log = log
+        self.frame = frame
+        self.x = self.y = None
+        
+    def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+        """!Left mouse button pressed -> select item & update statusbar"""
+        shape = self.GetShape()
+        canvas = shape.GetCanvas()
+        dc = wx.ClientDC(canvas)
+        canvas.PrepareDC(dc)
+        
+        if hasattr(self.frame, 'defineRelation'):
+            drel = self.frame.defineRelation
+            if drel['from'] is None:
+                drel['from'] = shape
+            elif drel['to'] is None:
+                drel['to'] = shape
+                rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
+                                    toShape = drel['to'])
+                dlg = ModelRelationDialog(parent = self.frame,
+                                          shape = rel)
+                if dlg.IsValid():
+                    ret = dlg.ShowModal()
+                    if ret == wx.ID_OK:
+                        option = dlg.GetOption()
+                        rel.SetName(option)
+                        drel['from'].AddRelation(rel)
+                        drel['to'].AddRelation(rel)
+                        drel['from'].Update()
+                        params = { 'params' : [{ 'name' : option,
+                                                 'value' : drel['from'].GetValue()}] }
+                        drel['to'].MergeParams(params)
+                        self.frame.AddLine(rel)
+                
+                    dlg.Destroy()
+                del self.frame.defineRelation
+        
+        if shape.Selected():
+            shape.Select(False, dc)
+        else:
+            redraw = False
+            shapeList = canvas.GetDiagram().GetShapeList()
+            toUnselect = list()
+            
+            for s in shapeList:
+                if s.Selected():
+                    toUnselect.append(s)
+            
+            shape.Select(True, dc)
+            
+            for s in toUnselect:
+                s.Select(False, dc)
+                
+        canvas.Refresh(False)
+
+        if hasattr(shape, "GetLog"):
+            self.log.SetStatusText(shape.GetLog(), 0)
+        else:
+            self.log.SetStatusText('', 0)
+        
+    def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
+        """!Left mouse button pressed (double-click) -> show properties"""
+        self.OnProperties()
+        
+    def OnProperties(self, event = None):
+        """!Show properties dialog"""
+        self.frame.ModelChanged()
+        shape = self.GetShape()
+        if isinstance(shape, ModelAction):
+            module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
+                                                                        completed = (self.frame.GetOptData, shape, shape.GetParams()))
+        
+        elif isinstance(shape, ModelData):
+            dlg = ModelDataDialog(parent = self.frame, shape = shape)
+            shape.SetPropDialog(dlg)
+            dlg.CentreOnParent()
+            dlg.Show()
+        
+        elif isinstance(shape, ModelLoop):
+            dlg = ModelLoopDialog(parent = self.frame, shape = shape)
+            dlg.CentreOnParent()
+            if dlg.ShowModal() == wx.ID_OK:
+                shape.SetText(dlg.GetCondition())
+                alist = list()
+                ids = dlg.GetItems()
+                for aId in ids['unchecked']:
+                    action = self.frame.GetModel().GetItem(aId)
+                    action.UnSetBlock(shape)
+                for aId in ids['checked']:
+                    action = self.frame.GetModel().GetItem(aId)
+                    action.SetBlock(shape)
+                    if action:
+                        alist.append(action)
+                shape.SetItems(alist)
+                self.frame.DefineLoop(shape)
+                self.frame.SetStatusText(shape.GetLog(), 0)
+            self.frame.GetCanvas().Refresh()
+            
+            dlg.Destroy()
+        
+        elif isinstance(shape, ModelCondition):
+            dlg = ModelConditionDialog(parent = self.frame, shape = shape)
+            dlg.CentreOnParent()
+            if dlg.ShowModal() == wx.ID_OK:
+                shape.SetText(dlg.GetCondition())
+                ids = dlg.GetItems()
+                for b in ids.keys():
+                    alist = list()
+                    for aId in ids[b]['unchecked']:
+                        action = self.frame.GetModel().GetItem(aId)
+                        action.UnSetBlock(shape)
+                    for aId in ids[b]['checked']:
+                        action = self.frame.GetModel().GetItem(aId)
+                        action.SetBlock(shape)
+                        if action:
+                            alist.append(action)
+                    shape.SetItems(alist, branch = b)
+                self.frame.DefineCondition(shape)
+            self.frame.GetCanvas().Refresh()
+            
+            dlg.Destroy()
+                   
+    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+        """!Drag shape (begining)"""
+        self.frame.ModelChanged()
+        if self._previousHandler:
+            self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
+        
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        """!Drag shape (end)"""
+        if self._previousHandler:
+            self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
+        
+        shape = self.GetShape()
+        if isinstance(shape, ModelLoop):
+            self.frame.DefineLoop(shape)
+        elif isinstance(shape, ModelCondition):
+            self.frame.DefineCondition(shape)
+        
+        for mo in shape.GetBlock():
+            if isinstance(mo, ModelLoop):
+                self.frame.DefineLoop(mo)
+            elif isinstance(mo, ModelCondition):
+                self.frame.DefineCondition(mo)
+        
+    def OnEndSize(self, x, y):
+        """!Resize shape"""
+        self.frame.ModelChanged()
+        if self._previousHandler:
+            self._previousHandler.OnEndSize(x, y)
+        
+    def OnRightClick(self, x, y, keys = 0, attachment = 0):
+        """!Right click -> pop-up menu"""
+        if not hasattr (self, "popupID"):
+            self.popupID = dict()
+            for key in ('remove', 'enable', 'addPoint',
+                        'delPoint', 'intermediate', 'props', 'id'):
+                self.popupID[key] = wx.NewId()
+        
+        # record coordinates
+        self.x = x
+        self.y = y
+        
+        shape = self.GetShape()
+        popupMenu = wx.Menu()
+        popupMenu.Append(self.popupID['remove'], text=_('Remove'))
+        self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
+        if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
+            if shape.IsEnabled():
+                popupMenu.Append(self.popupID['enable'], text=_('Disable'))
+                self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
+            else:
+                popupMenu.Append(self.popupID['enable'], text=_('Enable'))
+                self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
+        
+        if isinstance(shape, ModelRelation):
+            popupMenu.AppendSeparator()
+            popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
+            self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
+            popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
+            self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
+            if len(shape.GetLineControlPoints()) == 2:
+                popupMenu.Enable(self.popupID['delPoint'], False)
+        
+        if isinstance(shape, ModelData) and '@' not in shape.GetValue():
+            popupMenu.AppendSeparator()
+            popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
+                             kind = wx.ITEM_CHECK)
+            if self.GetShape().IsIntermediate():
+                popupMenu.Check(self.popupID['intermediate'], True)
+            
+            self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
+            
+        if isinstance(shape, ModelData) or \
+                isinstance(shape, ModelAction) or \
+                isinstance(shape, ModelLoop):
+            popupMenu.AppendSeparator()
+            popupMenu.Append(self.popupID['props'], text=_('Properties'))
+            self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
+        
+        self.frame.PopupMenu(popupMenu)
+        popupMenu.Destroy()
+
+    def OnDisable(self, event):
+        """!Disable action"""
+        self._onEnable(False)
+        
+    def OnEnable(self, event):
+        """!Disable action"""
+        self._onEnable(True)
+        
+    def _onEnable(self, enable):
+        shape = self.GetShape()
+        shape.Enable(enable)
+        self.frame.ModelChanged()
+        self.frame.canvas.Refresh()
+        
+    def OnAddPoint(self, event):
+        """!Add control point"""
+        shape = self.GetShape()
+        shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
+        shape.ResetShapes()
+        shape.Select(True)
+        self.frame.ModelChanged()
+        self.frame.canvas.Refresh()
+        
+    def OnRemovePoint(self, event):
+        """!Remove control point"""
+        shape = self.GetShape()
+        shape.DeleteLineControlPoint()
+        shape.Select(False)
+        shape.Select(True)
+        self.frame.ModelChanged()
+        self.frame.canvas.Refresh()
+        
+    def OnIntermediate(self, event):
+        """!Mark data as intermediate"""
+        self.frame.ModelChanged()
+        shape = self.GetShape()
+        shape.SetIntermediate(event.IsChecked())
+        self.frame.canvas.Refresh()
+
+    def OnRemove(self, event):
+        """!Remove shape
+        """
+        self.frame.GetCanvas().RemoveSelected()
+        self.frame.itemPanel.Update()
+        
+class ModelRelation(ogl.LineShape):
+    """!Data - action relation"""
+    def __init__(self, parent, fromShape, toShape, param = ''):
+        self.fromShape = fromShape
+        self.toShape   = toShape
+        self.param     = param
+        self.parent    = parent
+        
+        self._points    = None
+        
+        if self.parent.GetCanvas():        
+            ogl.LineShape.__init__(self)
+    
+    def __del__(self):
+        if self in self.fromShape.rels:
+            self.fromShape.rels.remove(self)
+        if self in self.toShape.rels:
+            self.toShape.rels.remove(self)
+        
+    def GetFrom(self):
+        """!Get id of 'from' shape"""
+        return self.fromShape
+    
+    def GetTo(self):
+        """!Get id of 'to' shape"""
+        return self.toShape
+    
+    def GetData(self):
+        """!Get related ModelData instance
+
+        @return ModelData instance
+        @return None if not found
+        """
+        if isinstance(self.fromShape, ModelData):
+            return self.fromShape
+        elif isinstance(self.toShape, ModelData):
+            return self.toShape
+        
+        return None
+    
+    def GetName(self):
+        """!Get parameter name"""
+        return self.param
+    
+    def ResetShapes(self):
+        """!Reset related objects"""
+        self.fromShape.ResetControlPoints()
+        self.toShape.ResetControlPoints()
+        self.ResetControlPoints()
+        
+    def SetControlPoints(self, points):
+        """!Set control points"""
+        self._points = points
+        
+    def GetControlPoints(self):
+        """!Get list of control points"""
+        return self._points
+    
+    def _setPen(self):
+        """!Set pen"""
+        pen = self.GetPen()
+        pen.SetWidth(1)
+        pen.SetStyle(wx.SOLID)
+        self.SetPen(pen)
+        
+    def OnDraw(self, dc):
+        """!Draw relation"""
+        self._setPen()
+        ogl.LineShape.OnDraw(self, dc)
+    
+    def SetName(self, param):
+        self.param = param
+        
+class ModelListCtrl(wx.ListCtrl,
+                    listmix.ListCtrlAutoWidthMixin,
+                    listmix.TextEditMixin,
+                    listmix.ColumnSorterMixin):
+    def __init__(self, parent, columns, id = wx.ID_ANY,
+                 style = wx.LC_REPORT | wx.BORDER_NONE |
+                 wx.LC_SORT_ASCENDING |wx.LC_HRULES |
+                 wx.LC_VRULES, **kwargs):
+        """!List of model variables"""
+        self.parent = parent
+        self.columns = columns
+        self.shape = None
+        try:
+            self.frame  = parent.parent
+        except AttributeError:
+            self.frame = None
+        
+        wx.ListCtrl.__init__(self, parent, id = id, style = style, **kwargs)
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        listmix.TextEditMixin.__init__(self)
+        listmix.ColumnSorterMixin.__init__(self, 4)
+        
+        i = 0
+        for col in columns:
+            self.InsertColumn(i, col)
+            self.SetColumnWidth(i, wx.LIST_AUTOSIZE_USEHEADER)
+            i += 1
+        
+        self.itemDataMap = {} # requested by sorter
+        self.itemCount   = 0
+        
+        self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit)
+        self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndEdit)
+        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
+        self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightUp) #wxMSW
+        self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)            #wxGTK
+                
+    def OnBeginEdit(self, event):
+        """!Editing of item started"""
+        event.Allow()
+
+    def OnEndEdit(self, event):
+        """!Finish editing of item"""
+        pass
+    
+    def OnColClick(self, event):
+        """!Click on column header (order by)"""
+        event.Skip()
+
+class VariablePanel(wx.Panel):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 **kwargs):
+        """!Manage model variables panel
+        """
+        self.parent = parent
+        
+        wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
+        
+        self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                    label=" %s " % _("List of variables - right-click to delete"))
+        
+        self.list = VariableListCtrl(parent = self,
+                                     columns = [_("Name"), _("Data type"),
+                                                _("Default value"), _("Description")])
+        
+        # add new category
+        self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                   label = " %s " % _("Add new variable"))
+        self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
+        wx.CallAfter(self.name.SetFocus)
+        self.type = wx.Choice(parent = self, id = wx.ID_ANY,
+                              choices = [_("integer"),
+                                         _("float"),
+                                         _("string"),
+                                         _("raster"),
+                                         _("vector"),
+                                         _("mapset"),
+                                         _("file")])
+        self.type.SetSelection(2) # string
+        self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
+        self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
+        
+        # buttons
+        self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
+        self.btnAdd.SetToolTipString(_("Add new variable to the model"))
+        self.btnAdd.Enable(False)
+        
+        # bindings
+        self.name.Bind(wx.EVT_TEXT, self.OnText)
+        self.value.Bind(wx.EVT_TEXT, self.OnText)
+        self.desc.Bind(wx.EVT_TEXT, self.OnText)
+        self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
+        
+        self._layout()
+
+    def _layout(self):
+        """!Layout dialog"""
+        listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
+        listSizer.Add(item = self.list, proportion = 1,
+                      flag = wx.EXPAND)
+        
+        addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        gridSizer.AddGrowableCol(1)
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                           label = "%s:" % _("Name")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (0, 0))
+        gridSizer.Add(item = self.name,
+                      pos = (0, 1),
+                      flag = wx.EXPAND)
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                           label = "%s:" % _("Data type")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (0, 2))
+        gridSizer.Add(item = self.type,
+                      pos = (0, 3))
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                           label = "%s:" % _("Default value")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (1, 0))
+        gridSizer.Add(item = self.value,
+                      pos = (1, 1), span = (1, 3),
+                      flag = wx.EXPAND)
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                           label = "%s:" % _("Description")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (2, 0))
+        gridSizer.Add(item = self.desc,
+                      pos = (2, 1), span = (1, 3),
+                      flag = wx.EXPAND)
+        addSizer.Add(item = gridSizer,
+                     flag = wx.EXPAND)
+        addSizer.Add(item = self.btnAdd, proportion = 0,
+                     flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = listSizer, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        mainSizer.Add(item = addSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALIGN_CENTER |
+                      wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+        
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+        
+    def OnText(self, event):
+        """!Text entered"""
+        if self.name.GetValue():
+            self.btnAdd.Enable()
+        else:
+            self.btnAdd.Enable(False)
+    
+    def OnAdd(self, event):
+        """!Add new variable to the list"""
+        msg = self.list.Append(self.name.GetValue(),
+                               self.type.GetStringSelection(),
+                               self.value.GetValue(),
+                               self.desc.GetValue())
+        self.name.SetValue('')
+        self.name.SetFocus()
+        
+        if msg:
+            GError(parent = self,
+                   message = msg)
+        else:
+            self.type.SetSelection(2) # string
+            self.value.SetValue('')
+            self.desc.SetValue('')
+            self.UpdateModelVariables()
+        
+    def UpdateModelVariables(self):
+        """!Update model variables"""
+        variables = dict()
+        for values in self.list.GetData().itervalues():
+            name = values[0]
+            variables[name] = { 'type' : str(values[1]) }
+            if values[2]:
+                variables[name]['value'] = values[2]
+            if values[3]:
+                variables[name]['description'] = values[3]
+        
+        self.parent.GetModel().SetVariables(variables)
+        self.parent.ModelChanged()
+
+    def Update(self):
+        """!Reload list of variables"""
+        self.list.OnReload(None)
+        
+    def Reset(self):
+        """!Remove all variables"""
+        self.list.DeleteAllItems()
+        self.parent.GetModel().SetVariables([])
+        
+class VariableListCtrl(ModelListCtrl):
+    def __init__(self, parent, columns, **kwargs):
+        """!List of model variables"""
+        ModelListCtrl.__init__(self, parent, columns, **kwargs)
+
+        self.SetColumnWidth(2, 200) # default value
+
+    def GetListCtrl(self):
+        """!Used by ColumnSorterMixin"""
+        return self
+    
+    def GetData(self):
+        """!Get list data"""
+        return self.itemDataMap
+    
+    def Populate(self, data):
+        """!Populate the list"""
+        self.itemDataMap = dict()
+        i = 0
+        for name, values in data.iteritems():
+            self.itemDataMap[i] = [name, values['type'],
+                                   values.get('value', ''),
+                                   values.get('description', '')]
+            i += 1
+        
+        self.itemCount = len(self.itemDataMap.keys())
+        self.DeleteAllItems()
+        i = 0
+        for name, vtype, value, desc in self.itemDataMap.itervalues():
+            index = self.InsertStringItem(sys.maxint, name)
+            self.SetStringItem(index, 0, name)
+            self.SetStringItem(index, 1, vtype)
+            self.SetStringItem(index, 2, value)
+            self.SetStringItem(index, 3, desc)
+            self.SetItemData(index, i)
+            i += 1
+        
+    def Append(self, name, vtype, value, desc):
+        """!Append new item to the list
+
+        @return None on success
+        @return error string
+        """
+        for iname, ivtype, ivalue, idesc in self.itemDataMap.itervalues():
+            if iname == name:
+                return _("Variable <%s> already exists in the model. "
+                         "Adding variable failed.") % name
+        
+        index = self.InsertStringItem(sys.maxint, name)
+        self.SetStringItem(index, 0, name)
+        self.SetStringItem(index, 1, vtype)
+        self.SetStringItem(index, 2, value)
+        self.SetStringItem(index, 3, desc)
+        self.SetItemData(index, self.itemCount)
+        
+        self.itemDataMap[self.itemCount] = [name, vtype, value, desc]
+        self.itemCount += 1
+        
+        return None
+
+    def OnRemove(self, event):
+        """!Remove selected variable(s) from the model"""
+        item = self.GetFirstSelected()
+        while item != -1:
+            self.DeleteItem(item)
+            del self.itemDataMap[item]
+            item = self.GetFirstSelected()
+        self.parent.UpdateModelVariables()
+        
+        event.Skip()
+        
+    def OnRemoveAll(self, event):
+        """!Remove all variable(s) from the model"""
+        dlg = wx.MessageBox(parent=self,
+                            message=_("Do you want to delete all variables from "
+                                      "the model?"),
+                            caption=_("Delete variables"),
+                            style=wx.YES_NO | wx.CENTRE)
+        if dlg != wx.YES:
+            return
+        
+        self.DeleteAllItems()
+        self.itemDataMap = dict()
+        
+        self.parent.UpdateModelVariables()
+        
+    def OnEndEdit(self, event):
+        """!Finish editing of item"""
+        itemIndex = event.GetIndex()
+        columnIndex = event.GetColumn()
+        nameOld = self.GetItem(itemIndex, 0).GetText()
+
+        if columnIndex == 0: # TODO
+            event.Veto()
+        
+        self.itemDataMap[itemIndex][columnIndex] = event.GetText()
+        
+        self.parent.UpdateModelVariables()
+
+    def OnReload(self, event):
+        """!Reload list of variables"""
+        self.Populate(self.parent.parent.GetModel().GetVariables())
+
+    def OnRightUp(self, event):
+        """!Mouse right button up"""
+        if not hasattr(self, "popupID1"):
+            self.popupID1 = wx.NewId()
+            self.popupID2 = wx.NewId()
+            self.popupID3 = wx.NewId()
+            self.Bind(wx.EVT_MENU, self.OnRemove,    id = self.popupID1)
+            self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
+            self.Bind(wx.EVT_MENU, self.OnReload,    id = self.popupID3)
+        
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupID1, _("Delete selected"))
+        menu.Append(self.popupID2, _("Delete all"))
+        if self.GetFirstSelected() == -1:
+            menu.Enable(self.popupID1, False)
+            menu.Enable(self.popupID2, False)
+        
+        menu.AppendSeparator()
+        menu.Append(self.popupID3, _("Reload"))
+        
+        self.PopupMenu(menu)
+        menu.Destroy()
+        
+class ModelItem(ModelObject):
+    def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
+        """!Abstract class for loops and conditions"""
+        ModelObject.__init__(self, id)
+        self.parent  = parent
+        self.text    = text
+        self.items   = items  # list of items in the loop
+        
+    def GetText(self):
+        """!Get loop text"""
+        return self.text
+
+    def GetItems(self):
+        """!Get items (id)"""
+        return self.items
+
+    def SetId(self, id):
+        """!Set loop id"""
+        self.id = id
+
+    def SetText(self, cond):
+        """!Set loop text (condition)"""
+        self.text = cond
+        self.ClearText()
+        self.AddText('(' + str(self.id) + ') ' + self.text)
+
+    def GetLog(self):
+        """!Get log info"""
+        if self.text:
+            return _("Condition: ") + self.text
+        else:
+            return _("Condition: not defined")
+
+    def AddRelation(self, rel):
+        """!Record relation"""
+        self.rels.append(rel)
+        
+    def Clear(self):
+        """!Clear object, remove rels"""
+        self.rels = list()
+   
+class ModelLoop(ModelItem, ogl.RectangleShape):
+    def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
+        """!Defines a loop"""
+        ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
+        
+        if not width:
+            width = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'width'))
+        if not height:
+            height = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'height'))
+        
+        if self.parent.GetCanvas():
+            ogl.RectangleShape.__init__(self, width, height)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            self.SetCornerRadius(100)
+            if text:
+                self.AddText('(' + str(self.id) + ') ' + text)
+            else:
+                self.AddText('(' + str(self.id) + ')')
+        
+        self._setBrush()
+        
+    def _setBrush(self):
+        """!Set brush"""
+        if not self.isEnabled:
+            color = UserSettings.Get(group='modeler', key='disabled',
+                                     subkey='color')
+        else:
+            color = UserSettings.Get(group='modeler', key='loop',
+                                     subkey=('color', 'valid'))
+        
+        wxColor = wx.Color(color[0], color[1], color[2])
+        self.SetBrush(wx.Brush(wxColor))
+
+    def Enable(self, enabled = True):
+        """!Enable/disable action"""
+        for item in self.items:
+            if not isinstance(item, ModelAction):
+                continue
+            item.Enable(enabled)
+        
+        ModelObject.Enable(self, enabled)
+        
+    def Update(self):
+        self._setBrush()
+        
+    def GetName(self):
+        """!Get name"""
+        return _("loop")
+    
+    def SetItems(self, items):
+        """!Set items (id)"""
+        self.items = items
+
+class ItemPanel(wx.Panel):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 **kwargs):
+        """!Manage model items
+        """
+        self.parent = parent
+        
+        wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
+        
+        self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                    label=" %s " % _("List of items - right-click to delete"))
+        
+        self.list = ItemListCtrl(parent = self,
+                                 columns = [_("ID"), _("Name"), _("In block"),
+                                            _("Command / Condition")])
+        
+        self._layout()
+
+    def _layout(self):
+        """!Layout dialog"""
+        listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
+        listSizer.Add(item = self.list, proportion = 1,
+                      flag = wx.EXPAND)
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = listSizer, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+        
+    def Update(self):
+        """!Reload list of variables"""
+        self.list.OnReload(None)
+        
+class ItemListCtrl(ModelListCtrl):
+    def __init__(self, parent, columns, disablePopup = False, **kwargs):
+        """!List of model actions"""
+        self.disablePopup = disablePopup
+                
+        ModelListCtrl.__init__(self, parent, columns, **kwargs)
+        self.SetColumnWidth(1, 100)
+        self.SetColumnWidth(2, 65)
+        
+    def GetListCtrl(self):
+        """!Used by ColumnSorterMixin"""
+        return self
+    
+    def GetData(self):
+        """!Get list data"""
+        return self.itemDataMap
+    
+    def Populate(self, data):
+        """!Populate the list"""
+        self.itemDataMap = dict()
+        
+        if self.shape:
+            if isinstance(self.shape, ModelCondition):
+                if self.GetName() == 'ElseBlockList':
+                    shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['else'])
+                else:
+                    shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['if'])
+            else:
+                shapeItems = map(lambda x: x.GetId(), self.shape.GetItems())
+        else:
+            shapeItems = list()
+        
+        i = 0
+        if len(self.columns) == 3: # ItemCheckList
+            checked = list()
+        for action in data:
+            if isinstance(action, ModelData) or \
+                    action == self.shape:
+                continue
+            
+            if len(self.columns) == 3:
+                self.itemDataMap[i] = [str(action.GetId()),
+                                       action.GetName(),
+                                       action.GetLog()]
+                aId = action.GetBlockId()
+                if action.GetId() in shapeItems:
+                    checked.append(aId)
+                else:
+                    checked.append(None)
+            else:
+                bId = action.GetBlockId()
+                if not bId:
+                    bId = ''
+                self.itemDataMap[i] = [str(action.GetId()),
+                                       action.GetName(),
+                                       ','.join(map(str, bId)),
+                                       action.GetLog()]
+            
+            i += 1
+        
+        self.itemCount = len(self.itemDataMap.keys())
+        self.DeleteAllItems()
+        i = 0
+        if len(self.columns) == 3:
+            for aid, name, desc in self.itemDataMap.itervalues():
+                index = self.InsertStringItem(sys.maxint, aid)
+                self.SetStringItem(index, 0, aid)
+                self.SetStringItem(index, 1, name)
+                self.SetStringItem(index, 2, desc)
+                self.SetItemData(index, i)
+                if checked[i]:
+                    self.CheckItem(index, True)
+                i += 1
+        else:
+            for aid, name, inloop, desc in self.itemDataMap.itervalues():
+                index = self.InsertStringItem(sys.maxint, aid)
+                self.SetStringItem(index, 0, aid)
+                self.SetStringItem(index, 1, name)
+                self.SetStringItem(index, 2, inloop)
+                self.SetStringItem(index, 3, desc)
+                self.SetItemData(index, i)
+                i += 1
+                
+    def OnRemove(self, event):
+        """!Remove selected action(s) from the model"""
+        model = self.frame.GetModel()
+        canvas = self.frame.GetCanvas()
+        
+        item = self.GetFirstSelected()
+        while item != -1:
+            self.DeleteItem(item)
+            del self.itemDataMap[item]
+            
+            aId = self.GetItem(item, 0).GetText()
+            action = model.GetItem(int(aId))
+            if not action:
+                item = self.GetFirstSelected()
+                continue
+            
+            model.RemoveItem(action)
+            canvas.GetDiagram().RemoveShape(action)
+            self.frame.ModelChanged()
+            
+            item = self.GetFirstSelected()
+        
+        canvas.Refresh()
+        
+        event.Skip()
+    
+    def OnRemoveAll(self, event):
+        """!Remove all variable(s) from the model"""
+        deleteDialog = wx.MessageBox(parent=self,
+                                     message=_("Selected data records (%d) will permanently deleted "
+                                               "from table. Do you want to delete them?") % \
+                                         (len(self.listOfSQLStatements)),
+                                     caption=_("Delete records"),
+                                     style=wx.YES_NO | wx.CENTRE)
+        if deleteDialog != wx.YES:
+            return False
+        
+        self.DeleteAllItems()
+        self.itemDataMap = dict()
+
+        self.parent.UpdateModelVariables()
+
+    def OnEndEdit(self, event):
+        """!Finish editing of item"""
+        itemIndex = event.GetIndex()
+        columnIndex = event.GetColumn()
+        
+        self.itemDataMap[itemIndex][columnIndex] = event.GetText()
+        
+        aId = int(self.GetItem(itemIndex, 0).GetText())
+        action = self.frame.GetModel().GetItem(aId)
+        if not action:
+            event.Veto()
+        if columnIndex == 0:
+            action.SetId(int(event.GetText()))
+        
+        self.frame.ModelChanged()
+
+    def OnReload(self, event = None):
+        """!Reload list of actions"""
+        self.Populate(self.frame.GetModel().GetItems())
+
+    def OnRightUp(self, event):
+        """!Mouse right button up"""
+        if self.disablePopup:
+            return
+        
+        if not hasattr(self, "popupID1"):
+            self.popupID1 = wx.NewId()
+            self.popupID2 = wx.NewId()
+            self.popupID3 = wx.NewId()
+            self.popupID4 = wx.NewId()
+            self.Bind(wx.EVT_MENU, self.OnRemove,    id = self.popupID1)
+            self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
+            self.Bind(wx.EVT_MENU, self.OnReload,    id = self.popupID3)
+            self.Bind(wx.EVT_MENU, self.OnNormalize, id = self.popupID4)
+
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupID1, _("Delete selected"))
+        menu.Append(self.popupID2, _("Delete all"))
+        if self.GetFirstSelected() == -1:
+            menu.Enable(self.popupID1, False)
+            menu.Enable(self.popupID2, False)
+        
+        menu.AppendSeparator()
+        menu.Append(self.popupID4, _("Normalize"))
+        menu.Append(self.popupID3, _("Reload"))
+        
+        self.PopupMenu(menu)
+        menu.Destroy()
+    
+    def OnNormalize(self, event):
+        """!Update id of actions"""
+        model = self.frame.GetModel()
+        
+        aId = 1
+        for item in model.GetItems():
+            item.SetId(aId)
+            aId += 1
+        
+        self.OnReload(None)
+        self.frame.GetCanvas().Refresh()
+        self.frame.ModelChanged()
+
+class ItemCheckListCtrl(ItemListCtrl, listmix.CheckListCtrlMixin):
+    def __init__(self, parent, shape, columns, window = None, **kwargs):
+        self.parent = parent
+        self.window = window
+        
+        ItemListCtrl.__init__(self, parent, columns, disablePopup = True, **kwargs)
+        listmix.CheckListCtrlMixin.__init__(self)
+        self.SetColumnWidth(0, 50)
+        
+        self.shape  = shape
+        
+    def OnBeginEdit(self, event):
+        """!Disable editing"""
+        event.Veto()
+        
+    def OnCheckItem(self, index, flag):
+        """!Item checked/unchecked"""
+        name = self.GetName()
+        if name == 'IfBlockList' and self.window:
+            self.window.OnCheckItemIf(index, flag)
+        elif name == 'ElseBlockList' and self.window:
+            self.window.OnCheckItemElse(index, flag)
+        
+    def GetItems(self):
+        """!Get list of selected actions"""
+        ids = { 'checked'   : list(),
+                'unchecked' : list() }
+        for i in range(self.GetItemCount()):
+            iId = int(self.GetItem(i, 0).GetText())
+            if self.IsChecked(i):
+                ids['checked'].append(iId)
+            else:
+                ids['unchecked'].append(iId)
+            
+        return ids
+
+    def CheckItemById(self, aId, flag):
+        """!Check/uncheck given item by id"""
+        for i in range(self.GetItemCount()):
+            iId = int(self.GetItem(i, 0).GetText())
+            if iId == aId:
+                self.CheckItem(i, flag)
+                break
+        
+class ModelCondition(ModelItem, ogl.PolygonShape):
+    def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '',
+                 items = { 'if' : [], 'else' : [] }):
+        """!Defines a if-else condition"""
+        ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
+        
+        if not width:
+            self.width = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'width'))
+        else:
+            self.width = width
+        if not height:
+            self.height = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'height'))
+        else:
+            self.height = height
+        
+        if self.parent.GetCanvas():
+            ogl.PolygonShape.__init__(self)
+            
+            points = [(0, - self.height / 2),
+                      (self.width / 2, 0),
+                      (0, self.height / 2),
+                      (- self.width / 2, 0)]
+            self.Create(points)
+            
+            self.SetCanvas(self.parent)
+            self.SetX(x)
+            self.SetY(y)
+            self.SetPen(wx.BLACK_PEN)
+            if text:
+                self.AddText('(' + str(self.id) + ') ' + text)
+            else:
+                self.AddText('(' + str(self.id) + ')')
+
+    def GetName(self):
+        """!Get name"""
+        return _("if-else")
+
+    def GetWidth(self):
+        """!Get object width"""
+        return self.width
+
+    def GetHeight(self):
+        """!Get object height"""
+        return self.height
+
+    def SetItems(self, items, branch = 'if'):
+        """!Set items (id)
+
+        @param items list of items
+        @param branch 'if' / 'else'
+        """
+        if branch in ['if', 'else']:
+            self.items[branch] = items
+        
+def main():
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+    
+    app = wx.PySimpleApp()
+    wx.InitAllImageHandlers()
+    frame = ModelFrame(parent = None)
+    if len(sys.argv) > 1:
+        frame.LoadModelFile(sys.argv[1])
+    frame.Show()
+    
+    app.MainLoop()
+    
+if __name__ == "__main__":
+    main()

Added: grass/trunk/gui/wxpython/gmodeler/model.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/model.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gmodeler/model.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,731 @@
+"""!
+ at package gmodeler.model
+
+ at brief wxGUI Graphical Modeler (base classes)
+
+Classes:
+ - Model
+
+(C) 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import getpass
+import copy
+import re
+try:
+    import xml.etree.ElementTree as etree
+except ImportError:
+    import elementtree.ElementTree as etree # Python <= 2.4
+
+import wx
+
+from core.globalvar import ETCWXDIR
+from core.gcmd      import GMessage, GException, GError, RunCommand
+
+from grass.script import core as grass
+from grass.script import task as gtask
+
+class Model(object):
+    """!Class representing the model"""
+    def __init__(self, canvas = None):
+        self.items      = list() # list of actions/loops/...
+        
+        # model properties
+        self.properties = { 'name'        : _("model"),
+                            'description' : _("Script generated by wxGUI Graphical Modeler."),
+                            'author'      : getpass.getuser() }
+        # model variables
+        self.variables = dict()
+        self.variablesParams = dict()
+        
+        self.canvas  = canvas
+        
+    def GetCanvas(self):
+        """!Get canvas or None"""
+        return self.canvas
+    
+    def GetItems(self, objType = None):
+        """!Get list of model items
+
+        @param objType Object type to filter model objects
+        """
+        if not objType:
+            return self.items
+        
+        result = list()
+        for item in self.items:
+            if isinstance(item, objType):
+                result.append(item)
+        
+        return result
+
+    def GetItem(self, aId):
+        """!Get item of given id
+
+        @param aId item id
+        
+        @return Model* instance
+        @return None if no item found
+        """
+        ilist = self.GetItems()
+        for item in ilist:
+            if item.GetId() == aId:
+                return item
+        
+        return None
+
+    def GetNumItems(self, actionOnly = False):
+        """!Get number of items"""
+        if actionOnly:
+            return len(self.GetItems(objType = ModelAction))
+        
+        return len(self.GetItems())
+
+    def GetNextId(self):
+        """!Get next id (data ignored)
+
+        @return next id to be used (default: 1)
+        """
+        if len(self.items) < 1:
+            return 1
+        
+        currId = self.items[-1].GetId()
+        if currId > 0:
+            return currId + 1
+        
+        return 1
+
+    def GetProperties(self):
+        """!Get model properties"""
+        return self.properties
+
+    def GetVariables(self, params = False):
+        """!Get model variables"""
+        if params:
+            return self.variablesParams
+        
+        return self.variables
+    
+    def SetVariables(self, data):
+        """!Set model variables"""
+        self.variables = data
+    
+    def Reset(self):
+        """!Reset model"""
+        self.items = list()
+        
+    def RemoveItem(self, item):
+        """!Remove item from model
+        
+        @return list of related items to remove/update
+        """
+        relList = list()
+        upList = list()
+        
+        if not isinstance(item, ModelData):
+            self.items.remove(item)
+        
+        if isinstance(item, ModelAction):
+            for rel in item.GetRelations():
+                relList.append(rel)
+                data = rel.GetData()
+                if len(data.GetRelations()) < 2:
+                    relList.append(data)
+                else:
+                    upList.append(data)
+            
+        elif isinstance(item, ModelData):
+            for rel in item.GetRelations():
+                relList.append(rel)
+                if rel.GetFrom() == self:
+                    relList.append(rel.GetTo())
+                else:
+                    relList.append(rel.GetFrom())
+        
+        elif isinstance(item, ModelLoop):
+            for rel in item.GetRelations():
+                relList.append(rel)
+            for action in self.GetItems():
+                action.UnSetBlock(item)
+        
+        return relList, upList
+    
+    def FindAction(self, aId):
+        """!Find action by id"""
+        alist = self.GetItems(objType = ModelAction)
+        for action in alist:
+            if action.GetId() == aId:
+                return action
+        
+        return None
+
+    def GetData(self):
+        """!Get list of data items"""
+        result = list()
+        dataItems = self.GetItems(objType = ModelData)
+        
+        for action in self.GetItems(objType = ModelAction):
+            for rel in action.GetRelations():
+                dataItem = rel.GetData()
+                if dataItem not in result:
+                    result.append(dataItem)
+                if dataItem in dataItems:
+                    dataItems.remove(dataItem)
+        
+        # standalone data
+        if dataItems:
+            result += dataItems
+        
+        return result
+
+    def FindData(self, value, prompt):
+        """!Find data item in the model
+
+        @param value value
+        @param prompt prompt
+
+        @return ModelData instance
+        @return None if not found
+        """
+        for data in self.GetData():
+            if data.GetValue() == value and \
+                    data.GetPrompt() == prompt:
+                return data
+        
+        return None
+                
+    def LoadModel(self, filename):
+        """!Load model definition stored in GRASS Model XML file (gxm)
+        
+        @todo Validate against DTD
+        
+        Raise exception on error.
+        """
+        dtdFilename = os.path.join(ETCWXDIR, "xml", "grass-gxm.dtd")
+        
+        # parse workspace file
+        try:
+            gxmXml = ProcessModelFile(etree.parse(filename))
+        except StandardError, e:
+            raise GException(e)
+        
+        if self.canvas:
+            win = self.canvas.parent
+            if gxmXml.pos:
+                win.SetPosition(gxmXml.pos)
+            if gxmXml.size:
+                win.SetSize(gxmXml.size)
+        
+        # load properties
+        self.properties = gxmXml.properties
+        self.variables  = gxmXml.variables
+        
+        # load model.GetActions()
+        for action in gxmXml.actions:
+            actionItem = ModelAction(parent = self, 
+                                     x = action['pos'][0],
+                                     y = action['pos'][1],
+                                     width = action['size'][0],
+                                     height = action['size'][1],
+                                     task = action['task'],
+                                     id = action['id'])
+            
+            if action['disabled']:
+                actionItem.Enable(False)
+            
+            self.AddItem(actionItem)
+            
+            actionItem.SetValid(actionItem.GetTask().get_options())
+            actionItem.GetLog() # substitute variables (-> valid/invalid)
+        
+        # load data & relations
+        for data in gxmXml.data:
+            dataItem = ModelData(parent = self, 
+                                 x = data['pos'][0],
+                                 y = data['pos'][1],
+                                 width = data['size'][0],
+                                 height = data['size'][1],
+                                 prompt = data['prompt'],
+                                 value = data['value'])
+            dataItem.SetIntermediate(data['intermediate'])
+            
+            for rel in data['rels']:
+                actionItem = self.FindAction(rel['id'])
+                if rel['dir'] == 'from':
+                    relation = ModelRelation(parent = self, fromShape = dataItem,
+                                             toShape = actionItem, param = rel['name'])
+                else:
+                    relation = ModelRelation(parent = self, fromShape = actionItem,
+                                             toShape = dataItem, param = rel['name'])
+                relation.SetControlPoints(rel['points'])
+                actionItem.AddRelation(relation)
+                dataItem.AddRelation(relation)
+
+            if self.canvas:
+                dataItem.Update()
+           
+        # load loops
+        for loop in gxmXml.loops:
+            loopItem = ModelLoop(parent = self, 
+                                 x = loop['pos'][0],
+                                 y = loop['pos'][1],
+                                 width = loop['size'][0],
+                                 height = loop['size'][1],
+                                 text = loop['text'],
+                                 id = loop['id'])
+            self.AddItem(loopItem)
+
+        # load conditions
+        for condition in gxmXml.conditions:
+            conditionItem = ModelCondition(parent = self, 
+                                           x = condition['pos'][0],
+                                           y = condition['pos'][1],
+                                           width = condition['size'][0],
+                                           height = condition['size'][1],
+                                           text = condition['text'],
+                                           id = condition['id'])
+            self.AddItem(conditionItem)
+
+        # define loops & if/else items
+        for loop in gxmXml.loops:
+            alist = list()
+            for aId in loop['items']:
+                action = self.GetItem(aId)
+                alist.append(action)
+            
+            loopItem = self.GetItem(loop['id'])
+            loopItem.SetItems(alist)
+            
+            for action in loopItem.GetItems():
+                action.SetBlock(loopItem)
+        
+        for condition in gxmXml.conditions:
+            conditionItem = self.GetItem(condition['id'])
+            for b in condition['items'].keys():
+                alist = list()
+                for aId in condition['items'][b]:
+                    action = self.GetItem(aId)
+                    alist.append(action)
+                conditionItem.SetItems(alist, branch = b)
+            
+            items = conditionItem.GetItems()
+            for b in items.keys():
+                for action in items[b]:
+                    action.SetBlock(conditionItem)
+        
+    def AddItem(self, newItem):
+        """!Add item to the list"""
+        iId = newItem.GetId()
+        
+        i  = 0
+        for item in self.items:
+            if item.GetId() > iId:
+                self.items.insert(i, newItem)
+                return
+            i += 1
+        
+        self.items.append(newItem)
+        
+    def IsValid(self):
+        """Return True if model is valid"""
+        if self.Validate():
+            return False
+        
+        return True
+    
+    def Validate(self):
+        """!Validate model, return None if model is valid otherwise
+        error string"""
+        errList = list()
+
+        variables = self.GetVariables().keys()
+        pattern = re.compile(r'(.*)(%.+\s?)(.*)')
+        for action in self.GetItems(objType = ModelAction):
+            cmd = action.GetLog(string = False)
+            
+            task = menuform.GUI(show = None).ParseCommand(cmd = cmd)
+            errList += map(lambda x: cmd[0] + ': ' + x, task.get_cmd_error())
+            
+            # check also variables
+            for opt in cmd[1:]:
+                if '=' not in opt:
+                    continue
+                key, value = opt.split('=', 1)
+                sval = pattern.search(value)
+                if sval:
+                    var = sval.group(2).strip()[1:] # ignore '%'
+                    if var not in variables:
+                        report = True
+                        for item in filter(lambda x: isinstance(x, ModelLoop), action.GetBlock()):
+                            if var in item.GetText():
+                                report = False
+                                break
+                        if report:
+                            errList.append(_("%s: undefined variable '%s'") % (cmd[0], var))
+            ### TODO: check variables in file only optionally
+            ### errList += self._substituteFile(action, checkOnly = True)
+        
+        return errList
+
+    def _substituteFile(self, item, params = None, checkOnly = False):
+        """!Subsitute variables in command file inputs
+
+        @param checkOnly tuble - True to check variable, don't touch files
+        
+        @return list of undefined variables
+        """
+        errList = list()
+        
+        self.fileInput = dict()
+        
+        # collect ascii inputs
+        for p in item.GetParams()['params']:
+            if p.get('element', '') == 'file' and \
+                    p.get('prompt', '') == 'input' and \
+                    p.get('age', '') == 'old':
+                filename = p.get('value', p.get('default', ''))
+                if filename and \
+                        mimetypes.guess_type(filename)[0] == 'text/plain':
+                    self.fileInput[filename] = None
+        
+        for finput in self.fileInput:
+            # read lines
+            fd = open(finput, "r")
+            try:
+                data = self.fileInput[finput] = fd.read()
+            finally:
+                fd.close()
+            
+            # substitute variables
+            write = False
+            variables = self.GetVariables()
+            for variable in variables:
+                pattern = re.compile('%' + variable)
+                value = ''
+                if params and 'variables' in params:
+                    for p in params['variables']['params']:
+                        if variable == p.get('name', ''):
+                            if p.get('type', 'string') == 'string':
+                                value = p.get('value', '')
+                            else:
+                                value = str(p.get('value', ''))
+                            break
+                
+                if not value:
+                    value = variables[variable].get('value', '')
+                
+                data = pattern.sub(value, data)
+                if not checkOnly:
+                    write = True
+            
+            pattern = re.compile(r'(.*)(%.+\s?)(.*)')
+            sval = pattern.search(data)
+            if sval:
+                var = sval.group(2).strip()[1:] # ignore '%'
+                cmd = item.GetLog(string = False)[0]
+                errList.append(_("%s: undefined variable '%s'") % (cmd, var))
+            
+            if not checkOnly:
+                if write:
+                    fd = open(finput, "w")
+                    try:
+                        fd.write(data)
+                    finally:
+                        fd.close()
+                else:
+                    self.fileInput[finput] = None
+        
+        return errList
+    
+    def OnPrepare(self, item, params):
+        self._substituteFile(item, params, checkOnly = False)
+
+    def RunAction(self, item, params, log, onDone, onPrepare = None, statusbar = None):
+        """!Run given action
+
+        @param item action item
+        @param params parameters dict
+        @param log logging window
+        @param onDone on-done method
+        @param onPrepare on-prepare method
+        @param statusbar wx.StatusBar instance or None
+        """
+        name = item.GetName()
+        if name in params:
+            paramsOrig = item.GetParams(dcopy = True)
+            item.MergeParams(params[name])
+        
+        if statusbar:
+            statusbar.SetStatusText(_('Running model...'), 0)
+            
+        data = { 'item' : item,
+                 'params' : copy.deepcopy(params) }
+        log.RunCmd(command = item.GetLog(string = False, substitute = params),
+                   onDone = onDone, onPrepare = self.OnPrepare, userData = data)
+        
+        if name in params:
+            item.SetParams(paramsOrig)
+
+    def Run(self, log, onDone, parent = None):
+        """!Run model
+
+        @param log logging window (see goutput.GMConsole)
+        @param onDone on-done method
+        @param parent window for messages or None
+        """
+        if self.GetNumItems() < 1:
+            GMessage(parent = parent,
+                     message = _('Model is empty. Nothing to run.'))
+            return
+        
+        statusbar = None
+        if isinstance(parent, wx.Frame):
+            statusbar = parent.GetStatusBar()
+        
+        # validation
+        if statusbar:
+            statusbar.SetStatusText(_('Validating model...'), 0)
+        errList = self.Validate()
+        if statusbar:
+            statusbar.SetStatusText('', 0)
+        if errList:
+            dlg = wx.MessageDialog(parent = parent,
+                                   message = _('Model is not valid. Do you want to '
+                                               'run the model anyway?\n\n%s') % '\n'.join(errList),
+                                   caption = _("Run model?"),
+                                   style = wx.YES_NO | wx.NO_DEFAULT |
+                                   wx.ICON_QUESTION | wx.CENTRE)
+            ret = dlg.ShowModal()
+            if ret != wx.ID_YES:
+                return
+        
+        # parametrization
+        params = self.Parameterize()
+        if params:
+            dlg = ModelParamDialog(parent = parent,
+                                   params = params)
+            dlg.CenterOnParent()
+            
+            ret = dlg.ShowModal()
+            if ret != wx.ID_OK:
+                dlg.Destroy()
+                return
+            
+            err = dlg.GetErrors()
+            if err:
+                GError(parent = parent, message = unicode('\n'.join(err)))
+                return
+        
+            err = list()
+            for key, item in params.iteritems():
+                for p in item['params']:
+                    if p.get('value', '') == '':
+                        err.append((key, p.get('name', ''), p.get('description', '')))
+            if err:
+                GError(parent = parent,
+                       message = _("Variables below not defined:") + \
+                           "\n\n" + unicode('\n'.join(map(lambda x: "%s: %s (%s)" % (x[0], x[1], x[2]), err))))
+                return
+        
+        log.cmdThread.SetId(-1)
+        for item in self.GetItems():
+            if not item.IsEnabled():
+                continue
+            if isinstance(item, ModelAction):
+                if item.GetBlockId():
+                    continue
+                self.RunAction(item, params, log, onDone)
+            elif isinstance(item, ModelLoop):
+                cond = item.GetText()
+                # substitute variables in condition
+                variables = self.GetVariables()
+                for variable in variables:
+                    pattern = re.compile('%' + variable)
+                    if pattern.search(cond):
+                        value = ''
+                        if params and 'variables' in params:
+                            for p in params['variables']['params']:
+                                if variable == p.get('name', ''):
+                                    value = p.get('value', '')
+                                    break
+                        
+                        if not value:
+                            value = variables[variable].get('value', '')
+                        
+                        if not value:
+                            continue
+                        
+                        vtype = variables[variable].get('type', 'string')
+                        if vtype == 'string':
+                            value = '"' + value + '"'
+                        cond = pattern.sub(value, cond)
+                
+                # split condition
+                condVar, condText = map(lambda x: x.strip(), re.split('\s*in\s*', cond))
+                pattern = re.compile('%' + condVar)
+                ### for vars()[condVar] in eval(condText): ?
+                if condText[0] == '`' and condText[-1] == '`':
+                    # run command
+                    cmd, dcmd = utils.CmdToTuple(condText[1:-1].split(' '))
+                    ret = RunCommand(cmd,
+                                     read = True,
+                                     **dcmd)
+                    if ret:
+                        vlist = ret.splitlines()
+                else:
+                    vlist = eval(condText)
+                
+                if 'variables' not in params:
+                    params['variables'] = { 'params' : [] }
+                varDict = { 'name' : condVar, 'value' : '' }
+                params['variables']['params'].append(varDict)
+                                
+                for var in vlist:
+                    for action in item.GetItems():
+                        if not isinstance(action, ModelAction) or \
+                                not action.IsEnabled():
+                            continue
+                        
+                        varDict['value'] = var
+                                                
+                        self.RunAction(item = action, params = params,
+                                       log = log, onDone = onDone)
+                params['variables']['params'].remove(varDict)
+                
+        # discard values
+        if params:
+            for item in params.itervalues():
+                for p in item['params']:
+                    p['value'] = ''
+        
+        if params:
+            dlg.Destroy()
+        
+    def DeleteIntermediateData(self, log):
+        """!Detele intermediate data"""
+        rast, vect, rast3d, msg = self.GetIntermediateData()
+        
+        if rast:
+            log.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
+        if rast3d:
+            log.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
+        if vect:
+            log.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
+        
+    def GetIntermediateData(self):
+        """!Get info about intermediate data"""
+        rast = list()
+        rast3d = list()
+        vect = list()
+        for data in self.GetData():
+            if not data.IsIntermediate():
+                continue
+            name = data.GetValue()
+            prompt = data.GetPrompt()
+            if prompt == 'raster':
+                rast.append(name)
+            elif prompt == 'vector':
+                vect.append(name)
+            elif prompt == 'rast3d':
+                rast3d.append(name)
+        
+        msg = ''
+        if rast:
+            msg += '\n\n%s: ' % _('Raster maps')
+            msg += ', '.join(rast)
+        if rast3d:
+            msg += '\n\n%s: ' % _('3D raster maps')
+            msg += ', '.join(rast3d)
+        if vect:
+            msg += '\n\n%s: ' % _('Vector maps')
+            msg += ', '.join(vect)
+        
+        return rast, vect, rast3d, msg
+
+    def Update(self):
+        """!Update model"""
+        for item in self.items:
+            item.Update()
+        
+    def IsParameterized(self):
+        """!Return True if model is parameterized"""
+        if self.Parameterize():
+            return True
+        
+        return False
+    
+    def Parameterize(self):
+        """!Return parameterized options"""
+        result = dict()
+        idx = 0
+        if self.variables:
+            params = list()
+            result["variables"] = { 'flags'  : list(),
+                                    'params' : params,
+                                    'idx'    : idx }
+            for name, values in self.variables.iteritems():
+                gtype = values.get('type', 'string')
+                if gtype in ('raster', 'vector', 'mapset', 'file'):
+                    gisprompt = True
+                    prompt = gtype
+                    if gtype == 'raster':
+                        element = 'cell'
+                    else:
+                        element = gtype
+                    ptype = 'string'
+                else:
+                    gisprompt = False
+                    prompt = None
+                    element = None
+                    ptype = gtype
+                params.append({ 'gisprompt' : gisprompt,
+                                'multiple'  : False,
+                                'description' : values.get('description', ''),
+                                'guidependency' : '',
+                                'default' : '',
+                                'age' : None,
+                                'required' : True,
+                                'value' : values.get('value', ''),
+                                'label' : '',
+                                'guisection' : '',
+                                'key_desc' : '',
+                                'values' : list(),
+                                'parameterized' : False,
+                                'values_desc' : list(),
+                                'prompt' : prompt,
+                                'element' : element,
+                                'type' : ptype,
+                                'name' : name })
+            
+            idx += 1
+        
+        for action in self.GetItems(objType = ModelAction):
+            if not action.IsEnabled():
+                continue
+            name   = action.GetName()
+            params = action.GetParams()
+            for f in params['flags']:
+                if f.get('parameterized', False):
+                    if name not in result:
+                        result[name] = { 'flags' : list(),
+                                         'params': list(),
+                                         'idx'   : idx }
+                    result[name]['flags'].append(f)
+            for p in params['params']:
+                if p.get('parameterized', False):
+                    if name not in result:
+                        result[name] = { 'flags' : list(),
+                                         'params': list(),
+                                         'idx'   : idx }
+                    result[name]['params'].append(p)
+            if name in result:
+                idx += 1
+        
+        self.variablesParams = result # record parameters
+        
+        return result


Property changes on: grass/trunk/gui/wxpython/gmodeler/model.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/gmodeler/model_file.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/model_file.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gmodeler/model_file.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,695 @@
+"""!
+ at package gmodeler.model_file
+
+ at brief wxGUI Graphical Modeler - model definition file
+
+Classes:
+ - ProcessModelFile
+ - WriteModelFile
+ - WritePythonFile
+
+(C) 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import time
+import re
+
+from gui_core.forms import GUI
+from core.gcmd      import GWarning, EncodeString
+
+class ProcessModelFile:
+    """!Process GRASS model file (gxm)"""
+    def __init__(self, tree):
+        """!A ElementTree handler for the GXM XML file, as defined in
+        grass-gxm.dtd.
+        """
+        self.tree = tree
+        self.root = self.tree.getroot()
+        
+        # list of actions, data
+        self.properties = dict()
+        self.variables  = dict() 
+        self.actions = list()
+        self.data    = list()
+        self.loops   = list()
+        self.conditions = list()
+        
+        self._processWindow()
+        self._processProperties()
+        self._processVariables()
+        self._processItems()
+        self._processData()
+        
+    def _filterValue(self, value):
+        """!Filter value
+        
+        @param value
+        """
+        value = value.replace('&lt;', '<')
+        value = value.replace('&gt;', '>')
+        
+        return value
+        
+    def _getNodeText(self, node, tag, default = ''):
+        """!Get node text"""
+        p = node.find(tag)
+        if p is not None:
+            if p.text:
+                return utils.normalize_whitespace(p.text)
+            else:
+                return ''
+        
+        return default
+    
+    def _processWindow(self):
+        """!Process window properties"""
+        node = self.root.find('window')
+        if node is None:
+            self.pos = self.size = None
+            return
+        
+        self.pos, self.size = self._getDim(node)
+        
+    def _processProperties(self):
+        """!Process model properties"""
+        node = self.root.find('properties')
+        if node is None:
+            return
+        for key in ('name', 'description', 'author'):
+            self._processProperty(node, key)
+        
+        for f in node.findall('flag'):
+            name = f.get('name', '')
+            if name == 'overwrite':
+                self.properties['overwrite'] = True
+        
+    def _processProperty(self, pnode, name):
+        """!Process given property"""
+        node = pnode.find(name)
+        if node is not None:
+            self.properties[name] = node.text
+        else:
+            self.properties[name] = ''
+
+    def _processVariables(self):
+        """!Process model variables"""
+        vnode = self.root.find('variables')
+        if vnode is None:
+            return
+        for node in vnode.findall('variable'):
+            name = node.get('name', '')
+            if not name:
+                continue # should not happen
+            self.variables[name] = { 'type' : node.get('type', 'string') }
+            for key in ('description', 'value'):
+                self._processVariable(node, name, key)
+        
+    def _processVariable(self, pnode, name, key):
+        """!Process given variable"""
+        node = pnode.find(key)
+        if node is not None:
+            if node.text:
+                self.variables[name][key] = node.text
+
+    def _processItems(self):
+        """!Process model items (actions, loops, conditions)"""
+        self._processActions()
+        self._processLoops()
+        self._processConditions()
+        
+    def _processActions(self):
+        """!Process model file"""
+        for action in self.root.findall('action'):
+            pos, size = self._getDim(action)
+            disabled  = False
+            
+            task = action.find('task')
+            if task is not None:
+                if task.find('disabled') is not None:
+                    disabled = True
+                task = self._processTask(task)
+            else:
+                task = None
+            
+            aId = int(action.get('id', -1))
+            
+            self.actions.append({ 'pos'      : pos,
+                                  'size'     : size,
+                                  'task'     : task,
+                                  'id'       : aId,
+                                  'disabled' : disabled })
+            
+    def _getDim(self, node):
+        """!Get position and size of shape"""
+        pos = size = None
+        posAttr = node.get('pos', None)
+        if posAttr:
+            posVal = map(int, posAttr.split(','))
+            try:
+                pos = (posVal[0], posVal[1])
+            except:
+                pos = None
+        
+        sizeAttr = node.get('size', None)
+        if sizeAttr:
+            sizeVal = map(int, sizeAttr.split(','))
+            try:
+                size = (sizeVal[0], sizeVal[1])
+            except:
+                size = None
+        
+        return pos, size        
+    
+    def _processData(self):
+        """!Process model file"""
+        for data in self.root.findall('data'):
+            pos, size = self._getDim(data)
+            param = data.find('data-parameter')
+            prompt = value = None
+            if param is not None:
+                prompt = param.get('prompt', None)
+                value = self._filterValue(self._getNodeText(param, 'value'))
+            
+            if data.find('intermediate') is None:
+                intermediate = False
+            else:
+                intermediate = True
+            
+            rels = list()
+            for rel in data.findall('relation'):
+                defrel = { 'id'  : int(rel.get('id', -1)),
+                           'dir' : rel.get('dir', 'to'),
+                           'name' : rel.get('name', '') }
+                points = list()
+                for point in rel.findall('point'):
+                    x = self._filterValue(self._getNodeText(point, 'x'))
+                    y = self._filterValue(self._getNodeText(point, 'y'))
+                    points.append((float(x), float(y)))
+                defrel['points'] = points
+                rels.append(defrel)
+            
+            self.data.append({ 'pos' : pos,
+                               'size': size,
+                               'prompt' : prompt,
+                               'value' : value,
+                               'intermediate' : intermediate,
+                               'rels' : rels })
+        
+    def _processTask(self, node):
+        """!Process task
+
+        @return grassTask instance
+        @return None on error
+        """
+        cmd = list()
+        parameterized = list()
+        
+        name = node.get('name', None)
+        if not name:
+            return None
+        
+        cmd.append(name)
+        
+        # flags
+        for f in node.findall('flag'):
+            flag = f.get('name', '')
+            if f.get('parameterized', '0') == '1':
+                parameterized.append(('flag', flag))
+                if f.get('value', '1') == '0':
+                    continue
+            if len(flag) > 1:
+                cmd.append('--' + flag)
+            else:
+                cmd.append('-' + flag)
+        # parameters
+        for p in node.findall('parameter'):
+            name = p.get('name', '')
+            if p.find('parameterized') is not None:
+                parameterized.append(('param', name))
+            cmd.append('%s=%s' % (name,
+                                  self._filterValue(self._getNodeText(p, 'value'))))
+        
+        task, err = GUI(show = None, checkError = True).ParseCommand(cmd = cmd)
+        if err:
+            GWarning(os.linesep.join(err))
+        
+        for opt, name in parameterized:
+            if opt == 'flag':
+                task.set_flag(name, True, element = 'parameterized')
+            else:
+                task.set_param(name, True, element = 'parameterized')
+        
+        return task
+
+    def _processLoops(self):
+        """!Process model loops"""
+        for node in self.root.findall('loop'):
+            pos, size = self._getDim(node)
+            text = self._filterValue(self._getNodeText(node, 'condition')).strip()
+            aid = list()
+            for anode in node.findall('item'):
+                try:
+                    aid.append(int(anode.text))
+                except ValueError:
+                    pass
+            
+            self.loops.append({ 'pos'     : pos,
+                                'size'    : size,
+                                'text'    : text,
+                                'id'      : int(node.get('id', -1)),
+                                'items'   : aid })
+        
+    def _processConditions(self):
+        """!Process model conditions"""
+        for node in self.root.findall('if-else'):
+            pos, size = self._getDim(node)
+            text = self._filterValue(self._getNodeText(node, 'condition')).strip()
+            aid = { 'if'   : list(),
+                    'else' : list() }
+            for b in aid.keys():
+                bnode = node.find(b)
+                if bnode is None:
+                    continue
+                for anode in bnode.findall('item'):
+                    try:
+                        aid[b].append(int(anode.text))
+                    except ValueError:
+                        pass
+            
+            self.conditions.append({ 'pos'     : pos,
+                                     'size'    : size,
+                                     'text'    : text,
+                                     'id'      : int(node.get('id', -1)),
+                                     'items'   : aid })
+        
+class WriteModelFile:
+    """!Generic class for writing model file"""
+    def __init__(self, fd, model):
+        self.fd         = fd
+        self.model      = model
+        self.properties = model.GetProperties()
+        self.variables  = model.GetVariables()
+        self.items      = model.GetItems()
+        
+        self.indent = 0
+        
+        self._header()
+        
+        self._window()
+        self._properties()
+        self._variables()
+        self._items()
+        
+        dataList = list()
+        for action in model.GetItems(objType = ModelAction):
+            for rel in action.GetRelations():
+                dataItem = rel.GetData()
+                if dataItem not in dataList:
+                    dataList.append(dataItem)
+        self._data(dataList)
+        
+        self._footer()
+
+    def _filterValue(self, value):
+        """!Make value XML-valid"""
+        value = value.replace('<', '&lt;')
+        value = value.replace('>', '&gt;')
+        
+        return value
+        
+    def _header(self):
+        """!Write header"""
+        self.fd.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+        self.fd.write('<!DOCTYPE gxm SYSTEM "grass-gxm.dtd">\n')
+        self.fd.write('%s<gxm>\n' % (' ' * self.indent))
+        self.indent += 4
+                
+    def _footer(self):
+        """!Write footer"""
+        self.indent -= 4
+        self.fd.write('%s</gxm>\n' % (' ' * self.indent))
+
+    def _window(self):
+        """!Write window properties"""
+        canvas = self.model.GetCanvas()
+        if canvas is None:
+            return
+        win  = canvas.parent
+        pos  = win.GetPosition()
+        size = win.GetSize()
+        self.fd.write('%s<window pos="%d,%d" size="%d,%d" />\n' % \
+                          (' ' * self.indent, pos[0], pos[1], size[0], size[1]))
+        
+    def _properties(self):
+        """!Write model properties"""
+        self.fd.write('%s<properties>\n' % (' ' * self.indent))
+        self.indent += 4
+        if self.properties['name']:
+            self.fd.write('%s<name>%s</name>\n' % (' ' * self.indent, self.properties['name']))
+        if self.properties['description']:
+            self.fd.write('%s<description>%s</description>\n' % (' ' * self.indent,
+                                                                 EncodeString(self.properties['description'])))
+        if self.properties['author']:
+            self.fd.write('%s<author>%s</author>\n' % (' ' * self.indent,
+                                                       EncodeString(self.properties['author'])))
+        
+        if 'overwrite' in self.properties and \
+                self.properties['overwrite']:
+            self.fd.write('%s<flag name="overwrite" />\n' % (' ' * self.indent))
+        self.indent -= 4
+        self.fd.write('%s</properties>\n' % (' ' * self.indent))
+
+    def _variables(self):
+        """!Write model variables"""
+        if not self.variables:
+            return
+        self.fd.write('%s<variables>\n' % (' ' * self.indent))
+        self.indent += 4
+        for name, values in self.variables.iteritems():
+            self.fd.write('%s<variable name="%s" type="%s">\n' % \
+                              (' ' * self.indent, name, values['type']))
+            self.indent += 4
+            if 'value' in values:
+                self.fd.write('%s<value>%s</value>\n' % \
+                                  (' ' * self.indent, values['value']))
+            if 'description' in values:
+                self.fd.write('%s<description>%s</description>\n' % \
+                                  (' ' * self.indent, values['description']))
+            self.indent -= 4
+            self.fd.write('%s</variable>\n' % (' ' * self.indent))
+        self.indent -= 4
+        self.fd.write('%s</variables>\n' % (' ' * self.indent))
+        
+    def _items(self):
+        """!Write actions/loops/conditions"""
+        for item in self.items:
+            if isinstance(item, ModelAction):
+                self._action(item)
+            elif isinstance(item, ModelLoop):
+                self._loop(item)
+            elif isinstance(item, ModelCondition):
+                self._condition(item)
+        
+    def _action(self, action):
+        """!Write actions"""
+        self.fd.write('%s<action id="%d" name="%s" pos="%d,%d" size="%d,%d">\n' % \
+                          (' ' * self.indent, action.GetId(), action.GetName(), action.GetX(), action.GetY(),
+                           action.GetWidth(), action.GetHeight()))
+        self.indent += 4
+        self.fd.write('%s<task name="%s">\n' % (' ' * self.indent, action.GetLog(string = False)[0]))
+        self.indent += 4
+        if not action.IsEnabled():
+            self.fd.write('%s<disabled />\n' % (' ' * self.indent))
+        for key, val in action.GetParams().iteritems():
+            if key == 'flags':
+                for f in val:
+                    if f.get('value', False) or f.get('parameterized', False):
+                        if f.get('parameterized', False):
+                            if f.get('value', False) == False:
+                                self.fd.write('%s<flag name="%s" value="0" parameterized="1" />\n' %
+                                              (' ' * self.indent, f.get('name', '')))
+                            else:
+                                self.fd.write('%s<flag name="%s" parameterized="1" />\n' %
+                                              (' ' * self.indent, f.get('name', '')))
+                        else:
+                            self.fd.write('%s<flag name="%s" />\n' %
+                                          (' ' * self.indent, f.get('name', '')))
+            else: # parameter
+                for p in val:
+                    if not p.get('value', '') and not p.get('parameterized', False):
+                        continue
+                    self.fd.write('%s<parameter name="%s">\n' %
+                                  (' ' * self.indent, p.get('name', '')))
+                    self.indent += 4
+                    if p.get('parameterized', False):
+                        self.fd.write('%s<parameterized />\n' % (' ' * self.indent))
+                    self.fd.write('%s<value>%s</value>\n' %
+                                  (' ' * self.indent, self._filterValue(p.get('value', ''))))
+                    self.indent -= 4
+                    self.fd.write('%s</parameter>\n' % (' ' * self.indent))
+        self.indent -= 4
+        self.fd.write('%s</task>\n' % (' ' * self.indent))
+        self.indent -= 4
+        self.fd.write('%s</action>\n' % (' ' * self.indent))
+                
+    def _data(self, dataList):
+        """!Write data"""
+        for data in dataList:
+            self.fd.write('%s<data pos="%d,%d" size="%d,%d">\n' % \
+                              (' ' * self.indent, data.GetX(), data.GetY(),
+                               data.GetWidth(), data.GetHeight()))
+            self.indent += 4
+            self.fd.write('%s<data-parameter prompt="%s">\n' % \
+                              (' ' * self.indent, data.GetPrompt()))
+            self.indent += 4
+            self.fd.write('%s<value>%s</value>\n' %
+                          (' ' * self.indent, self._filterValue(data.GetValue())))
+            self.indent -= 4
+            self.fd.write('%s</data-parameter>\n' % (' ' * self.indent))
+            
+            if data.IsIntermediate():
+                self.fd.write('%s<intermediate />\n' % (' ' * self.indent))
+
+            # relations
+            for ft in ('from', 'to'):
+                for rel in data.GetRelations(ft):
+                    if ft == 'from':
+                        aid = rel.GetTo().GetId()
+                    else:
+                        aid  = rel.GetFrom().GetId()
+                    self.fd.write('%s<relation dir="%s" id="%d" name="%s">\n' % \
+                                      (' ' * self.indent, ft, aid, rel.GetName()))
+                    self.indent += 4
+                    for point in rel.GetLineControlPoints()[1:-1]:
+                        self.fd.write('%s<point>\n' % (' ' * self.indent))
+                        self.indent += 4
+                        x, y = point.Get()
+                        self.fd.write('%s<x>%d</x>\n' % (' ' * self.indent, int(x)))
+                        self.fd.write('%s<y>%d</y>\n' % (' ' * self.indent, int(y)))
+                        self.indent -= 4
+                        self.fd.write('%s</point>\n' % (' ' * self.indent))
+                    self.indent -= 4
+                    self.fd.write('%s</relation>\n' % (' ' * self.indent))
+                
+            self.indent -= 4
+            self.fd.write('%s</data>\n' % (' ' * self.indent))
+
+    def _loop(self, loop):
+        """!Write loops"""
+        self.fd.write('%s<loop id="%d" pos="%d,%d" size="%d,%d">\n' % \
+                          (' ' * self.indent, loop.GetId(), loop.GetX(), loop.GetY(),
+                           loop.GetWidth(), loop.GetHeight()))
+        text = loop.GetText()
+        self.indent += 4
+        if text:
+            self.fd.write('%s<condition>%s</condition>\n' %
+                          (' ' * self.indent, self._filterValue(text)))
+        for item in loop.GetItems():
+            self.fd.write('%s<item>%d</item>\n' %
+                          (' ' * self.indent, item.GetId()))
+        self.indent -= 4
+        self.fd.write('%s</loop>\n' % (' ' * self.indent))
+
+    def _condition(self, condition):
+        """!Write conditions"""
+        bbox = condition.GetBoundingBoxMin()
+        self.fd.write('%s<if-else id="%d" pos="%d,%d" size="%d,%d">\n' % \
+                          (' ' * self.indent, condition.GetId(), condition.GetX(), condition.GetY(),
+                           bbox[0], bbox[1]))
+        text = condition.GetText()
+        self.indent += 4
+        if text:
+            self.fd.write('%s<condition>%s</condition>\n' %
+                          (' ' * self.indent, self._filterValue(text)))
+        items = condition.GetItems()
+        for b in items.keys():
+            if len(items[b]) < 1:
+                continue
+            self.fd.write('%s<%s>\n' % (' ' * self.indent, b))
+            self.indent += 4
+            for item in items[b]:
+                self.fd.write('%s<item>%d</item>\n' %
+                              (' ' * self.indent, item.GetId()))
+            self.indent -= 4
+            self.fd.write('%s</%s>\n' % (' ' * self.indent, b))
+        
+        self.indent -= 4
+        self.fd.write('%s</if-else>\n' % (' ' * self.indent))
+        
+class WritePythonFile:
+    def __init__(self, fd, model):
+        """!Class for exporting model to Python script
+
+        @param fd file desciptor
+        """
+        self.fd     = fd
+        self.model  = model
+        self.indent = 4
+
+        self._writePython()
+        
+    def _writePython(self):
+        """!Write model to file"""
+        properties = self.model.GetProperties()
+        
+        self.fd.write(
+r"""#!/usr/bin/env python
+#
+############################################################################
+#
+# MODULE:       %s
+#
+# AUTHOR(S):	%s
+#               
+# PURPOSE:      %s
+#
+# DATE:         %s
+#
+#############################################################################
+""" % (properties['name'],
+       properties['author'],
+       properties['description'],
+       time.asctime()))
+        
+        self.fd.write(
+r"""
+import sys
+import os
+import atexit
+
+import grass.script as grass
+""")
+        
+        # cleanup()
+        rast, vect, rast3d, msg = self.model.GetIntermediateData()
+        self.fd.write(
+r"""
+def cleanup():
+""")
+        if rast:
+            self.fd.write(
+r"""    grass.run_command('g.remove',
+                      rast=%s)
+""" % ','.join(map(lambda x: "'" + x + "'", rast)))
+        if vect:
+            self.fd.write(
+r"""    grass.run_command('g.remove',
+                      vect = %s)
+""" % ','.join(map(lambda x: "'" + x + "'", vect)))
+        if rast3d:
+            self.fd.write(
+r"""    grass.run_command('g.remove',
+                      rast3d = %s)
+""" % ','.join(map(lambda x: "'" + x + "'", rast3d)))
+        if not rast and not vect and not rast3d:
+            self.fd.write('    pass\n')
+        
+        self.fd.write("\ndef main():\n")
+        for item in self.model.GetItems():
+            self._writePythonItem(item)
+        
+        self.fd.write("\n    return 0\n")
+        
+        self.fd.write(
+r"""
+if __name__ == "__main__":
+    options, flags = grass.parser()
+    atexit.register(cleanup)
+    sys.exit(main())
+""")
+        
+    def _writePythonItem(self, item, ignoreBlock = True, variables = []):
+        """!Write model object to Python file"""
+        if isinstance(item, ModelAction):
+            if ignoreBlock and item.GetBlockId(): # ignore items in loops of conditions
+                return
+            self._writePythonAction(item, variables = variables)
+        elif isinstance(item, ModelLoop) or isinstance(item, ModelCondition):
+            # substitute condition
+            variables = self.model.GetVariables()
+            cond = item.GetText()
+            for variable in variables:
+                pattern = re.compile('%' + variable)
+                if pattern.search(cond):
+                    value = variables[variable].get('value', '')
+                    if variables[variable].get('type', 'string') == 'string':
+                        value = '"' + value + '"'
+                    cond = pattern.sub(value, cond)
+            if isinstance(item, ModelLoop):
+                condVar, condText = map(lambda x: x.strip(), re.split('\s*in\s*', cond))
+                cond = "%sfor %s in " % (' ' * self.indent, condVar)
+                if condText[0] == '`' and condText[-1] == '`':
+                    task = GUI(show = None).ParseCommand(cmd = utils.split(condText[1:-1]))
+                    cond += "grass.read_command("
+                    cond += self._getPythonActionCmd(task, len(cond), variables = [condVar]) + ".splitlines()"
+                else:
+                    cond += condText
+                self.fd.write('%s:\n' % cond)
+                self.indent += 4
+                for action in item.GetItems():
+                    self._writePythonItem(action, ignoreBlock = False, variables = [condVar])
+                self.indent -= 4
+            else: # ModelCondition
+                self.fd.write('%sif %s:\n' % (' ' * self.indent, cond))
+                self.indent += 4
+                condItems = item.GetItems()
+                for action in condItems['if']:
+                    self._writePythonItem(action, ignoreBlock = False)
+                if condItems['else']:
+                    self.indent -= 4
+                    self.fd.write('%selse:\n' % (' ' * self.indent))
+                    self.indent += 4
+                    for action in condItems['else']:
+                        self._writePythonItem(action, ignoreBlock = False)
+                self.indent += 4
+        
+    def _writePythonAction(self, item, variables = []):
+        """!Write model action to Python file"""
+        task = GUI(show = None).ParseCommand(cmd = item.GetLog(string = False))
+        strcmd = "%sgrass.run_command(" % (' ' * self.indent)
+        self.fd.write(strcmd + self._getPythonActionCmd(task, len(strcmd), variables) + '\n')
+        
+    def _getPythonActionCmd(self, task, cmdIndent, variables = []):
+        opts = task.get_options()
+        
+        ret = ''
+        flags = ''
+        params = list()
+        
+        for f in opts['flags']:
+            if f.get('value', False):
+                name = f.get('name', '')
+                if len(name) > 1:
+                    params.append('%s = True' % name)
+                else:
+                    flags += name
+            
+        for p in opts['params']:
+            name = p.get('name', None)
+            value = p.get('value', None)
+            if name and value:
+                ptype = p.get('type', 'string')
+                if value[0] == '%':
+                    params.append("%s = %s" % (name, value[1:]))
+                elif ptype == 'string':
+                    params.append('%s = "%s"' % (name, value))
+                else:
+                    params.append("%s = %s" % (name, value))
+        
+        ret += '"%s"' % task.get_name()
+        if flags:
+            ret += ",\n%sflags = '%s'" % (' ' * cmdIndent, flags)
+        if len(params) > 0:
+            ret += ",\n"
+            for opt in params[:-1]:
+                ret += "%s%s,\n" % (' ' * cmdIndent, opt)
+            ret += "%s%s)" % (' ' * cmdIndent, params[-1])
+        else:
+            ret += ")"
+        
+        return ret


Property changes on: grass/trunk/gui/wxpython/gmodeler/model_file.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Added: grass/trunk/gui/wxpython/gmodeler/preferences.py
===================================================================
--- grass/trunk/gui/wxpython/gmodeler/preferences.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gmodeler/preferences.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,527 @@
+"""!
+ at package gmodeler.preferences
+
+ at brief wxGUI Graphical Modeler - preferences
+
+Classes:
+ - PreferencesDialog
+ - PropertiesDialog
+
+(C) 2010-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import wx
+import wx.lib.colourselect    as csel
+
+from core                 import globalvar
+from gui_core.preferences import PreferencesBaseDialog
+from core.settings        import UserSettings
+
+class PreferencesDialog(PreferencesBaseDialog):
+    """!User preferences dialog"""
+    def __init__(self, parent, settings = UserSettings,
+                 title = _("Modeler settings")):
+        
+        PreferencesBaseDialog.__init__(self, parent = parent, title = title,
+                                       settings = settings)
+        
+        # create notebook pages
+        self._createGeneralPage(self.notebook)
+        self._createActionPage(self.notebook)
+        self._createDataPage(self.notebook)
+        self._createLoopPage(self.notebook)
+        
+        self.SetMinSize(self.GetBestSize())
+        self.SetSize(self.size)
+
+    def _createGeneralPage(self, notebook):
+        """!Create notebook page for action settings"""
+        panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
+        notebook.AddPage(page = panel, text = _("General"))
+        
+        # colors
+        border = wx.BoxSizer(wx.VERTICAL)
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Item properties"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(0)
+        
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                           label = _("Disabled:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='disabled', subkey='color'),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        rColor.SetName('GetColour')
+        self.winId['modeler:disabled:color'] = rColor.GetId()
+        
+        gridSizer.Add(item = rCoxolor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+        border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+
+        panel.SetSizer(border)
+        
+        return panel
+
+    def _createActionPage(self, notebook):
+        """!Create notebook page for action settings"""
+        panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
+        notebook.AddPage(page = panel, text = _("Action"))
+        
+        # colors
+        border = wx.BoxSizer(wx.VERTICAL)
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Color"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(0)
+        
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Valid:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'valid')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        vColor.SetName('GetColour')
+        self.winId['modeler:action:color:valid'] = vColor.GetId()
+        
+        gridSizer.Add(item = vColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Invalid:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        iColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'invalid')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        iColor.SetName('GetColour')
+        self.winId['modeler:action:color:invalid'] = iColor.GetId()
+        
+        gridSizer.Add(item = iColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Running:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'running')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        rColor.SetName('GetColour')
+        self.winId['modeler:action:color:running'] = rColor.GetId()
+        
+        gridSizer.Add(item = rColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+        border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+        
+        # size
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Shape size"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
+        gridSizer.AddGrowableCol(0)
+
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Width:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        
+        width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                            min = 0, max = 500,
+                            initial = self.settings.Get(group='modeler', key='action', subkey=('size', 'width')))
+        width.SetName('GetValue')
+        self.winId['modeler:action:size:width'] = width.GetId()
+        
+        gridSizer.Add(item = width,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                         label=_("Height:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos=(row, 0))
+        
+        height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                             min = 0, max = 500,
+                             initial = self.settings.Get(group='modeler', key='action', subkey=('size', 'height')))
+        height.SetName('GetValue')
+        self.winId['modeler:action:size:height'] = height.GetId()
+        
+        gridSizer.Add(item = height,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
+        border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
+                
+        panel.SetSizer(border)
+        
+        return panel
+
+    def _createDataPage(self, notebook):
+        """!Create notebook page for data settings"""
+        panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
+        notebook.AddPage(page = panel, text = _("Data"))
+        
+        # colors
+        border = wx.BoxSizer(wx.VERTICAL)
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Type"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(0)
+        
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Raster:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'raster')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        rColor.SetName('GetColour')
+        self.winId['modeler:data:color:raster'] = rColor.GetId()
+        
+        gridSizer.Add(item = rColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("3D raster:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        r3Color = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                    colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'raster3d')),
+                                    size = globalvar.DIALOG_COLOR_SIZE)
+        r3Color.SetName('GetColour')
+        self.winId['modeler:data:color:raster3d'] = r3Color.GetId()
+        
+        gridSizer.Add(item = r3Color,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Vector:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'vector')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        vColor.SetName('GetColour')
+        self.winId['modeler:data:color:vector'] = vColor.GetId()
+        
+        gridSizer.Add(item = vColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+        border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+
+        # size
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Shape size"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
+        gridSizer.AddGrowableCol(0)
+        
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Width:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        
+        width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                            min = 0, max = 500,
+                            initial = self.settings.Get(group='modeler', key='data', subkey=('size', 'width')))
+        width.SetName('GetValue')
+        self.winId['modeler:data:size:width'] = width.GetId()
+        
+        gridSizer.Add(item = width,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                         label=_("Height:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos=(row, 0))
+        
+        height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                             min = 0, max = 500,
+                             initial = self.settings.Get(group='modeler', key='data', subkey=('size', 'height')))
+        height.SetName('GetValue')
+        self.winId['modeler:data:size:height'] = height.GetId()
+        
+        gridSizer.Add(item = height,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
+        border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
+        
+        panel.SetSizer(border)
+        
+        return panel
+
+    def _createLoopPage(self, notebook):
+        """!Create notebook page for loop settings"""
+        panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
+        notebook.AddPage(page = panel, text = _("Loop"))
+        
+        # colors
+        border = wx.BoxSizer(wx.VERTICAL)
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Color"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(0)
+        
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Valid:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
+                                   colour = self.settings.Get(group='modeler', key='loop', subkey=('color', 'valid')),
+                                   size = globalvar.DIALOG_COLOR_SIZE)
+        vColor.SetName('GetColour')
+        self.winId['modeler:loop:color:valid'] = vColor.GetId()
+        
+        gridSizer.Add(item = vColor,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+        border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+        
+        # size
+        box   = wx.StaticBox (parent = panel, id = wx.ID_ANY,
+                              label = " %s " % _("Shape size"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        
+        gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
+        gridSizer.AddGrowableCol(0)
+
+        row = 0
+        gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                         label = _("Width:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 0))
+        
+        width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                            min = 0, max = 500,
+                            initial = self.settings.Get(group='modeler', key='loop', subkey=('size', 'width')))
+        width.SetName('GetValue')
+        self.winId['modeler:loop:size:width'] = width.GetId()
+        
+        gridSizer.Add(item = width,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+
+        row += 1
+        gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
+                                         label=_("Height:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos=(row, 0))
+        
+        height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
+                             min = 0, max = 500,
+                             initial = self.settings.Get(group='modeler', key='loop', subkey=('size', 'height')))
+        height.SetName('GetValue')
+        self.winId['modeler:loop:size:height'] = height.GetId()
+        
+        gridSizer.Add(item = height,
+                      flag = wx.ALIGN_RIGHT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (row, 1))
+        
+        sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
+        border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
+                
+        panel.SetSizer(border)
+        
+        return panel
+
+    def OnApply(self, event):
+        """!Button 'Apply' pressed"""
+        PreferencesBaseDialog.OnApply(self, event)
+        
+        self.parent.GetModel().Update()
+        self.parent.GetCanvas().Refresh()
+
+    def OnSave(self, event):
+        """!Button 'Save' pressed"""
+        PreferencesBaseDialog.OnSave(self, event)
+        
+        self.parent.GetModel().Update()
+        self.parent.GetCanvas().Refresh()
+
+class PropertiesDialog(wx.Dialog):
+    """!Model properties dialog
+    """
+    def __init__(self, parent, id = wx.ID_ANY,
+                 title = _('Model properties'),
+                 size = (350, 400),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
+        wx.Dialog.__init__(self, parent, id, title, size = size,
+                           style = style)
+        
+        self.metaBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                    label=" %s " % _("Metadata"))
+        self.cmdBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                   label=" %s " % _("Commands"))
+        
+        self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY,
+                                size = (300, 25))
+        self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY,
+                                style = wx.TE_MULTILINE,
+                                size = (300, 50))
+        self.author = wx.TextCtrl(parent = self, id = wx.ID_ANY,
+                                size = (300, 25))
+        
+        # commands
+        self.overwrite = wx.CheckBox(parent = self, id=wx.ID_ANY,
+                                     label=_("Allow output files to overwrite existing files"))
+        self.overwrite.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
+        
+        # buttons
+        self.btnOk     = wx.Button(self, wx.ID_OK)
+        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
+        self.btnOk.SetDefault()
+        
+        self.btnOk.SetToolTipString(_("Apply properties"))
+        self.btnOk.SetDefault()
+        self.btnCancel.SetToolTipString(_("Close dialog and ignore changes"))
+        
+        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+        
+        self._layout()
+
+    def _layout(self):
+        metaSizer = wx.StaticBoxSizer(self.metaBox, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(0)
+        gridSizer.AddGrowableRow(1)
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                         label = _("Name:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (0, 0))
+        gridSizer.Add(item = self.name,
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
+                      pos = (0, 1))
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                         label = _("Description:")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (1, 0))
+        gridSizer.Add(item = self.desc,
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
+                      pos = (1, 1))
+        gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                         label = _("Author(s):")),
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL,
+                      pos = (2, 0))
+        gridSizer.Add(item = self.author,
+                      flag = wx.ALIGN_LEFT |
+                      wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
+                      pos = (2, 1))
+        metaSizer.Add(item = gridSizer)
+        
+        cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL)
+        cmdSizer.Add(item = self.overwrite,
+                     flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        btnStdSizer = wx.StdDialogButtonSizer()
+        btnStdSizer.AddButton(self.btnCancel)
+        btnStdSizer.AddButton(self.btnOk)
+        btnStdSizer.Realize()
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item=metaSizer, proportion=1,
+                      flag=wx.EXPAND | wx.ALL, border=5)
+        mainSizer.Add(item=cmdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
+        mainSizer.Add(item=btnStdSizer, proportion=0,
+                      flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
+        
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+    def OnCloseWindow(self, event):
+        self.Hide()
+        
+    def GetValues(self):
+        """!Get values"""
+        return { 'name'        : self.name.GetValue(),
+                 'description' : self.desc.GetValue(),
+                 'author'      : self.author.GetValue(),
+                 'overwrite'   : self.overwrite.IsChecked() }
+    
+    def Init(self, prop):
+        """!Initialize dialog"""
+        self.name.SetValue(prop['name'])
+        self.desc.SetValue(prop['description'])
+        self.author.SetValue(prop['author'])
+        if 'overwrite' in prop:
+            self.overwrite.SetValue(prop['overwrite'])


Property changes on: grass/trunk/gui/wxpython/gmodeler/preferences.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Copied: grass/trunk/gui/wxpython/gui_core/dialogs.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/gdialogs.py)
===================================================================
--- grass/trunk/gui/wxpython/gui_core/dialogs.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/dialogs.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,2403 @@
+"""!
+ at package gui_core.gdialogs
+
+ at brief Various dialogs used in wxGUI.
+
+List of classes:
+ - ElementDialog
+ - LocationDialog
+ - MapsetDialog
+ - NewVectorDialog
+ - SavedRegion
+ - DecorationDialog
+ - TextLayerDialog 
+ - GroupDialog
+ - MapLayersDialog
+ - ImportDialog
+ - GdalImportDialog
+ - GdalOutputDialog
+ - DxfImportDialog
+ - LayersList (used by MultiImport) 
+ - SetOpacityDialog
+ - StaticWrapText
+ - ImageSizeDialog
+ - SqlQueryFrame
+
+(C) 2008-2011 by the GRASS Development Team
+
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+ at author Anna Kratochvilova <kratochanna gmail.com> (GroupDialog)
+"""
+
+import os
+import sys
+import re
+from bisect import bisect
+
+import wx
+import wx.lib.filebrowsebutton as filebrowse
+import wx.lib.mixins.listctrl as listmix
+
+from grass.script import core as grass
+from grass.script import task as gtask
+
+from core             import globalvar
+from core.gcmd        import GError, RunCommand, GMessage
+from gui_core.gselect import ElementSelect, LocationSelect, MapsetSelect, Select, OgrTypeSelect, GdalSelect
+from core.forms       import GUI
+from core.utils       import GetListOfMapsets, GetLayerNameFromCmd, GetValidLayerName
+from core.settings    import UserSettings
+from core.debug       import Debug
+
+class ElementDialog(wx.Dialog):
+    def __init__(self, parent, title, label, id = wx.ID_ANY,
+                 etype = False, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
+                 **kwargs):
+        """!General dialog to choose given element (location, mapset, vector map, etc.)
+        
+        @param parent window
+        @param title window title
+        @param label element label
+        @param etype show also ElementSelect
+        """
+        wx.Dialog.__init__(self, parent, id, title, style = style, **kwargs)
+        
+        self.etype = etype
+        self.label = label
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.btnOK     = wx.Button(parent = self.panel, id = wx.ID_OK)
+        self.btnOK.SetDefault()
+        self.btnOK.Enable(False)
+        
+        if self.etype:
+            self.typeSelect = ElementSelect(parent = self.panel,
+                                            size = globalvar.DIALOG_GSELECT_SIZE)
+            self.typeSelect.Bind(wx.EVT_CHOICE, self.OnType)
+        
+        self.element = None # must be defined 
+        
+        self.__layout()
+        
+    def PostInit(self):
+        self.element.SetFocus()
+        self.element.Bind(wx.EVT_TEXT, self.OnElement)
+        
+    def OnType(self, event):
+        """!Select element type"""
+        if not self.etype:
+            return
+        evalue = self.typeSelect.GetValue(event.GetString())
+        self.element.SetType(evalue)
+        
+    def OnElement(self, event):
+        """!Name for vector map layer given"""
+        if len(event.GetString()) > 0:
+            self.btnOK.Enable(True)
+        else:
+            self.btnOK.Enable(False)
+        
+    def __layout(self):
+        """!Do layout"""
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        self.dataSizer = wx.BoxSizer(wx.VERTICAL)
+        
+        if self.etype:
+            self.dataSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                                    label = _("Type of element:")),
+                               proportion = 0, flag = wx.ALL, border = 1)
+            self.dataSizer.Add(item = self.typeSelect,
+                               proportion = 0, flag = wx.ALL, border = 1)
+        
+        self.dataSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                                label = self.label),
+                           proportion = 0, flag = wx.ALL, border = 1)
+        
+        # buttons
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.AddButton(self.btnOK)
+        btnSizer.Realize()
+        
+        self.sizer.Add(item = self.dataSizer, proportion = 1,
+                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+        self.sizer.Add(item = btnSizer, proportion = 0,
+                       flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+        
+    def GetElement(self):
+        """!Return (mapName, overwrite)"""
+        return self.element.GetValue()
+    
+    def GetType(self):
+        """!Get element type"""
+        return self.element.tcp.GetType()
+        
+class LocationDialog(ElementDialog):
+    """!Dialog used to select location"""
+    def __init__(self, parent, title = _("Select GRASS location and mapset"), id =  wx.ID_ANY):
+        ElementDialog.__init__(self, parent, title, label = _("Name of GRASS location:"))
+
+        self.element = LocationSelect(parent = self.panel, id = wx.ID_ANY,
+                                      size = globalvar.DIALOG_GSELECT_SIZE)
+        
+        self.element1 = MapsetSelect(parent = self.panel, id = wx.ID_ANY,
+                                     size = globalvar.DIALOG_GSELECT_SIZE,
+                                     setItems = False)
+        
+        self.PostInit()
+        
+        self.__Layout()
+        self.SetMinSize(self.GetSize())
+
+    def __Layout(self):
+        """!Do layout"""
+        self.dataSizer.Add(self.element, proportion = 0,
+                           flag = wx.EXPAND | wx.ALL, border = 1)
+ 
+        self.dataSizer.Add(wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                         label = _("Name of mapset:")), proportion = 0,
+                           flag = wx.EXPAND | wx.ALL, border = 1)
+
+        self.dataSizer.Add(self.element1, proportion = 0,
+                           flag = wx.EXPAND | wx.ALL, border = 1)
+       
+        self.panel.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+
+    def OnElement(self, event):
+        """!Select mapset given location name"""
+        location = event.GetString()
+        
+        if location:
+            dbase = grass.gisenv()['GISDBASE']
+            self.element1.SetItems(GetListOfMapsets(dbase, location, selectable = True))
+            self.element1.SetSelection(0)
+            mapset = self.element1.GetStringSelection()
+        
+        if location and mapset:
+            self.btnOK.Enable(True)
+        else:
+            self.btnOK.Enable(False)
+
+    def GetValues(self):
+        """!Get location, mapset"""
+        return (self.GetElement(), self.element1.GetStringSelection())
+    
+class MapsetDialog(ElementDialog):
+    """!Dialog used to select mapset"""
+    def __init__(self, parent, title = _("Select mapset in GRASS location"),
+                 location = None, id =  wx.ID_ANY):
+        ElementDialog.__init__(self, parent, title, label = _("Name of mapset:"))
+        if location:
+            self.SetTitle(self.GetTitle() + ' <%s>' % location)
+        else:
+            self.SetTitle(self.GetTitle() + ' <%s>' % grass.gisenv()['LOCATION_NAME'])
+        
+        self.element = gselect.MapsetSelect(parent = self.panel, id = wx.ID_ANY,
+                                            size = globalvar.DIALOG_GSELECT_SIZE)
+        
+        self.PostInit()
+        
+        self.__Layout()
+        self.SetMinSize(self.GetSize())
+
+    def __Layout(self):
+        """!Do layout"""
+        self.dataSizer.Add(self.element, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 1)
+        
+        self.panel.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+
+    def GetMapset(self):
+        return self.GetElement()
+    
+class NewVectorDialog(ElementDialog):
+    def __init__(self, parent, id = wx.ID_ANY, title = _('Create new vector map'),
+                 disableAdd = False, disableTable = False, showType = False,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, *kwargs):
+        """!Dialog for creating new vector map
+
+        @param parent parent window
+        @param id window id
+        @param title window title
+        @param disableAdd disable 'add layer' checkbox
+        @param disableTable disable 'create table' checkbox
+        @param showType True to show feature type selector (used for creating new empty OGR layers)
+        @param style window style
+        @param kwargs other argumentes for ElementDialog
+        
+        @return dialog instance       
+        """
+        ElementDialog.__init__(self, parent, title, label = _("Name for new vector map:"))
+        
+        self.element = Select(parent = self.panel, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
+                              type = 'vector', mapsets = [grass.gisenv()['MAPSET'],])
+        
+        # determine output format
+        if showType:
+            self.ftype = OgrTypeSelect(parent = self, panel = self.panel)
+        else:
+            self.ftype = None
+        
+        self.table = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                 label = _("Create attribute table"))
+        self.table.SetValue(True)
+        if disableTable:
+            self.table.Enable(False)
+        
+        if showType:
+            self.keycol = None
+        else:
+            self.keycol = wx.TextCtrl(parent = self.panel, id =  wx.ID_ANY,
+                                      size = globalvar.DIALOG_SPIN_SIZE)
+            self.keycol.SetValue(UserSettings.Get(group = 'atm', key = 'keycolumn', subkey = 'value'))
+            if disableTable:
+                self.keycol.Enable(False)
+        
+        self.addbox = wx.CheckBox(parent = self.panel,
+                                  label = _('Add created map into layer tree'), style = wx.NO_BORDER)
+        if disableAdd:
+            self.addbox.SetValue(True)
+            self.addbox.Enable(False)
+        else:
+            self.addbox.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
+
+        self.table.Bind(wx.EVT_CHECKBOX, self.OnTable)
+        
+        self.PostInit()
+        
+        self._layout()
+        self.SetMinSize(self.GetSize())
+        
+    def OnMapName(self, event):
+        """!Name for vector map layer given"""
+        self.OnElement(event)
+        
+    def OnTable(self, event):
+        if self.keycol:
+            self.keycol.Enable(event.IsChecked())
+        
+    def _layout(self):
+        """!Do layout"""
+        self.dataSizer.Add(item = self.element, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 1)
+        if self.ftype:
+            self.dataSizer.AddSpacer(1)
+            self.dataSizer.Add(item = self.ftype, proportion = 0,
+                               flag = wx.EXPAND | wx.ALL, border = 1)
+        
+        self.dataSizer.Add(item = self.table, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 1)
+        
+        if self.keycol:
+            keySizer = wx.BoxSizer(wx.HORIZONTAL)
+            keySizer.Add(item = wx.StaticText(parent = self.panel, label = _("Key column:")),
+                         proportion = 0,
+                         flag = wx.ALIGN_CENTER_VERTICAL)
+            keySizer.AddSpacer(10)
+            keySizer.Add(item = self.keycol, proportion = 0,
+                         flag = wx.ALIGN_RIGHT)
+            self.dataSizer.Add(item = keySizer, proportion = 1,
+                               flag = wx.EXPAND | wx.ALL, border = 1)
+            
+        self.dataSizer.AddSpacer(5)
+        
+        self.dataSizer.Add(item = self.addbox, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 1)
+        
+        self.panel.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+
+    def GetName(self, full = False):
+        """!Get name of vector map to be created
+
+        @param full True to get fully qualified name
+        """
+        name = self.GetElement()
+        if full:
+            if '@' in name:
+                return name
+            else:
+                return name + '@' + grass.gisenv()['MAPSET']
+        
+        return name.split('@', 1)[0]
+
+    def GetKey(self):
+        """!Get key column name"""
+        if self.keycol:
+            return self.keycol.GetValue()
+        return UserSettings.Get(group = 'atm', key = 'keycolumn', subkey = 'value')
+    
+    def IsChecked(self, key):
+        """!Get dialog properties
+
+        @param key window key ('add', 'table')
+
+        @return True/False
+        @return None on error
+        """
+        if key == 'add':
+            return self.addbox.IsChecked()
+        elif key == 'table':
+            return self.table.IsChecked()
+        
+        return None
+    
+    def GetFeatureType(self):
+        """!Get feature type for OGR
+
+        @return feature type as string
+        @return None for native format
+        """
+        if self.ftype:
+            return self.ftype.GetType()
+        
+        return None
+
+def CreateNewVector(parent, cmd, title = _('Create new vector map'),
+                    exceptMap = None, log = None,
+                    disableAdd = False, disableTable = False):
+    """!Create new vector map layer
+    
+    @param cmd (prog, **kwargs)
+    @param title window title
+    @param exceptMap list of maps to be excepted
+    @param log
+    @param disableAdd disable 'add layer' checkbox
+    @param disableTable disable 'create table' checkbox
+    
+    @return dialog instance
+    @return None on error
+    """
+    vExternalOut = grass.parse_command('v.external.out', flags = 'g')
+    isNative = vExternalOut['format'] == 'native'
+    if cmd[0] == 'v.edit' and not isNative:
+        showType = True
+    else:
+        showType = False
+    dlg = NewVectorDialog(parent, title = title,
+                          disableAdd = disableAdd, disableTable = disableTable,
+                          showType = showType)
+    
+    if dlg.ShowModal() != wx.ID_OK:
+        dlg.Destroy()
+        return None
+
+    outmap = dlg.GetName()
+    key    = dlg.GetKey()
+    if outmap == exceptMap:
+        GError(parent = parent,
+               message = _("Unable to create vector map <%s>.") % outmap)
+        dlg.Destroy()
+        return None
+    if dlg.table.IsEnabled() and not key:
+        GError(parent = parent,
+               message = _("Invalid or empty key column.\n"
+                           "Unable to create vector map <%s>.") % outmap)
+        dlg.Destroy()
+        return
+        
+    if outmap == '': # should not happen
+        dlg.Destroy()
+        return None
+
+    # update cmd -> output name defined
+    cmd[1][cmd[2]] = outmap
+    if showType:
+        cmd[1]['type'] = dlg.GetFeatureType()
+        
+    if isNative:
+        listOfVectors = grass.list_grouped('vect')[grass.gisenv()['MAPSET']]
+    else:
+        listOfVectors = RunCommand('v.external',
+                                   quiet = True,
+                                   parent = parent,
+                                   read = True,
+                                   flags = 'l',
+                                   dsn = vExternalOut['dsn']).splitlines()
+    
+    overwrite = False
+    if not UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled') and \
+            outmap in listOfVectors:
+        dlgOw = wx.MessageDialog(parent, message = _("Vector map <%s> already exists "
+                                                     "in the current mapset. "
+                                                     "Do you want to overwrite it?") % outmap,
+                                 caption = _("Overwrite?"),
+                                 style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+        if dlgOw.ShowModal() == wx.ID_YES:
+            overwrite = True
+        else:
+            dlgOw.Destroy()
+            dlg.Destroy()
+            return None
+        
+    if UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'):
+        overwrite = True
+        
+    ret = RunCommand(prog = cmd[0],
+                     parent = parent,
+                     overwrite = overwrite,
+                     **cmd[1])
+    if ret != 0:
+        dlg.Destroy()
+        return None
+    
+    if not isNative:
+        # create link for OGR layers
+        RunCommand('v.external',
+                   overwrite = overwrite,
+                   parent = parent,
+                   dsn = vExternalOut['dsn'],
+                   layer = outmap)
+        
+    # create attribute table
+    if dlg.table.IsEnabled() and dlg.table.IsChecked():
+        if isNative:
+            sql = 'CREATE TABLE %s (%s INTEGER)' % (outmap, key)
+            
+            RunCommand('db.connect',
+                       flags = 'c')
+            
+            Debug.msg(1, "SQL: %s" % sql)
+            RunCommand('db.execute',
+                       quiet = True,
+                       parent = parent,
+                       input = '-',
+                       stdin = sql)
+            
+            RunCommand('v.db.connect',
+                       quiet = True,
+                       parent = parent,
+                       map = outmap,
+                       table = outmap,
+                       key = key,
+                       layer = '1')
+        # TODO: how to deal with attribute tables for OGR layers?
+            
+    # return fully qualified map name
+    if '@' not in outmap:
+        outmap += '@' + grass.gisenv()['MAPSET']
+        
+    if log:
+        log.WriteLog(_("New vector map <%s> created") % outmap)
+        
+    return dlg
+
+class SavedRegion(wx.Dialog):
+    def __init__(self, parent, id = wx.ID_ANY, title = "", loadsave = 'load',
+                 **kwargs):
+        """!Loading and saving of display extents to saved region file
+
+        @param loadsave load or save region?
+        """
+        wx.Dialog.__init__(self, parent, id, title, **kwargs)
+
+        self.loadsave = loadsave
+        self.wind = ''
+        
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        label = wx.StaticText(parent = self, id = wx.ID_ANY)
+        box.Add(item = label, proportion = 0, flag = wx.ALIGN_CENTRE | wx.ALL, border = 5)
+        if loadsave == 'load':
+            label.SetLabel(_("Load region:"))
+            selection = Select(parent = self, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
+                               type = 'windows')
+        elif loadsave == 'save':
+            label.SetLabel(_("Save region:"))
+            selection = Select(parent = self, id = wx.ID_ANY, size = globalvar.DIALOG_GSELECT_SIZE,
+                               type = 'windows', mapsets  =  [grass.gisenv()['MAPSET']])
+        
+        box.Add(item = selection, proportion = 0, flag = wx.ALIGN_CENTRE | wx.ALL, border = 5)
+        selection.SetFocus()
+        selection.Bind(wx.EVT_TEXT, self.OnRegion)
+        
+        sizer.Add(item = box, proportion = 0, flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
+                  border = 5)
+        
+        line = wx.StaticLine(parent = self, id = wx.ID_ANY, size = (20, -1), style = wx.LI_HORIZONTAL)
+        sizer.Add(item = line, proportion = 0,
+                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, border = 5)
+        
+        btnsizer = wx.StdDialogButtonSizer()
+        
+        btn = wx.Button(parent = self, id = wx.ID_OK)
+        btn.SetDefault()
+        btnsizer.AddButton(btn)
+        
+        btn = wx.Button(parent = self, id = wx.ID_CANCEL)
+        btnsizer.AddButton(btn)
+        btnsizer.Realize()
+        
+        sizer.Add(item = btnsizer, proportion = 0, flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
+        
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+        self.Layout()
+        
+    def OnRegion(self, event):
+        self.wind = event.GetString()
+    
+class DecorationDialog(wx.Dialog):
+    """!Controls setting options and displaying/hiding map overlay
+    decorations
+    """
+    def __init__(self, parent, ovlId, title, cmd, name = None,
+                 pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE,
+                 checktxt = '', ctrltxt = ''):
+        
+        wx.Dialog.__init__(self, parent, wx.ID_ANY, title, pos, size, style)
+        
+        self.ovlId   = ovlId   # PseudoDC id
+        self.cmd     = cmd
+        self.name    = name    # overlay name
+        self.parent  = parent  # MapFrame
+        
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        self.chkbox = wx.CheckBox(parent = self, id = wx.ID_ANY, label = checktxt)
+        if self.parent.Map.GetOverlay(self.ovlId) is None:
+            self.chkbox.SetValue(True)
+        else:
+            self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
+        box.Add(item = self.chkbox, proportion = 0,
+                flag = wx.ALIGN_CENTRE|wx.ALL, border = 5)
+        sizer.Add(item = box, proportion = 0,
+                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
+
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        optnbtn = wx.Button(parent = self, id = wx.ID_ANY, label = _("Set options"))
+        box.Add(item = optnbtn, proportion = 0, flag = wx.ALIGN_CENTRE|wx.ALL, border = 5)
+        sizer.Add(item = box, proportion = 0,
+                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
+        if self.name == 'legend':
+            box = wx.BoxSizer(wx.HORIZONTAL)
+            resize = wx.ToggleButton(parent = self, id = wx.ID_ANY, label = _("Set size and position"))
+            resize.SetToolTipString(_("Click and drag on the map display to set legend"
+                                        " size and position and then press OK"))
+            resize.SetName('resize')
+            if self.parent.IsPaneShown('3d'):
+                resize.Disable()
+            box.Add(item = resize, proportion = 0, flag = wx.ALIGN_CENTRE|wx.ALL, border = 5)
+            sizer.Add(item = box, proportion = 0,
+                      flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
+
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        label = wx.StaticText(parent = self, id = wx.ID_ANY,
+                              label = _("Drag %s with mouse in pointer mode to position.\n"
+                                      "Double-click to change options." % ctrltxt))
+        if self.name == 'legend':
+            label.SetLabel(label.GetLabel() + _('\nDefine raster map name for legend in '
+                                                'properties dialog.'))
+        box.Add(item = label, proportion = 0,
+                flag = wx.ALIGN_CENTRE|wx.ALL, border = 5)
+        sizer.Add(item = box, proportion = 0,
+                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
+
+        line = wx.StaticLine(parent = self, id = wx.ID_ANY, size = (20,-1), style = wx.LI_HORIZONTAL)
+        sizer.Add(item = line, proportion = 0,
+                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, border = 5)
+
+        # buttons
+        btnsizer = wx.StdDialogButtonSizer()
+
+        self.btnOK = wx.Button(parent = self, id = wx.ID_OK)
+        self.btnOK.SetDefault()
+        if self.name == 'legend':
+            self.btnOK.Enable(False)
+        btnsizer.AddButton(self.btnOK)
+
+        btnCancel = wx.Button(parent = self, id = wx.ID_CANCEL)
+        btnsizer.AddButton(btnCancel)
+        btnsizer.Realize()
+
+        sizer.Add(item = btnsizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
+
+        #
+        # bindings
+        #
+        self.Bind(wx.EVT_BUTTON,   self.OnOptions, optnbtn)
+        if self.name == 'legend':
+            self.Bind(wx.EVT_TOGGLEBUTTON,   self.OnResize, resize)
+        self.Bind(wx.EVT_BUTTON,   self.OnCancel,  btnCancel)
+        self.Bind(wx.EVT_BUTTON,   self.OnOK,      self.btnOK)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+        # create overlay if doesn't exist
+        self._createOverlay()
+        
+        if len(self.parent.MapWindow.overlays[self.ovlId]['cmd']) > 1:
+            if name == 'legend':
+                mapName, found = GetLayerNameFromCmd(self.parent.MapWindow.overlays[self.ovlId]['cmd'])
+                if found:
+                    # enable 'OK' button
+                    self.btnOK.Enable()
+                    # set title
+                    self.SetTitle(_('Legend of raster map <%s>') % \
+                                      mapName)
+            
+        
+    def _createOverlay(self):
+        """!Creates overlay"""
+        if not self.parent.GetMap().GetOverlay(self.ovlId):
+            self.newOverlay = self.parent.Map.AddOverlay(id = self.ovlId, type = self.name,
+                                                         command = self.cmd,
+                                                         l_active = False, l_render = False, l_hidden = True)
+            prop = { 'layer' : self.newOverlay,
+                     'params' : None,
+                     'propwin' : None,
+                     'cmd' : self.cmd,
+                     'coords': (0, 0),
+                     'pdcType': 'image' }
+            self.parent.MapWindow2D.overlays[self.ovlId] = prop
+            if self.parent.MapWindow3D:
+                self.parent.MapWindow3D.overlays[self.ovlId] = prop
+                
+        else:
+            if self.parent.MapWindow.overlays[self.ovlId]['propwin'] == None:
+                return
+            
+            self.parent.MapWindow.overlays[self.ovlId]['propwin'].get_dcmd = self.GetOptData
+        
+    def OnOptions(self, event):
+        """!Sets option for decoration map overlays
+        """
+        if self.parent.MapWindow.overlays[self.ovlId]['propwin'] is None:
+            # build properties dialog
+            GUI(parent = self.parent).ParseCommand(cmd = self.cmd,
+                                                   completed = (self.GetOptData, self.name, ''))
+            
+        else:
+            if self.parent.MapWindow.overlays[self.ovlId]['propwin'].IsShown():
+                self.parent.MapWindow.overlays[self.ovlId]['propwin'].SetFocus()
+            else:
+                self.parent.MapWindow.overlays[self.ovlId]['propwin'].Show()
+        
+    def OnResize(self, event):
+        if self.FindWindowByName('resize').GetValue():
+            self.parent.MapWindow.SetCursor(self.parent.cursors["cross"])
+            self.parent.MapWindow.mouse['use'] = 'legend'
+            self.parent.MapWindow.mouse['box'] = 'box'
+            self.parent.MapWindow.pen = wx.Pen(colour = 'Black', width = 2, style = wx.SHORT_DASH)
+        else:
+            self.parent.MapWindow.SetCursor(self.parent.cursors["default"])
+            self.parent.MapWindow.mouse['use'] = 'pointer'
+            
+    def OnCancel(self, event):
+        """!Cancel dialog"""
+        if self.name == 'legend' and self.FindWindowByName('resize').GetValue():
+            self.FindWindowByName('resize').SetValue(False)
+            self.OnResize(None)
+            
+        self.parent.dialogs['barscale'] = None
+        if event and hasattr(self, 'newOverlay'):
+            self.parent.Map.DeleteOverlay(self.newOverlay)
+        self.Destroy()
+
+    def OnOK(self, event):
+        """!Button 'OK' pressed"""
+        # enable or disable overlay
+        self.parent.Map.GetOverlay(self.ovlId).SetActive(self.chkbox.IsChecked())
+        
+        # update map
+        if self.parent.IsPaneShown('3d'):
+            self.parent.MapWindow.UpdateOverlays()
+        
+        self.parent.MapWindow.UpdateMap()
+        
+        # close dialog
+        self.OnCancel(None)
+        
+    def GetOptData(self, dcmd, layer, params, propwin):
+        """!Process decoration layer data"""
+        # update layer data
+        if params:
+            self.parent.MapWindow.overlays[self.ovlId]['params'] = params
+        if dcmd:
+            self.parent.MapWindow.overlays[self.ovlId]['cmd'] = dcmd
+        self.parent.MapWindow.overlays[self.ovlId]['propwin'] = propwin
+
+        # change parameters for item in layers list in render.Map
+        # "Use mouse..." (-m) flag causes GUI freeze and is pointless here, trac #119
+        
+        try:
+            self.parent.MapWindow.overlays[self.ovlId]['cmd'].remove('-m')
+        except ValueError:
+            pass
+            
+        self.parent.Map.ChangeOverlay(id = self.ovlId, type = self.name,
+                                      command = self.parent.MapWindow.overlays[self.ovlId]['cmd'],
+                                      l_active = self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive(),
+                                      l_render = False, l_hidden = True)
+        if  self.name == 'legend':
+            if params and not self.btnOK.IsEnabled():
+                self.btnOK.Enable()
+            
+class TextLayerDialog(wx.Dialog):
+    """
+    Controls setting options and displaying/hiding map overlay decorations
+    """
+
+    def __init__(self, parent, ovlId, title, name = 'text',
+                 pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_DIALOG_STYLE):
+
+        wx.Dialog.__init__(self, parent, wx.ID_ANY, title, pos, size, style)
+        from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
+
+        self.ovlId = ovlId
+        self.parent = parent
+
+        if self.ovlId in self.parent.MapWindow.textdict.keys():
+            self.currText = self.parent.MapWindow.textdict[self.ovlId]['text']
+            self.currFont = self.parent.MapWindow.textdict[self.ovlId]['font']
+            self.currClr  = self.parent.MapWindow.textdict[self.ovlId]['color']
+            self.currRot  = self.parent.MapWindow.textdict[self.ovlId]['rotation']
+            self.currCoords = self.parent.MapWindow.textdict[self.ovlId]['coords']
+            self.currBB = self.parent.MapWindow.textdict[self.ovlId]['bbox']
+        else:
+            self.currClr = wx.BLACK
+            self.currText = ''
+            self.currFont = self.GetFont()
+            self.currRot = 0.0
+            self.currCoords = [10, 10]
+            self.currBB = wx.Rect()
+
+        self.sizer = wx.BoxSizer(wx.VERTICAL)
+        box = wx.GridBagSizer(vgap = 5, hgap = 5)
+
+        # show/hide
+        self.chkbox = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                  label = _('Show text object'))
+        if self.parent.Map.GetOverlay(self.ovlId) is None:
+            self.chkbox.SetValue(True)
+        else:
+            self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
+        box.Add(item = self.chkbox, span = (1,2),
+                flag = wx.ALIGN_LEFT|wx.ALL, border = 5,
+                pos = (0, 0))
+
+        # text entry
+        label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("Enter text:"))
+        box.Add(item = label,
+                flag = wx.ALIGN_CENTER_VERTICAL,
+                pos = (1, 0))
+
+        self.textentry = ExpandoTextCtrl(parent = self, id = wx.ID_ANY, value = "", size = (300,-1))
+        self.textentry.SetFont(self.currFont)
+        self.textentry.SetForegroundColour(self.currClr)
+        self.textentry.SetValue(self.currText)
+        # get rid of unneeded scrollbar when text box first opened
+        self.textentry.SetClientSize((300,-1))
+        
+        box.Add(item = self.textentry,
+                pos = (1, 1))
+
+        # rotation
+        label = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("Rotation:"))
+        box.Add(item = label,
+                flag = wx.ALIGN_CENTER_VERTICAL,
+                pos = (2, 0))
+        self.rotation = wx.SpinCtrl(parent = self, id = wx.ID_ANY, value = "", pos = (30, 50),
+                                    size = (75,-1), style = wx.SP_ARROW_KEYS)
+        self.rotation.SetRange(-360, 360)
+        self.rotation.SetValue(int(self.currRot))
+        box.Add(item = self.rotation,
+                flag = wx.ALIGN_RIGHT,
+                pos = (2, 1))
+
+        # font
+        fontbtn = wx.Button(parent = self, id = wx.ID_ANY, label = _("Set font"))
+        box.Add(item = fontbtn,
+                flag = wx.ALIGN_RIGHT,
+                pos = (3, 1))
+
+        self.sizer.Add(item = box, proportion = 1,
+                  flag = wx.ALL, border = 10)
+
+        # note
+        box = wx.BoxSizer(wx.HORIZONTAL)
+        label = wx.StaticText(parent = self, id = wx.ID_ANY,
+                              label = _("Drag text with mouse in pointer mode "
+                                      "to position.\nDouble-click to change options"))
+        box.Add(item = label, proportion = 0,
+                flag = wx.ALIGN_CENTRE | wx.ALL, border = 5)
+        self.sizer.Add(item = box, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER | wx.ALL, border = 5)
+
+        line = wx.StaticLine(parent = self, id = wx.ID_ANY,
+                             size = (20,-1), style = wx.LI_HORIZONTAL)
+        self.sizer.Add(item = line, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border = 5)
+
+        btnsizer = wx.StdDialogButtonSizer()
+
+        btn = wx.Button(parent = self, id = wx.ID_OK)
+        btn.SetDefault()
+        btnsizer.AddButton(btn)
+
+        btn = wx.Button(parent = self, id = wx.ID_CANCEL)
+        btnsizer.AddButton(btn)
+        btnsizer.Realize()
+
+        self.sizer.Add(item = btnsizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+
+        self.SetSizer(self.sizer)
+        self.sizer.Fit(self)
+
+        # bindings
+        self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.textentry)
+        self.Bind(wx.EVT_BUTTON,     self.OnSelectFont, fontbtn)
+        self.Bind(wx.EVT_TEXT,       self.OnText,       self.textentry)
+        self.Bind(wx.EVT_SPINCTRL,   self.OnRotation,   self.rotation)
+
+    def OnRefit(self, event):
+        """!Resize text entry to match text"""
+        self.sizer.Fit(self)
+
+    def OnText(self, event):
+        """!Change text string"""
+        self.currText = event.GetString()
+
+    def OnRotation(self, event):
+        """!Change rotation"""
+        self.currRot = event.GetInt()
+
+        event.Skip()
+
+    def OnSelectFont(self, event):
+        """!Change font"""
+        data = wx.FontData()
+        data.EnableEffects(True)
+        data.SetColour(self.currClr)         # set colour
+        data.SetInitialFont(self.currFont)
+
+        dlg = wx.FontDialog(self, data)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            data = dlg.GetFontData()
+            self.currFont = data.GetChosenFont()
+            self.currClr = data.GetColour()
+
+            self.textentry.SetFont(self.currFont)
+            self.textentry.SetForegroundColour(self.currClr)
+
+            self.Layout()
+
+        dlg.Destroy()
+
+    def GetValues(self):
+        """!Get text properties"""
+        return { 'text' : self.currText,
+                 'font' : self.currFont,
+                 'color' : self.currClr,
+                 'rotation' : self.currRot,
+                 'coords' : self.currCoords,
+                 'active' : self.chkbox.IsChecked() }
+
+class GroupDialog(wx.Dialog):
+    """!Dialog for creating/editing groups"""
+    def __init__(self, parent = None, defaultGroup = None, 
+                 title = _("Create or edit imagery groups"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+                     
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = title,
+                            style = style, **kwargs)
+                            
+        self.parent = parent
+        self.defaultGroup = defaultGroup
+        self.currentGroup = self.defaultGroup
+        self.groupChanged = False
+        
+        self.bodySizer = self._createDialogBody()
+        
+        # buttons
+        btnOk = wx.Button(parent = self, id = wx.ID_OK)
+        btnApply = wx.Button(parent = self, id = wx.ID_APPLY)
+        btnClose = wx.Button(parent = self, id = wx.ID_CANCEL)
+        
+        btnOk.SetToolTipString(_("Apply changes to selected group and close dialog"))
+        btnApply.SetToolTipString(_("Apply changes to selected group"))
+        btnClose.SetToolTipString(_("Close dialog, changes are not applied"))
+
+        btnOk.SetDefault()
+        
+        # sizers & do layout
+        # btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        # btnSizer.Add(item = btnClose, proportion = 0,
+        #              flag = wx.RIGHT | wx.ALIGN_RIGHT | wx.EXPAND, border = 5)
+        # btnSizer.Add(item = btnApply, proportion = 0,
+        #              flag = wx.LEFT, border = 5)
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(btnOk)
+        btnSizer.AddButton(btnApply)
+        btnSizer.AddButton(btnClose)
+        btnSizer.Realize()
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = self.bodySizer, proportion = 1,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10)
+        mainSizer.Add(item = wx.StaticLine(parent = self, id = wx.ID_ANY,
+                      style = wx.LI_HORIZONTAL), proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10) 
+        
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.ALL | wx.ALIGN_RIGHT, border = 10)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+        
+        btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
+        btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
+        btnClose.Bind(wx.EVT_BUTTON, self.OnClose)
+
+        # set dialog min size
+        self.SetMinSize(self.GetSize())
+        
+    def _createDialogBody(self):
+        bodySizer = wx.BoxSizer(wx.VERTICAL)
+    
+        # group selection
+        bodySizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
+                                           label = _("Select the group you want to edit or "
+                                                     "enter name of new group:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL | wx.TOP, border = 10)
+        self.groupSelect = Select(parent = self, type = 'group',
+                                  mapsets = [grass.gisenv()['MAPSET']],
+                                  size = globalvar.DIALOG_GSELECT_SIZE) # searchpath?
+            
+        bodySizer.Add(item = self.groupSelect, flag = wx.TOP | wx.EXPAND, border = 5)
+        
+        bodySizer.AddSpacer(10)
+        # layers in group
+        bodySizer.Add(item = wx.StaticText(parent = self, label = _("Layers in selected group:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM, border = 5)
+        
+        gridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
+        gridSizer.AddGrowableCol(0)
+        
+        self.layerBox = wx.ListBox(parent = self,  id = wx.ID_ANY, size = (-1, 150),
+                                   style = wx.LB_MULTIPLE | wx.LB_NEEDED_SB)
+        
+        gridSizer.Add(item = self.layerBox, pos = (0, 0), span = (2, 1), flag = wx.EXPAND)
+        
+        self.addLayer = wx.Button(self, id = wx.ID_ADD)
+        self.addLayer.SetToolTipString(_("Select map layers and add them to the list."))
+        gridSizer.Add(item = self.addLayer, pos = (0, 1))
+        
+        self.removeLayer = wx.Button(self, id = wx.ID_REMOVE)
+        self.removeLayer.SetToolTipString(_("Remove selected layer(s) from list."))
+        gridSizer.Add(item = self.removeLayer, pos = (1, 1))
+        
+        bodySizer.Add(item = gridSizer, proportion = 1, flag = wx.EXPAND)
+        
+        self.infoLabel = wx.StaticText(parent = self, id = wx.ID_ANY)
+        bodySizer.Add(item = self.infoLabel, 
+                      flag = wx.ALIGN_CENTER_VERTICAL | wx.TOP | wx.BOTTOM, border = 5)
+        
+        # bindings
+        self.groupSelect.GetTextCtrl().Bind(wx.EVT_TEXT, self.OnGroupSelected)
+        self.addLayer.Bind(wx.EVT_BUTTON, self.OnAddLayer)
+        self.removeLayer.Bind(wx.EVT_BUTTON, self.OnRemoveLayer)
+        
+        if self.defaultGroup:
+            self.groupSelect.SetValue(self.defaultGroup)
+        
+        return bodySizer
+        
+    def OnAddLayer(self, event):
+        """!Add new layer to listbox"""
+        dlg = MapLayersDialog(parent = self, title = _("Add selected map layers into group"),
+                              mapType = 'raster', selectAll = False,
+                              fullyQualified = True, showFullyQualified = False)
+        if dlg.ShowModal() != wx.ID_OK:
+            dlg.Destroy()
+            return
+        
+        layers = dlg.GetMapLayers()
+        for layer in layers:
+            if layer not in self.GetLayers():
+                self.layerBox.Append(layer)
+                self.groupChanged = True
+            
+    
+    def OnRemoveLayer(self, event):
+        """!Remove layer from listbox"""
+        while self.layerBox.GetSelections():
+            sel = self.layerBox.GetSelections()[0]
+            self.layerBox.Delete(sel)
+            self.groupChanged = True
+                
+    def GetLayers(self):
+        """!Get layers"""
+        return self.layerBox.GetItems()
+        
+    def OnGroupSelected(self, event):
+        """!Text changed in group selector"""
+        # callAfter must be called to close popup before other actions
+        wx.CallAfter(self.GroupSelected)
+        
+    def GroupSelected(self):
+        """!Group was selected, check if changes were apllied"""
+        group = self.GetSelectedGroup()
+        if self.groupChanged:
+            dlg = wx.MessageDialog(self, message = _("Group <%s> was changed, "
+                                                     "do you want to apply changes?") % self.currentGroup,
+                                   caption = _("Unapplied changes"),
+                                   style = wx.YES_NO | wx.ICON_QUESTION | wx.YES_DEFAULT)
+            if dlg.ShowModal() == wx.ID_YES:
+                self.ApplyChanges(showResult = True)
+                
+            dlg.Destroy()
+            
+            
+        
+        groups = self.GetExistGroups()
+        if group in groups:
+            self.ShowGroupLayers(self.GetGroupLayers(group))
+            
+        self.currentGroup = group
+        self.groupChanged = False
+        
+        self.ClearNotification()
+        
+    def ShowGroupLayers(self, mapList):
+        """!Show map layers in currently selected group"""
+        self.layerBox.Set(mapList)
+        
+        
+    def EditGroup(self, group):
+        """!Edit selected group"""
+        layersNew = self.GetLayers()
+        layersOld = self.GetGroupLayers(group)
+        
+        add = []
+        remove = []
+        for layerNew in layersNew:
+            if layerNew not in layersOld:
+                add.append(layerNew)
+                
+        for layerOld in layersOld:
+            if layerOld not in layersNew:
+                remove.append(layerOld)
+        
+        ret = None
+        if remove:
+            ret = RunCommand('i.group',
+                             parent = self,
+                             group = group,
+                             flags = 'r',
+                             input = ','.join(remove))
+                        
+        if add:
+            ret = RunCommand('i.group',
+                             parent = self,
+                             group = group,
+                             input = ','.join(add))
+                            
+        return ret
+        
+    def CreateNewGroup(self, group):
+        """!Create new group"""
+        layers = self.GetLayers()
+        ret = RunCommand('i.group',
+                         parent = self,
+                         group = group,
+                         input = layers)
+        return ret
+        
+                        
+    def GetExistGroups(self):
+        """!Returns existing groups in current mapset"""
+        return grass.list_grouped('group')[grass.gisenv()['MAPSET']]
+        
+    def ShowResult(self, group, returnCode, create):
+        """!Show if operation was successfull."""
+        group += '@' + grass.gisenv()['MAPSET']
+        if returnCode is None:
+            label = _("No changes to apply in group <%s>.") % group
+        elif returnCode == 0:
+            if create:
+                label = _("Group <%s> was successfully created.") % group
+            else:
+                label = _("Group <%s> was successfully changed.") % group
+        else:
+            if create:
+                label = _("Creating of new group <%s> failed.") % group
+            else:
+                label = _("Changing of group <%s> failed.") % group
+                
+        self.infoLabel.SetLabel(label)
+        wx.FutureCall(4000, self.ClearNotification)
+        
+    def GetSelectedGroup(self):
+        """!Return currently selected group (without mapset)"""
+        return self.groupSelect.GetValue().split('@')[0]
+        
+    def GetGroupLayers(self, group):
+        """!Get layers in group"""
+        res = RunCommand('i.group',
+                         parent = self,
+                         flags = 'g',
+                         group = group,
+                         read = True).strip()
+        if res.split('\n')[0]:
+            return res.split('\n')
+        return []
+        
+    def ClearNotification(self):
+        """!Clear notification string"""
+        self.infoLabel.SetLabel("")
+       
+    def ApplyChanges(self, showResult):
+        """!Create or edit group"""
+        group = self.currentGroup
+        if not group:
+            GMessage(parent = self,
+                     message = _("No group selected."))
+            return False
+        
+        groups = self.GetExistGroups()
+        if group in groups:
+            ret = self.EditGroup(group)
+            self.ShowResult(group = group, returnCode = ret, create = False)
+            
+        else:
+            ret = self.CreateNewGroup(group)
+            self.ShowResult(group = group, returnCode = ret, create = True)
+            
+        self.groupChanged = False
+        
+        return True
+        
+    def OnApply(self, event):
+        """!Apply changes"""
+        self.ApplyChanges(showResult = True)
+        
+    def OnOk(self, event):
+        """!Apply changes and close dialog"""
+        if self.ApplyChanges(showResult = False):
+            self.OnClose(event)
+        
+    def OnClose(self, event):
+        """!Close dialog"""
+        if not self.IsModal():
+            self.Destroy()
+        event.Skip()
+        
+class MapLayersDialog(wx.Dialog):
+    def __init__(self, parent, title, modeler = False,
+                 mapType = None, selectAll = True, fullyQualified = True, showFullyQualified = True, 
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        """!Dialog for selecting map layers (raster, vector)
+        
+        @param mapType type of map (if None: raster, vector, 3d raster, if one only: selects it and disables selection)
+        @param selectAll all/none maps should be selected by default
+        @param fullyQualified True if dialog should return full map names by default
+        @param showFullyQualified True to show 'fullyQualified' checkbox, otherwise hide it
+        """
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = title,
+                           style = style, **kwargs)
+        
+        self.parent = parent # GMFrame or ?
+        self.mapType = mapType
+        self.selectAll = selectAll
+        
+        # dialog body
+        self.bodySizer = self._createDialogBody()
+        # update list of layer to be loaded
+        self.map_layers = [] # list of map layers (full list type/mapset)
+        self.LoadMapLayers(self.GetLayerType(cmd = True),
+                           self.mapset.GetStringSelection())
+        
+        self.fullyQualified = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                          label = _("Use fully-qualified map names"))
+        self.fullyQualified.SetValue(fullyQualified)
+        self.fullyQualified.Show(showFullyQualified)
+
+        self.dseries = None
+        if modeler:
+            self.dseries = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                       label = _("Dynamic series (%s)") % 'g.mlist')
+            self.dseries.SetValue(False)
+        
+        # buttons
+        btnCancel = wx.Button(parent = self, id = wx.ID_CANCEL)
+        btnOk = wx.Button(parent = self, id = wx.ID_OK)
+        btnOk.SetDefault()
+        
+        # sizers & do layout
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(btnCancel)
+        btnSizer.AddButton(btnOk)
+        btnSizer.Realize()
+        
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(item = self.bodySizer, proportion = 1,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+        mainSizer.Add(item = self.fullyQualified, proportion = 0,
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
+        if self.dseries:
+            mainSizer.Add(item = self.dseries, proportion = 0,
+                          flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
+        
+        mainSizer.Add(item = btnSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+        # set dialog min size
+        self.SetMinSize(self.GetSize())
+        
+    def _createDialogBody(self):
+        bodySizer = wx.GridBagSizer(vgap = 3, hgap = 3)
+        bodySizer.AddGrowableCol(1)
+        bodySizer.AddGrowableRow(3)
+        
+        # layer type
+        bodySizer.Add(item = wx.StaticText(parent = self, label = _("Map type:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (0,0))
+        
+        self.layerType = wx.Choice(parent = self, id = wx.ID_ANY,
+                                   choices = [_('raster'), _('3D raster'), _('vector')], size = (100,-1))
+        
+        if self.mapType:
+            self.layerType.SetStringSelection(self.mapType)
+            self.layerType.Disable()
+        else:
+            self.layerType.SetSelection(0)
+            
+        bodySizer.Add(item = self.layerType,
+                      pos = (0,1))
+        
+        # select toggle
+        self.toggle = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                  label = _("Select toggle"))
+        self.toggle.SetValue(self.selectAll)
+        bodySizer.Add(item = self.toggle,
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (0,2))
+        
+        # mapset filter
+        bodySizer.Add(item = wx.StaticText(parent = self, label = _("Mapset:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (1,0))
+        
+        self.mapset = MapsetSelect(parent = self, searchPath = True)
+        self.mapset.SetStringSelection(grass.gisenv()['MAPSET'])
+        bodySizer.Add(item = self.mapset,
+                      pos = (1,1), span = (1, 2))
+        
+        # map name filter
+        bodySizer.Add(item = wx.StaticText(parent = self, label = _("Pattern:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL,
+                      pos = (2,0))
+        
+        self.filter = wx.TextCtrl(parent = self, id = wx.ID_ANY,
+                                  value = "",
+                                  size = (250,-1))
+        bodySizer.Add(item = self.filter,
+                      flag = wx.EXPAND,
+                      pos = (2,1), span = (1, 2))
+        
+        # layer list 
+        bodySizer.Add(item = wx.StaticText(parent = self, label = _("List of maps:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_TOP,
+                      pos = (3,0))
+        self.layers = wx.CheckListBox(parent = self, id = wx.ID_ANY,
+                                      size = (250, 100),
+                                      choices = [])
+        bodySizer.Add(item = self.layers,
+                      flag = wx.EXPAND,
+                      pos = (3,1), span = (1, 2))
+        
+        # bindings
+        self.layerType.Bind(wx.EVT_CHOICE, self.OnChangeParams)
+        self.mapset.Bind(wx.EVT_COMBOBOX, self.OnChangeParams)
+        self.layers.Bind(wx.EVT_RIGHT_DOWN, self.OnMenu)
+        self.filter.Bind(wx.EVT_TEXT, self.OnFilter)
+        self.toggle.Bind(wx.EVT_CHECKBOX, self.OnToggle)
+        
+        return bodySizer
+
+    def LoadMapLayers(self, type, mapset):
+        """!Load list of map layers
+
+        @param type layer type ('raster' or 'vector')
+        @param mapset mapset name
+        """
+        self.map_layers = grass.mlist_grouped(type = type)[mapset]
+        self.layers.Set(self.map_layers)
+        
+        # check all items by default
+        for item in range(self.layers.GetCount()):
+            
+            self.layers.Check(item, check = self.selectAll)
+        
+    def OnChangeParams(self, event):
+        """!Filter parameters changed by user"""
+        # update list of layer to be loaded
+        self.LoadMapLayers(self.GetLayerType(cmd = True),
+                           self.mapset.GetStringSelection())
+        
+        event.Skip()
+        
+    def OnMenu(self, event):
+        """!Table description area, context menu"""
+        if not hasattr(self, "popupID1"):
+            self.popupDataID1 = wx.NewId()
+            self.popupDataID2 = wx.NewId()
+            self.popupDataID3 = wx.NewId()
+
+            self.Bind(wx.EVT_MENU, self.OnSelectAll,    id = self.popupDataID1)
+            self.Bind(wx.EVT_MENU, self.OnSelectInvert, id = self.popupDataID2)
+            self.Bind(wx.EVT_MENU, self.OnDeselectAll,  id = self.popupDataID3)
+        
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupDataID1, _("Select all"))
+        menu.Append(self.popupDataID2, _("Invert selection"))
+        menu.Append(self.popupDataID3, _("Deselect all"))
+        
+        self.PopupMenu(menu)
+        menu.Destroy()
+        
+    def OnSelectAll(self, event):
+        """!Select all map layer from list"""
+        for item in range(self.layers.GetCount()):
+            self.layers.Check(item, True)
+        
+    def OnSelectInvert(self, event):
+        """!Invert current selection"""
+        for item in range(self.layers.GetCount()):
+            if self.layers.IsChecked(item):
+                self.layers.Check(item, False)
+            else:
+                self.layers.Check(item, True)
+        
+    def OnDeselectAll(self, event):
+        """!Select all map layer from list"""
+        for item in range(self.layers.GetCount()):
+            self.layers.Check(item, False)
+        
+    def OnFilter(self, event):
+        """!Apply filter for map names"""
+        if len(event.GetString()) == 0:
+           self.layers.Set(self.map_layers) 
+           return 
+        
+        list = []
+        for layer in self.map_layers:
+            try:
+                if re.compile('^' + event.GetString()).search(layer):
+                    list.append(layer)
+            except:
+                pass
+        
+        self.layers.Set(list)
+        self.OnSelectAll(None)
+        
+        event.Skip()
+        
+    def OnToggle(self, event):
+        """!Select toggle (check or uncheck all layers)"""
+        check = event.Checked()
+        for item in range(self.layers.GetCount()):
+            self.layers.Check(item, check)
+        
+        event.Skip()
+        
+    def GetMapLayers(self):
+        """!Return list of checked map layers"""
+        layerNames = []
+        for indx in self.layers.GetSelections():
+            # layers.append(self.layers.GetStringSelec(indx))
+            pass
+
+        fullyQualified = self.fullyQualified.IsChecked()
+        mapset = self.mapset.GetStringSelection()
+        for item in range(self.layers.GetCount()):
+            if not self.layers.IsChecked(item):
+                continue
+            if fullyQualified:
+                layerNames.append(self.layers.GetString(item) + '@' + mapset)
+            else:
+                layerNames.append(self.layers.GetString(item))
+        
+        return layerNames
+    
+    def GetLayerType(self, cmd = False):
+        """!Get selected layer type
+
+        @param cmd True for g.mlist
+        """
+        if not cmd:
+            return self.layerType.GetStringSelection()
+        
+        sel = self.layerType.GetSelection()
+        if sel == 0:
+            ltype = 'rast'
+        elif sel == 1:
+            ltype = 'rast3d'
+        else:
+            ltype = 'vect'
+        
+        return ltype
+
+    def GetDSeries(self):
+        """!Used by modeler only
+
+        @return g.mlist command
+        """
+        if not self.dseries or not self.dseries.IsChecked():
+            return ''
+        
+        cond = 'map in `g.mlist type=%s ' % self.GetLayerType(cmd = True)
+        patt = self.filter.GetValue()
+        if patt:
+            cond += 'pattern=%s ' % patt
+        cond += 'mapset=%s`' % self.mapset.GetStringSelection()
+        
+        return cond
+    
+class ImportDialog(wx.Dialog):
+    """!Dialog for bulk import of various data (base class)"""
+    def __init__(self, parent, itype,
+                 id = wx.ID_ANY, title = _("Multiple import"),
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
+        self.parent = parent    # GMFrame 
+        self.importType = itype
+        self.options = dict()   # list of options
+        
+        self.commandId = -1  # id of running command
+        
+        wx.Dialog.__init__(self, parent, id, title, style = style,
+                           name = "MultiImportDialog")
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.layerBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                     label = _(" List of %s layers ") % self.importType.upper())
+        
+        #
+        # list of layers
+        #
+        columns = [_('Layer id'),
+                   _('Layer name'),
+                   _('Name for GRASS map (editable)')]
+        if itype == 'ogr':
+            columns.insert(2, _('Feature type'))
+        self.list = LayersList(parent = self.panel, columns = columns)
+        self.list.LoadData()
+
+        self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                      label = "%s" % _("Options"))
+        
+        cmd = self._getCommand()
+        task = gtask.parse_interface(cmd)
+        for f in task.get_options()['flags']:
+            name = f.get('name', '')
+            desc = f.get('label', '')
+            if not desc:
+                desc = f.get('description', '')
+            if not name and not desc:
+                continue
+            if cmd == 'r.in.gdal' and name not in ('o', 'e', 'l', 'k'):
+                continue
+            elif cmd == 'r.external' and name not in ('o', 'e', 'r', 'h', 'v'):
+                continue
+            elif cmd == 'v.in.ogr' and name not in ('c', 'z', 't', 'o', 'r', 'e', 'w'):
+                continue
+            elif cmd == 'v.external' and name not in ('b'):
+                continue
+            elif cmd == 'v.in.dxf' and name not in ('e', 't', 'b', 'f', 'i'):
+                continue
+            self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                             label = desc)
+        
+        
+        self.overwrite = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                     label = _("Allow output files to overwrite existing files"))
+        self.overwrite.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
+        
+        self.add = wx.CheckBox(parent = self.panel, id = wx.ID_ANY)
+        
+        #
+        # buttons
+        #
+        # cancel
+        self.btn_cancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.btn_cancel.SetToolTipString(_("Close dialog"))
+        self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
+        # run
+        self.btn_run = wx.Button(parent = self.panel, id = wx.ID_OK, label = _("&Import"))
+        self.btn_run.SetToolTipString(_("Import selected layers"))
+        self.btn_run.SetDefault()
+        self.btn_run.Enable(False)
+        self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
+        # run command dialog
+        self.btn_cmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                 label = _("Command dialog"))
+        self.btn_cmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
+        
+    def doLayout(self):
+        """!Do layout"""
+        dialogSizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # dsn input
+        dialogSizer.Add(item = self.dsnInput, proportion = 0,
+                        flag = wx.EXPAND)
+        
+        #
+        # list of DXF layers
+        #
+        layerSizer = wx.StaticBoxSizer(self.layerBox, wx.HORIZONTAL)
+
+        layerSizer.Add(item = self.list, proportion = 1,
+                      flag = wx.ALL | wx.EXPAND, border = 5)
+        
+        dialogSizer.Add(item = layerSizer, proportion = 1,
+                        flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
+
+        # options
+        optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
+        for key in self.options.keys():
+            optionSizer.Add(item = self.options[key], proportion = 0)
+            
+        dialogSizer.Add(item = optionSizer, proportion = 0,
+                        flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
+        
+        dialogSizer.Add(item = self.overwrite, proportion = 0,
+                        flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+        
+        dialogSizer.Add(item = self.add, proportion = 0,
+                        flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+        
+        #
+        # buttons
+        #
+        btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+        
+        btnsizer.Add(item = self.btn_cmd, proportion = 0,
+                     flag = wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        
+        btnsizer.Add(item = self.btn_cancel, proportion = 0,
+                     flag = wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        
+        btnsizer.Add(item = self.btn_run, proportion = 0,
+                     flag = wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        
+        dialogSizer.Add(item = btnsizer, proportion = 0,
+                        flag = wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.ALIGN_RIGHT,
+                        border = 10)
+        
+        # dialogSizer.SetSizeHints(self.panel)
+        self.panel.SetAutoLayout(True)
+        self.panel.SetSizer(dialogSizer)
+        dialogSizer.Fit(self.panel)
+        
+        # auto-layout seems not work here - FIXME
+        size = wx.Size(globalvar.DIALOG_GSELECT_SIZE[0] + 225, 550)
+        self.SetMinSize(size)
+        self.SetSize((size.width, size.height + 100))
+        # width = self.GetSize()[0]
+        # self.list.SetColumnWidth(col = 1, width = width / 2 - 50)
+        self.Layout()
+
+    def _getCommand(self):
+        """!Get command"""
+        return ''
+    
+    def OnCancel(self, event = None):
+        """!Close dialog"""
+        self.Close()
+
+    def OnRun(self, event):
+        """!Import/Link data (each layes as separate vector map)"""
+        pass
+
+    def OnCmdDialog(self, event):
+        """!Show command dialog"""
+        pass
+    
+    def AddLayers(self, returncode, cmd = None):
+        """!Add imported/linked layers into layer tree"""
+        self.commandId += 1
+        
+        if not self.add.IsChecked() or returncode != 0:
+            return
+        
+        maptree = self.parent.curr_page.maptree
+        
+        layer, output = self.list.GetLayers()[self.commandId]
+        
+        if '@' not in output:
+            name = output + '@' + grass.gisenv()['MAPSET']
+        else:
+            name = output
+        
+        # add imported layers into layer tree
+        if self.importType == 'gdal':
+            cmd = ['d.rast',
+                   'map=%s' % name]
+            if UserSettings.Get(group = 'cmd', key = 'rasterOverlay', subkey = 'enabled'):
+                cmd.append('-o')
+                
+            item = maptree.AddLayer(ltype = 'raster',
+                                    lname = name, lchecked = False,
+                                    lcmd = cmd)
+        else:
+            item = maptree.AddLayer(ltype = 'vector',
+                                    lname = name, lchecked = False,
+                                    lcmd = ['d.vect',
+                                            'map=%s' % name])
+        
+        maptree.mapdisplay.MapWindow.ZoomToMap()
+        
+    def OnAbort(self, event):
+        """!Abort running import
+
+        @todo not yet implemented
+        """
+        pass
+
+class GdalImportDialog(ImportDialog):
+    def __init__(self, parent, ogr = False, link = False):
+        """!Dialog for bulk import of various raster/vector data
+
+        @param parent parent window
+        @param ogr True for OGR (vector) otherwise GDAL (raster)
+        @param link True for linking data otherwise importing data
+        """
+        self.link = link
+        self.ogr  = ogr
+        
+        if ogr:
+            ImportDialog.__init__(self, parent, itype = 'ogr')
+            if link:
+                self.SetTitle(_("Link external vector data"))
+            else:
+                self.SetTitle(_("Import vector data"))
+        else:
+            ImportDialog.__init__(self, parent, itype = 'gdal') 
+            if link:
+                self.SetTitle(_("Link external raster data"))
+            else:
+                self.SetTitle(_("Import raster data"))
+        
+        self.dsnInput = GdalSelect(parent = self, panel = self.panel, ogr = ogr)
+        
+        if link:
+            self.add.SetLabel(_("Add linked layers into layer tree"))
+        else:
+            self.add.SetLabel(_("Add imported layers into layer tree"))
+        
+        self.add.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
+
+        if link:
+            self.btn_run.SetLabel(_("&Link"))
+            self.btn_run.SetToolTipString(_("Link selected layers"))
+            if ogr:
+                self.btn_cmd.SetToolTipString(_('Open %s dialog') % 'v.external')
+            else:
+                self.btn_cmd.SetToolTipString(_('Open %s dialog') % 'r.external')
+        else:
+            self.btn_run.SetLabel(_("&Import"))
+            self.btn_run.SetToolTipString(_("Import selected layers"))
+            if ogr:
+                self.btn_cmd.SetToolTipString(_('Open %s dialog') % 'v.in.ogr')
+            else:
+                self.btn_cmd.SetToolTipString(_('Open %s dialog') % 'r.in.gdal')
+        
+        self.doLayout()
+
+    def OnRun(self, event):
+        """!Import/Link data (each layes as separate vector map)"""
+        data = self.list.GetLayers()
+        
+        # hide dialog
+        self.Hide()
+        
+        dsn = self.dsnInput.GetDsn()
+        ext = self.dsnInput.GetFormatExt()
+            
+        for layer, output in data:
+            if self.importType == 'ogr':
+                if ext and layer.rfind(ext) > -1:
+                    layer = layer.replace('.' + ext, '')
+                if self.link:
+                    cmd = ['v.external',
+                           'dsn=%s' % dsn,
+                           'output=%s' % output,
+                           'layer=%s' % layer]
+                else:
+                    cmd = ['v.in.ogr',
+                           'dsn=%s' % dsn,
+                           'layer=%s' % layer,
+                           'output=%s' % output]
+            else: # gdal
+                if self.dsnInput.GetType() == 'dir':
+                    idsn = os.path.join(dsn, layer)
+                else:
+                    idsn = dsn
+                
+                if self.link:
+                    cmd = ['r.external',
+                           'input=%s' % idsn,
+                           'output=%s' % output]
+                else:
+                    cmd = ['r.in.gdal',
+                           'input=%s' % idsn,
+                           'output=%s' % output]
+            
+            if self.overwrite.IsChecked():
+                cmd.append('--overwrite')
+            
+            for key in self.options.keys():
+                if self.options[key].IsChecked():
+                    cmd.append('-%s' % key)
+            
+            if UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'):
+                cmd.append('--overwrite')
+            
+            # run in Layer Manager
+            self.parent.goutput.RunCmd(cmd, switchPage = True,
+                                       onDone = self.AddLayers)
+        
+        self.OnCancel()
+
+    def _getCommand(self):
+        """!Get command"""
+        if self.link:
+            if self.ogr:
+                return 'v.external'
+            else:
+                return 'r.external'
+        else:
+            if self.ogr:
+                return 'v.in.ogr'
+            else:
+                return 'r.in.gdal'
+        
+        return ''
+    
+    def OnCmdDialog(self, event):
+        """!Show command dialog"""
+        name = self._getCommand()
+        GUI(parent = self, modal = False).ParseCommand(cmd = [name])
+
+class GdalOutputDialog(wx.Dialog):
+    def __init__(self, parent, id = wx.ID_ANY, ogr = False,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, *kwargs):
+        """!Dialog for setting output format for rasters/vectors
+
+        @param parent parent window
+        @param id window id
+        @param ogr True for OGR (vector) otherwise GDAL (raster)
+        @param style window style
+        @param *kwargs other wx.Dialog's arguments
+        """
+        self.parent = parent # GMFrame 
+        self.ogr = ogr
+        wx.Dialog.__init__(self, parent, id = id, style = style, *kwargs)
+        if self.ogr:
+            self.SetTitle(_("Define output format for vector data"))
+        else:
+            self.SetTitle(_("Define output format for raster data"))
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+
+        # buttons
+        self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                label = _("Command dialog"))
+        self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.btnCancel.SetToolTipString(_("Close dialog"))
+        self.btnOk = wx.Button(parent = self.panel, id = wx.ID_OK)
+        self.btnOk.SetToolTipString(_("Set external format and close dialog"))
+        self.btnOk.SetDefault()
+        self.btnOk.Enable(False)
+        
+        self.dsnInput = GdalSelect(parent = self, panel = self.panel,
+                                   ogr = ogr,
+                                   exclude = ['file', 'protocol'], dest = True)
+        
+        self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
+        self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOk)
+        
+        self._layout()
+
+    def _layout(self):
+        dialogSizer = wx.BoxSizer(wx.VERTICAL)
+        
+        dialogSizer.Add(item = self.dsnInput, proportion = 0,
+                        flag = wx.EXPAND)
+
+        btnSizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+        btnSizer.Add(item = self.btnCmd, proportion = 0,
+                     flag = wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        btnSizer.Add(item = self.btnCancel, proportion = 0,
+                     flag = wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        btnSizer.Add(item = self.btnOk, proportion = 0,
+                     flag = wx.RIGHT | wx.ALIGN_CENTER,
+                     border = 10)
+        
+        dialogSizer.Add(item = btnSizer, proportion = 0,
+                        flag = wx.ALIGN_CENTER_VERTICAL | wx.BOTTOM | wx.TOP | wx.ALIGN_RIGHT,
+                        border = 10)
+        
+        self.panel.SetAutoLayout(True)
+        self.panel.SetSizer(dialogSizer)
+        dialogSizer.Fit(self.panel)
+
+        size = wx.Size(globalvar.DIALOG_GSELECT_SIZE[0] + 225, self.GetBestSize()[1])
+        self.SetMinSize(size)
+        self.SetSize((size.width, size.height))
+        self.Layout()
+        
+    def OnCmdDialog(self, event):
+        GUI(parent = self, modal = True).ParseCommand(cmd = ['v.external.out'])
+    
+    def OnCancel(self, event):
+        self.Destroy()
+        
+    def OnOK(self, event):
+        if self.dsnInput.GetType() == 'native':
+            RunCommand('v.external.out',
+                       parent = self,
+                       flags = 'r')
+        else:
+            dsn = self.dsnInput.GetDsn()
+            frmt = self.dsnInput.GetFormat()
+            options = self.dsnInput.GetOptions()
+            
+            RunCommand('v.external.out',
+                       parent = self,
+                       dsn = dsn, format = frmt,
+                       options = options)
+        self.Close()
+        
+class DxfImportDialog(ImportDialog):
+    """!Dialog for bulk import of DXF layers""" 
+    def __init__(self, parent):
+        ImportDialog.__init__(self, parent, itype = 'dxf',
+                              title = _("Import DXF layers"))
+        
+        self.dsnInput = filebrowse.FileBrowseButton(parent = self.panel, id = wx.ID_ANY, 
+                                                    size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
+                                                    dialogTitle = _('Choose DXF file to import'),
+                                                    buttonText = _('Browse'),
+                                                    startDirectory = os.getcwd(), fileMode = 0,
+                                                    changeCallback = self.OnSetDsn,
+                                                    fileMask = "DXF File (*.dxf)|*.dxf")
+        
+        self.add.SetLabel(_("Add imported layers into layer tree"))
+        
+        self.add.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
+        
+        self.doLayout()
+
+    def _getCommand(self):
+        """!Get command"""
+        return 'v.in.dxf'
+    
+    def OnRun(self, event):
+        """!Import/Link data (each layes as separate vector map)"""
+        data = self.list.GetLayers()
+        
+        # hide dialog
+        self.Hide()
+        
+        inputDxf = self.dsnInput.GetValue()
+        
+        for layer, output in data:
+            cmd = ['v.in.dxf',
+                   'input=%s' % inputDxf,
+                   'layers=%s' % layer,
+                   'output=%s' % output]
+
+            for key in self.options.keys():
+                if self.options[key].IsChecked():
+                    cmd.append('-%s' % key)
+            
+            if self.overwrite.IsChecked() or \
+                    UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'):
+                cmd.append('--overwrite')
+            
+            # run in Layer Manager
+            self.parent.goutput.RunCmd(cmd, switchPage = True,
+                                       onDone = self.AddLayers)
+        
+        self.OnCancel()
+
+    def OnSetDsn(self, event):
+        """!Input DXF file defined, update list of layer widget"""
+        path = event.GetString()
+        if not path:
+            return 
+        
+        data = list()        
+        ret = RunCommand('v.in.dxf',
+                         quiet = True,
+                         parent = self,
+                         read = True,
+                         flags = 'l',
+                         input = path)
+        if not ret:
+            self.list.LoadData()
+            self.btn_run.Enable(False)
+            return
+            
+        for line in ret.splitlines():
+            layerId = line.split(':')[0].split(' ')[1]
+            layerName = line.split(':')[1].strip()
+            grassName = GetValidLayerName(layerName)
+            data.append((layerId, layerName.strip(), grassName.strip()))
+        
+        self.list.LoadData(data)
+        if len(data) > 0:
+            self.btn_run.Enable(True)
+        else:
+            self.btn_run.Enable(False)
+
+    def OnCmdDialog(self, event):
+        """!Show command dialog"""
+        GUI(parent = self, modal = True).ParseCommand(cmd = ['v.in.dxf'])
+                
+class LayersList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin,
+                 listmix.CheckListCtrlMixin, listmix.TextEditMixin):
+    """!List of layers to be imported (dxf, shp...)"""
+    def __init__(self, parent, columns, log = None):
+        self.parent = parent
+        
+        wx.ListCtrl.__init__(self, parent, wx.ID_ANY,
+                             style = wx.LC_REPORT)
+        listmix.CheckListCtrlMixin.__init__(self)
+        self.log = log
+        
+        # setup mixins
+        listmix.ListCtrlAutoWidthMixin.__init__(self)
+        listmix.TextEditMixin.__init__(self)
+        
+        for i in range(len(columns)):
+            self.InsertColumn(i, columns[i])
+        
+        if len(columns) == 3:
+            width = (65, 200)
+        else:
+            width = (65, 180, 110)
+        
+        for i in range(len(width)):
+            self.SetColumnWidth(col = i, width = width[i])
+        
+        self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnPopupMenu) #wxMSW
+        self.Bind(wx.EVT_RIGHT_UP,            self.OnPopupMenu) #wxGTK
+
+    def LoadData(self, data = None):
+        """!Load data into list"""
+        if data is None:
+            return
+        
+        self.DeleteAllItems()
+        for item in data:
+            index = self.InsertStringItem(sys.maxint, str(item[0]))
+            for i in range(1, len(item)):
+                self.SetStringItem(index, i, "%s" % str(item[i]))
+        
+        # check by default only on one item
+        if len(data) == 1:
+            self.CheckItem(index, True)
+        
+    def OnPopupMenu(self, event):
+        """!Show popup menu"""
+        if self.GetItemCount() < 1:
+            return
+        
+        if not hasattr(self, "popupDataID1"):
+            self.popupDataID1 = wx.NewId()
+            self.popupDataID2 = wx.NewId()
+            
+            self.Bind(wx.EVT_MENU, self.OnSelectAll,  id = self.popupDataID1)
+            self.Bind(wx.EVT_MENU, self.OnSelectNone, id = self.popupDataID2)
+        
+        # generate popup-menu
+        menu = wx.Menu()
+        menu.Append(self.popupDataID1, _("Select all"))
+        menu.Append(self.popupDataID2, _("Deselect all"))
+        
+        self.PopupMenu(menu)
+        menu.Destroy()
+
+    def OnSelectAll(self, event):
+        """!Select all items"""
+        item = -1
+        
+        while True:
+            item = self.GetNextItem(item)
+            if item == -1:
+                break
+            self.CheckItem(item, True)
+        
+        event.Skip()
+        
+    def OnSelectNone(self, event):
+        """!Deselect items"""
+        item = -1
+        
+        while True:
+            item = self.GetNextItem(item, wx.LIST_STATE_SELECTED)
+            if item == -1:
+                break
+            self.CheckItem(item, False)
+        
+        event.Skip()
+        
+    def OnLeftDown(self, event):
+        """!Allow editing only output name
+        
+        Code taken from TextEditMixin class.
+        """
+        x, y = event.GetPosition()
+        
+        colLocs = [0]
+        loc = 0
+        for n in range(self.GetColumnCount()):
+            loc = loc + self.GetColumnWidth(n)
+            colLocs.append(loc)
+        
+        col = bisect(colLocs, x + self.GetScrollPos(wx.HORIZONTAL)) - 1
+        
+        if col == self.GetColumnCount() - 1:
+            listmix.TextEditMixin.OnLeftDown(self, event)
+        else:
+            event.Skip()
+        
+    def GetLayers(self):
+        """!Get list of layers (layer name, output name)"""
+        data = []
+        item = -1
+        while True:
+            item = self.GetNextItem(item)
+            if item == -1:
+                break
+            if not self.IsChecked(item):
+                continue
+            # layer / output name
+            data.append((self.GetItem(item, 1).GetText(),
+                         self.GetItem(item, self.GetColumnCount() - 1).GetText()))
+        
+        return data
+
+class SetOpacityDialog(wx.Dialog):
+    """!Set opacity of map layers"""
+    def __init__(self, parent, id = wx.ID_ANY, title = _("Set Map Layer Opacity"),
+                 size = wx.DefaultSize, pos = wx.DefaultPosition,
+                 style = wx.DEFAULT_DIALOG_STYLE, opacity = 100):
+
+        self.parent = parent    # GMFrame
+        self.opacity = opacity  # current opacity
+
+        super(SetOpacityDialog, self).__init__(parent, id = id, pos = pos,
+                                               size = size, style = style, title = title)
+
+        panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        box = wx.GridBagSizer(vgap = 5, hgap = 5)
+        self.value = wx.Slider(panel, id = wx.ID_ANY, value = self.opacity,
+                               style = wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | \
+                                   wx.SL_TOP | wx.SL_LABELS,
+                               minValue = 0, maxValue = 100,
+                               size = (350, -1))
+
+        box.Add(item = self.value,
+                flag = wx.ALIGN_CENTRE, pos = (0, 0), span = (1, 2))
+        box.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                   label = _("transparent")),
+                pos = (1, 0))
+        box.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
+                                   label = _("opaque")),
+                flag = wx.ALIGN_RIGHT,
+                pos = (1, 1))
+
+        sizer.Add(item = box, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
+
+        line = wx.StaticLine(parent = panel, id = wx.ID_ANY,
+                             style = wx.LI_HORIZONTAL)
+        sizer.Add(item = line, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
+
+        # buttons
+        btnsizer = wx.StdDialogButtonSizer()
+
+        btnOK = wx.Button(parent = panel, id = wx.ID_OK)
+        btnOK.SetDefault()
+        btnsizer.AddButton(btnOK)
+
+        btnCancel = wx.Button(parent = panel, id = wx.ID_CANCEL)
+        btnsizer.AddButton(btnCancel)
+        btnsizer.Realize()
+
+        sizer.Add(item = btnsizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
+
+        panel.SetSizer(sizer)
+        sizer.Fit(panel)
+
+        self.SetSize(self.GetBestSize())
+
+        self.Layout()
+
+    def GetOpacity(self):
+        """!Button 'OK' pressed"""
+        # return opacity value
+        opacity = float(self.value.GetValue()) / 100
+        return opacity
+
+def GetImageHandlers(image):
+    """!Get list of supported image handlers"""
+    lext = list()
+    ltype = list()
+    for h in image.GetHandlers():
+        lext.append(h.GetExtension())
+        
+    filetype = ''
+    if 'png' in lext:
+        filetype += "PNG file (*.png)|*.png|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_PNG,
+                       'ext'  : 'png' })
+    filetype +=  "BMP file (*.bmp)|*.bmp|"
+    ltype.append({ 'type' : wx.BITMAP_TYPE_BMP,
+                   'ext'  : 'bmp' })
+    if 'gif' in lext:
+        filetype += "GIF file (*.gif)|*.gif|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_GIF,
+                       'ext'  : 'gif' })
+        
+    if 'jpg' in lext:
+        filetype += "JPG file (*.jpg)|*.jpg|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_JPEG,
+                       'ext'  : 'jpg' })
+
+    if 'pcx' in lext:
+        filetype += "PCX file (*.pcx)|*.pcx|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_PCX,
+                       'ext'  : 'pcx' })
+        
+    if 'pnm' in lext:
+        filetype += "PNM file (*.pnm)|*.pnm|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_PNM,
+                       'ext'  : 'pnm' })
+
+    if 'tif' in lext:
+        filetype += "TIF file (*.tif)|*.tif|"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_TIF,
+                       'ext'  : 'tif' })
+
+    if 'xpm' in lext:
+        filetype += "XPM file (*.xpm)|*.xpm"
+        ltype.append({ 'type' : wx.BITMAP_TYPE_XPM,
+                       'ext'  : 'xpm' })
+    
+    return filetype, ltype
+
+class StaticWrapText(wx.StaticText):
+    """!A Static Text field that wraps its text to fit its width,
+    enlarging its height if necessary.
+    """
+    def __init__(self, parent, id = wx.ID_ANY, label = '', *args, **kwds):
+        self.parent        = parent
+        self.originalLabel = label
+        
+        wx.StaticText.__init__(self, parent, id, label = '', *args, **kwds)
+        
+        self.SetLabel(label)
+        self.Bind(wx.EVT_SIZE, self.OnResize)
+    
+    def SetLabel(self, label):
+        self.originalLabel = label
+        self.wrappedSize = None
+        self.OnResize(None)
+
+    def OnResize(self, event):
+        if not getattr(self, "resizing", False):
+            self.resizing = True
+            newSize = wx.Size(self.parent.GetSize().width - 50,
+                              self.GetSize().height)
+            if self.wrappedSize != newSize:
+                wx.StaticText.SetLabel(self, self.originalLabel)
+                self.Wrap(newSize.width)
+                self.wrappedSize = newSize
+                
+                self.SetSize(self.wrappedSize)
+            del self.resizing
+
+class ImageSizeDialog(wx.Dialog):
+    """!Set size for saved graphic file"""
+    def __init__(self, parent, id = wx.ID_ANY, title = _("Set image size"),
+                 style = wx.DEFAULT_DIALOG_STYLE, **kwargs):
+        self.parent = parent
+        
+        wx.Dialog.__init__(self, parent, id = id, style = style, title = title, **kwargs)
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.box = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                label = ' % s' % _("Image size"))
+        
+        size = self.parent.GetWindow().GetClientSize()
+        self.width = wx.SpinCtrl(parent = self.panel, id = wx.ID_ANY,
+                                 style = wx.SP_ARROW_KEYS)
+        self.width.SetRange(20, 1e6)
+        self.width.SetValue(size.width)
+        wx.CallAfter(self.width.SetFocus)
+        self.height = wx.SpinCtrl(parent = self.panel, id = wx.ID_ANY,
+                                  style = wx.SP_ARROW_KEYS)
+        self.height.SetRange(20, 1e6)
+        self.height.SetValue(size.height)
+        self.template = wx.Choice(parent = self.panel, id = wx.ID_ANY,
+                                  size = (125, -1),
+                                  choices = [ "",
+                                              "640x480",
+                                              "800x600",
+                                              "1024x768",
+                                              "1280x960",
+                                              "1600x1200",
+                                              "1920x1440" ])
+        
+        self.btnOK = wx.Button(parent = self.panel, id = wx.ID_OK)
+        self.btnOK.SetDefault()
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        
+        self.template.Bind(wx.EVT_CHOICE, self.OnTemplate)
+        
+        self._layout()
+        self.SetSize(self.GetBestSize())
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # body
+        box = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
+        fbox = wx.FlexGridSizer(cols = 2, vgap = 5, hgap = 5)
+        fbox.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                      label = _("Width:")),
+                 flag = wx.ALIGN_CENTER_VERTICAL)
+        fbox.Add(item = self.width)
+        fbox.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                      label = _("Height:")),
+                 flag = wx.ALIGN_CENTER_VERTICAL)
+        fbox.Add(item = self.height)
+        fbox.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
+                                      label = _("Template:")),
+                 flag = wx.ALIGN_CENTER_VERTICAL)
+        fbox.Add(item = self.template)
+        
+        box.Add(item = fbox, proportion = 1,
+                flag = wx.EXPAND | wx.ALL, border = 5)
+        sizer.Add(item = box, proportion = 1,
+                  flag=wx.EXPAND | wx.ALL, border = 3)
+        
+        # buttons
+        btnsizer = wx.StdDialogButtonSizer()
+        btnsizer.AddButton(self.btnOK)
+        btnsizer.AddButton(self.btnCancel)
+        btnsizer.Realize()
+
+        sizer.Add(item = btnsizer, proportion = 0,
+                  flag = wx.EXPAND | wx.ALIGN_RIGHT | wx.ALL, border=5)
+        
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self.panel)
+        self.Layout()
+    
+    def GetValues(self):
+        """!Get width/height values"""
+        return self.width.GetValue(), self.height.GetValue()
+    
+    def OnTemplate(self, event):
+        """!Template selected"""
+        sel = event.GetString()
+        if not sel:
+            width, height = self.parent.GetWindow().GetClientSize()
+        else:
+            width, height = map(int, sel.split('x'))
+        self.width.SetValue(width)
+        self.height.SetValue(height)
+        
+class SqlQueryFrame(wx.Frame):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 title = _("GRASS GIS SQL Query Utility"),
+                 *kwargs):
+        """!SQL Query Utility window
+        """
+        self.parent = parent
+
+        wx.Frame.__init__(self, parent = parent, id = id, title = title, *kwargs)
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO))
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        self.sqlBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                   label = _(" SQL statement "))
+        self.sql = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
+                               style = wx.TE_MULTILINE)
+        
+        self.btnApply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
+        self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
+        self.Bind(wx.EVT_BUTTON, self.OnCloseWindow, self.btnCancel)
+        
+        self._layout()
+
+        self.SetMinSize(wx.Size(300, 150))
+        self.SetSize(wx.Size(500, 200))
+        
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        sqlSizer = wx.StaticBoxSizer(self.sqlBox, wx.HORIZONTAL)
+        sqlSizer.Add(item = self.sql, proportion = 1,
+                     flag = wx.EXPAND)
+
+        btnSizer = wx.StdDialogButtonSizer()
+        btnSizer.AddButton(self.btnApply)
+        btnSizer.AddButton(self.btnCancel)
+        btnSizer.Realize()
+        
+        sizer.Add(item = sqlSizer, proportion = 1,
+                  flag = wx.EXPAND | wx.ALL, border = 5) 
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+       
+        self.panel.SetSizer(sizer)
+        
+        self.Layout()
+
+    def OnCloseWindow(self, event):
+        """!Close window
+        """
+        self.Close()

Copied: grass/trunk/gui/wxpython/gui_core/forms.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/menuform.py)
===================================================================
--- grass/trunk/gui/wxpython/gui_core/forms.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/forms.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,2268 @@
+"""
+ at package gui_core.forms
+
+ at brief Construct simple wxPython GUI from a GRASS command interface
+description.
+
+Classes:
+ - helpPanel
+ - mainFrame
+ - cmdPanel
+ - GrassGUIApp
+ - GUI
+ - FloatValidator
+ - GNotebook
+
+This program is just a coarse approach to automatically build a GUI
+from a xml-based GRASS user interface description.
+
+You need to have Python 2.4, wxPython 2.8 and python-xml.
+
+The XML stream is read from executing the command given in the
+command line, thus you may call it for instance this way:
+
+python <this file.py> r.basins.fill
+
+Or you set an alias or wrap the call up in a nice shell script, GUI
+environment ... please contribute your idea.
+
+Updated to wxPython 2.8 syntax and contrib widgets.  Methods added to
+make it callable by gui.  Method added to automatically re-run with
+pythonw on a Mac.
+
+ at todo
+ - verify option value types
+
+Copyright(C) 2000-2011 by the GRASS Development Team
+This program is free software under the GPL(>=v2) Read the file
+COPYING coming with GRASS for details.
+
+ at author Jan-Oliver Wagner <jan at intevation.de>
+ at author Bernhard Reiter <bernhard at intevation.de>
+ at author Michael Barton, Arizona State University
+ at author Daniel Calvelo <dca.gis at gmail.com>
+ at author Martin Landa <landa.martin at gmail.com>
+ at author Luca Delucchi <lucadeluge at gmail.com>
+"""
+
+import sys
+import re
+import string
+import textwrap
+import os
+import time
+import copy
+import locale
+from threading import Thread
+import Queue
+
+from core import globalvar
+import wx
+try:
+    import wx.lib.agw.flatnotebook as FN
+except ImportError:
+    import wx.lib.flatnotebook as FN
+import wx.lib.colourselect     as csel
+import wx.lib.filebrowsebutton as filebrowse
+import wx.lib.scrolledpanel    as scrolled
+from wx.lib.expando  import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
+from wx.lib.newevent import NewEvent
+
+try:
+    import xml.etree.ElementTree as etree
+except ImportError:
+    import elementtree.ElementTree as etree # Python <= 2.4
+
+from gui_core.dialogs import StaticWrapText
+from gui_core.ghelp   import HelpPanel
+
+gisbase = os.getenv("GISBASE")
+if gisbase is None:
+    print >>sys.stderr, "We don't seem to be properly installed, or we are being run outside GRASS. Expect glitches."
+    gisbase = os.path.join(os.path.dirname(sys.argv[0]), os.path.pardir)
+    wxbase = gisbase
+else:
+    wxbase = os.path.join(globalvar.ETCWXDIR)
+
+sys.path.append(wxbase)
+
+from grass.script import core as grass
+from grass.script import task as gtask
+
+from gui_core import gselect
+from core import cmd as gcmd
+from gui_core import goutput
+from core import utils
+from core.settings import UserSettings
+try:
+    import subprocess
+except:
+    from compat import subprocess
+
+wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
+
+# From lib/gis/col_str.c, except purple which is mentioned
+# there but not given RGB values
+str2rgb = {'aqua': (100, 128, 255),
+           'black': (0, 0, 0),
+           'blue': (0, 0, 255),
+           'brown': (180, 77, 25),
+           'cyan': (0, 255, 255),
+           'gray': (128, 128, 128),
+           'green': (0, 255, 0),
+           'grey': (128, 128, 128),
+           'indigo': (0, 128, 255),
+           'magenta': (255, 0, 255),
+           'orange': (255, 128, 0),
+           'purple': (128, 0, 128),
+           'red': (255, 0, 0),
+           'violet': (128, 0, 255),
+           'white': (255, 255, 255),
+           'yellow': (255, 255, 0)}
+rgb2str = {}
+for (s,r) in str2rgb.items():
+    rgb2str[ r ] = s
+
+"""!Hide some options in the GUI"""
+#_blackList = { 'enabled' : False,
+#               'items'   : { 'r.buffer' : {'params' : ['input', 'output'],
+#                                           'flags' : ['z', 'overwrite']}}}
+_blackList = { 'enabled' : False,
+               'items'   : {} }
+
+def color_resolve(color):
+    if len(color) > 0 and color[0] in "0123456789":
+        rgb = tuple(map(int, color.split(':')))
+        label = color
+    else:
+        # Convert color names to RGB
+        try:
+            rgb = str2rgb[color]
+            label = color
+        except KeyError:
+            rgb = (200, 200, 200)
+            label = _('Select Color')
+    return (rgb, label)
+
+def text_beautify(someString , width = 70):
+    """!Make really long texts shorter, clean up whitespace and remove
+    trailing punctuation.
+    """
+    if width > 0:
+        return escape_ampersand(string.strip(
+                os.linesep.join(textwrap.wrap(utils.normalize_whitespace(someString), width)),
+                ".,;:"))
+    else:
+        return escape_ampersand(string.strip(utils.normalize_whitespace(someString), ".,;:"))
+    
+def escape_ampersand(text):
+    """!Escapes ampersands with additional ampersand for GUI"""
+    return string.replace(text, "&", "&&")
+
+class UpdateThread(Thread):
+    """!Update dialog widgets in the thread"""
+    def __init__(self, parent, event, eventId, task):
+        Thread.__init__(self)
+        
+        self.parent = parent
+        self.event = event
+        self.eventId = eventId
+        self.task = task
+        self.setDaemon(True)
+        
+        # list of functions which updates the dialog
+        self.data = {}
+        
+    def run(self):
+        # get widget id
+        if not self.eventId:
+            for p in self.task.params:
+                if p.get('gisprompt', False) == False:
+                    continue
+                prompt = p.get('element', '')
+                if prompt == 'vector':
+                    name = p.get('name', '')
+                    if name in ('map', 'input'):
+                        self.eventId = p['wxId'][0]
+            if self.eventId is None:
+                return
+        
+        p = self.task.get_param(self.eventId, element = 'wxId', raiseError = False)
+        if not p or 'wxId-bind' not in p:
+            return
+        
+        # get widget prompt
+        pType = p.get('prompt', '')
+        if not pType:
+            return
+        
+        # check for map/input parameter
+        pMap = self.task.get_param('map', raiseError = False)
+        
+        if not pMap:
+            pMap = self.task.get_param('input', raiseError = False)
+        
+        if pMap:
+            map = pMap.get('value', '')
+        else:
+            map = None
+        
+        # avoid running db.describe several times
+        cparams = dict()
+        cparams[map] = { 'dbInfo' : None,
+                         'layers' : None, }
+        
+        # update reference widgets
+        for uid in p['wxId-bind']:
+            win = self.parent.FindWindowById(uid)
+            if not win:
+                continue
+            
+            name = win.GetName()
+            
+            map = layer = None
+            driver = db = table = None
+            if name in ('LayerSelect', 'ColumnSelect'):
+                if p.get('element', '') == 'vector': # -> vector
+                    # get map name
+                    map = p.get('value', '')
+                    
+                    # get layer
+                    for bid in p['wxId-bind']:
+                        p = self.task.get_param(bid, element = 'wxId', raiseError = False)
+                        if not p:
+                            continue
+                        
+                        if p.get('element', '') == 'layer':
+                            layer = p.get('value', '')
+                            if layer != '':
+                                layer = p.get('value', '')
+                            else:
+                                layer = p.get('default', '')
+                            break
+                        
+                elif p.get('element', '') == 'layer': # -> layer
+                    # get layer
+                    layer = p.get('value', '')
+                    if layer != '':
+                        layer = p.get('value', '')
+                    else:
+                        layer = p.get('default', '')
+                    
+                    # get map name
+                    pMapL = self.task.get_param(p['wxId'][0], element = 'wxId-bind', raiseError = False)
+                    if pMapL:
+                        map = pMapL.get('value', '')
+            
+            if name == 'TableSelect' or \
+                    (name == 'ColumnSelect' and not map):
+                pDriver = self.task.get_param('dbdriver', element = 'prompt', raiseError = False)
+                if pDriver:
+                    driver = pDriver.get('value', '')
+                pDb = self.task.get_param('dbname', element = 'prompt', raiseError = False)
+                if pDb:
+                    db = pDb.get('value', '')
+                if name == 'ColumnSelect':
+                    pTable = self.task.get_param('dbtable', element = 'element', raiseError = False)
+                    if pTable:
+                        table = pTable.get('value', '')
+
+            if name == 'LayerSelect':
+                # determine format
+                native = True
+                
+                for id in pMap['wxId']:
+                    winVec  = self.parent.FindWindowById(id)
+                    if winVec.GetName() == 'VectorFormat' and \
+                            winVec.GetSelection() != 0:
+                        native = False
+                        break
+                # TODO: update only if needed
+                if native:
+                    if map:
+                        self.data[win.InsertLayers] = { 'vector' : map }
+                    else:
+                        self.data[win.InsertLayers] = { }
+                else:
+                    if map:
+                        self.data[win.InsertLayers] = { 'dsn' : map.rstrip('@OGR') }
+                    else:
+                        self.data[win.InsertLayers] = { }
+            
+            elif name == 'TableSelect':
+                self.data[win.InsertTables] = { 'driver' : driver,
+                                                'database' : db }
+                
+            elif name == 'ColumnSelect':
+                if map:
+                    if map in cparams:
+                        if not cparams[map]['dbInfo']:
+                            cparams[map]['dbInfo'] = gselect.VectorDBInfo(map)
+                        self.data[win.InsertColumns] = { 'vector' : map, 'layer' : layer,
+                                                         'dbInfo' : cparams[map]['dbInfo'] }
+                else: # table
+                    if driver and db:
+                        self.data[win.InsertTableColumns] = { 'table' : pTable.get('value'),
+                                                              'driver' : driver,
+                                                              'database' : db }
+                    elif pTable:
+                        self.data[win.InsertTableColumns] = { 'table'  : pTable.get('value') }
+            
+            elif name == 'SubGroupSelect':
+                pGroup = self.task.get_param('group', element = 'element', raiseError = False)
+                if pGroup:
+                    self.data[win.Insert] = { 'group' : pGroup.get('value', '')}
+            
+            elif name == 'LocationSelect':
+                pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
+                if pDbase:
+                    self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', '')}
+
+            elif name == 'MapsetSelect':
+                pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
+                pLocation = self.task.get_param('location', element = 'element', raiseError = False)
+                if pDbase and pLocation:
+                    self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
+                                                   'location' : pLocation.get('value', '')}
+
+            elif name ==  'ProjSelect':
+                pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
+                pLocation = self.task.get_param('location', element = 'element', raiseError = False)
+                pMapset = self.task.get_param('mapset', element = 'element', raiseError = False)
+                if pDbase and pLocation and pMapset:
+                    self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
+                                                   'location' : pLocation.get('value', ''),
+                                                   'mapset' : pMapset.get('value', '')}
+            
+def UpdateDialog(parent, event, eventId, task):
+    return UpdateThread(parent, event, eventId, task)
+
+class UpdateQThread(Thread):
+    """!Update dialog widgets in the thread"""
+    requestId = 0
+    def __init__(self, parent, requestQ, resultQ, **kwds):
+        Thread.__init__(self, **kwds)
+        
+        self.parent = parent # cmdPanel
+        self.setDaemon(True)
+        
+        self.requestQ = requestQ
+        self.resultQ = resultQ
+        
+        self.start()
+        
+    def Update(self, callable, *args, **kwds):
+        UpdateQThread.requestId +=  1
+        
+        self.request = None
+        self.requestQ.put((UpdateQThread.requestId, callable, args, kwds))
+        
+        return UpdateQThread.requestId
+    
+    def run(self):
+        while True:
+            requestId, callable, args, kwds = self.requestQ.get()
+            
+            requestTime = time.time()
+            
+            self.request = callable(*args, **kwds)
+
+            self.resultQ.put((requestId, self.request.run()))
+           
+            if self.request:
+                event = wxUpdateDialog(data = self.request.data)
+                wx.PostEvent(self.parent, event)
+
+class mainFrame(wx.Frame):
+    """!This is the Frame containing the dialog for options input.
+
+    The dialog is organized in a notebook according to the guisections
+    defined by each GRASS command.
+
+    If run with a parent, it may Apply, Ok or Cancel; the latter two
+    close the dialog.  The former two trigger a callback.
+
+    If run standalone, it will allow execution of the command.
+
+    The command is checked and sent to the clipboard when clicking
+    'Copy'.
+    """
+    def __init__(self, parent, ID, task_description,
+                 get_dcmd = None, layer = None):
+        self.get_dcmd = get_dcmd
+        self.layer    = layer
+        self.task     = task_description
+        self.parent   = parent            # LayerTree | Modeler | None | ...
+        if parent and parent.GetName() ==  'Modeler':
+            self.modeler = self.parent
+        else:
+            self.modeler = None
+        
+        # module name + keywords
+        if self.task.name.split('.')[-1] in ('py', 'sh'):
+            title = str(self.task.name.rsplit('.',1)[0])
+        else:
+            title = self.task.name
+        try:
+            if self.task.keywords !=  ['']:
+                title +=   " [" + ', '.join(self.task.keywords) + "]"
+        except ValueError:
+            pass
+        
+        wx.Frame.__init__(self, parent = parent, id = ID, title = title,
+                          pos = wx.DefaultPosition, style = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL,
+                          name = "MainFrame")
+        
+        self.locale = wx.Locale(language = wx.LANGUAGE_DEFAULT)
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        # statusbar
+        self.CreateStatusBar()
+        
+        # icon
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_dialog.ico'), wx.BITMAP_TYPE_ICO))
+        
+        guisizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # set apropriate output window
+        if self.parent:
+            self.standalone = False
+        else:
+            self.standalone = True
+        
+        # logo + description
+        topsizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        # GRASS logo
+        self.logo = wx.StaticBitmap(parent = self.panel,
+                                    bitmap = wx.Bitmap(name = os.path.join(globalvar.ETCIMGDIR,
+                                                                           'grass_form.png'),
+                                                     type = wx.BITMAP_TYPE_PNG))
+        topsizer.Add(item = self.logo, proportion = 0, border = 3,
+                     flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL)
+        
+        # add module description
+        if self.task.label:
+            module_desc = self.task.label + ' ' + self.task.description
+        else:
+            module_desc = self.task.description
+        self.description = StaticWrapText(parent = self.panel,
+                                          label = module_desc)
+        topsizer.Add(item = self.description, proportion = 1, border = 5,
+                     flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
+        
+        guisizer.Add(item = topsizer, proportion = 0, flag = wx.EXPAND)
+        
+        self.panel.SetSizerAndFit(guisizer)
+        self.Layout()
+        
+        # notebooks
+        self.notebookpanel = cmdPanel(parent = self.panel, task = self.task,
+                                      mainFrame = self)
+        self.goutput = self.notebookpanel.goutput
+        self.notebookpanel.OnUpdateValues = self.updateValuesHook
+        guisizer.Add(item = self.notebookpanel, proportion = 1, flag = wx.EXPAND)
+        
+        # status bar
+        status_text = _("Enter parameters for '") + self.task.name + "'"
+        try:
+            self.task.get_cmd()
+            self.updateValuesHook()
+        except ValueError:
+            self.SetStatusText(status_text)
+        
+        # buttons
+        btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+        # cancel
+        self.btn_cancel = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
+        self.btn_cancel.SetToolTipString(_("Close this window without executing the command (Ctrl+Q)"))
+        btnsizer.Add(item = self.btn_cancel, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
+        self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
+
+        if self.get_dcmd is not None: # A callback has been set up
+            btn_apply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
+            btn_ok = wx.Button(parent = self.panel, id = wx.ID_OK)
+            btn_ok.SetDefault()
+
+            btnsizer.Add(item = btn_apply, proportion = 0,
+                         flag = wx.ALL | wx.ALIGN_CENTER,
+                         border = 10)
+            btnsizer.Add(item = btn_ok, proportion = 0,
+                         flag = wx.ALL | wx.ALIGN_CENTER,
+                         border = 10)
+            
+            btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
+            btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
+        else: # We're standalone
+            # run
+            self.btn_run = wx.Button(parent = self.panel, id = wx.ID_OK, label =  _("&Run"))
+            self.btn_run.SetToolTipString(_("Run the command (Ctrl+R)"))
+            self.btn_run.SetDefault()
+            self.btn_run.SetForegroundColour(wx.Colour(35, 142, 35))
+            
+            # copy
+            self.btn_clipboard = wx.Button(parent = self.panel, id = wx.ID_COPY)
+            self.btn_clipboard.SetToolTipString(_("Copy the current command string to the clipboard (Ctrl+C)"))
+            
+            btnsizer.Add(item = self.btn_run, proportion = 0,
+                         flag = wx.ALL | wx.ALIGN_CENTER,
+                         border = 10)
+            
+            btnsizer.Add(item = self.btn_clipboard, proportion = 0,
+                         flag = wx.ALL | wx.ALIGN_CENTER,
+                         border = 10)
+            
+            self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
+            self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
+        # help
+        self.btn_help = wx.Button(parent = self.panel, id = wx.ID_HELP)
+        self.btn_help.SetToolTipString(_("Show manual page of the command (Ctrl+H)"))
+        self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
+        if self.notebookpanel.notebook.GetPageIndexByName('manual') < 0:
+            self.btn_help.Hide()
+        
+        # add help button
+        btnsizer.Add(item = self.btn_help, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
+        
+        guisizer.Add(item = btnsizer, proportion = 0, flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT,
+                     border = 30)
+        
+        if self.parent and not self.modeler:
+            addLayer = False
+            for p in self.task.params:
+                if p.get('age', 'old') ==  'new' and \
+                   p.get('prompt', '') in ('raster', 'vector', '3d-raster'):
+                    addLayer = True
+            
+            if addLayer:
+                # add newly created map into layer tree
+                self.addbox = wx.CheckBox(parent = self.panel,
+                                          label = _('Add created map(s) into layer tree'), style = wx.NO_BORDER)
+                self.addbox.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
+                guisizer.Add(item = self.addbox, proportion = 0,
+                             flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
+                             border = 5)
+        
+        hasNew = False
+        for p in self.task.params:
+            if p.get('age', 'old') ==  'new':
+                hasNew = True
+                break
+        
+        if self.get_dcmd is None and hasNew:
+            # close dialog when command is terminated
+            self.closebox = wx.CheckBox(parent = self.panel,
+                                        label = _('Close dialog on finish'), style = wx.NO_BORDER)
+            self.closebox.SetValue(UserSettings.Get(group = 'cmd', key = 'closeDlg', subkey = 'enabled'))
+            self.closebox.SetToolTipString(_("Close dialog when command is successfully finished. "
+                                             "Change this settings in Preferences dialog ('Command' tab)."))
+            guisizer.Add(item = self.closebox, proportion = 0,
+                         flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
+                         border = 5)
+        
+        self.Bind(wx.EVT_CLOSE,  self.OnCancel)
+        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
+        
+        # do layout
+        # called automatically by SetSizer()
+        self.panel.SetAutoLayout(True) 
+        self.panel.SetSizerAndFit(guisizer)
+        
+        sizeFrame = self.GetBestSize()
+        self.SetMinSize(sizeFrame)
+        
+        if hasattr(self, "closebox"):
+            scale = 0.33
+        else:
+            scale = 0.50
+        self.SetSize(wx.Size(sizeFrame[0], sizeFrame[1] + scale * max(self.notebookpanel.panelMinHeight,
+                                                                      self.notebookpanel.constrained_size[1])))
+        
+        # thread to update dialog
+        # create queues
+        self.requestQ = Queue.Queue()
+        self.resultQ = Queue.Queue()
+        self.updateThread = UpdateQThread(self.notebookpanel, self.requestQ, self.resultQ)
+        
+        self.Layout()
+        
+        # keep initial window size limited for small screens
+        width, height = self.GetSizeTuple()
+        self.SetSize(wx.Size(min(width, 650),
+                             min(height, 500)))
+        
+        # fix goutput's pane size (required for Mac OSX)
+        if self.goutput:  	 	 
+            self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
+        
+    def updateValuesHook(self, event = None):
+        """!Update status bar data"""
+        self.SetStatusText(' '.join(self.notebookpanel.createCmd(ignoreErrors = True)))
+        if event:
+            event.Skip()
+        
+    def OnKeyUp(self, event):
+        """!Key released (check hot-keys)"""
+        try:
+            kc = chr(event.GetKeyCode())
+        except ValueError:
+            event.Skip()
+            return
+        
+        if not event.ControlDown():
+            event.Skip()
+            return
+        
+        if kc ==  'Q':
+            self.OnCancel(None)
+        elif kc ==  'S':
+            self.OnAbort(None)
+        elif kc ==  'H':
+            self.OnHelp(None)
+        elif kc ==  'R':
+            self.OnRun(None)
+        elif kc ==  'C':
+            self.OnCopy(None)
+        
+        event.Skip()
+
+    def OnDone(self, cmd, returncode):
+        """!This function is launched from OnRun() when command is
+        finished
+
+        @param returncode command's return code (0 for success)
+        """
+        if not self.parent or returncode !=  0:
+            return
+        if self.parent.GetName() not in ('LayerTree', 'LayerManager'):
+            return
+        
+        if self.parent.GetName() ==  'LayerTree':
+            display = self.parent.GetMapDisplay()
+        else: # Layer Manager
+            display = self.parent.GetLayerTree().GetMapDisplay()
+            
+        if not display or not display.IsAutoRendered():
+            return
+        
+        mapLayers = map(lambda x: x.GetName(),
+                        display.GetMap().GetListOfLayers(l_type = 'raster') +
+                        display.GetMap().GetListOfLayers(l_type = 'vector'))
+        
+        task = GUI(show = None).ParseCommand(cmd)
+        for p in task.get_options()['params']:
+            if p.get('prompt', '') not in ('raster', 'vector'):
+                continue
+            mapName = p.get('value', '')
+            if '@' not in mapName:
+                mapName = mapName + '@' + grass.gisenv()['MAPSET']
+            if mapName in mapLayers:
+                display.GetWindow().UpdateMap(render = True)
+                return
+        
+    def OnOK(self, event):
+        """!OK button pressed"""
+        cmd = self.OnApply(event)
+        if cmd is not None and self.get_dcmd is not None:
+            self.OnCancel(event)
+
+    def OnApply(self, event):
+        """!Apply the command"""
+        if self.modeler:
+            cmd = self.createCmd(ignoreErrors = True, ignoreRequired = True)
+        else:
+            cmd = self.createCmd()
+        
+        if cmd is not None and self.get_dcmd is not None:
+            # return d.* command to layer tree for rendering
+            self.get_dcmd(cmd, self.layer, {"params": self.task.params, 
+                                            "flags" : self.task.flags},
+                          self)
+            # echo d.* command to output console
+            # self.parent.writeDCommand(cmd)
+
+        return cmd
+
+    def OnRun(self, event):
+        """!Run the command"""
+        cmd = self.createCmd()
+        
+        if not cmd or len(cmd) < 1:
+            return
+        
+        ret = 0
+        if self.standalone or cmd[0][0:2] !=  "d.":
+            # Send any non-display command to parent window (probably wxgui.py)
+            # put to parents switch to 'Command output'
+            self.notebookpanel.notebook.SetSelectionByName('output')
+            
+            try:
+                if self.task.path:
+                    cmd[0] = self.task.path # full path
+                
+                ret = self.goutput.RunCmd(cmd, onDone = self.OnDone)
+            except AttributeError, e:
+                print >> sys.stderr, "%s: Probably not running in wxgui.py session?" % (e)
+                print >> sys.stderr, "parent window is: %s" % (str(self.parent))
+        else:
+            gcmd.Command(cmd)
+        
+        if ret != 0:
+            self.notebookpanel.notebook.SetSelection(0)
+            return
+        
+        # update buttons status
+        for btn in (self.btn_run,
+                    self.btn_cancel,
+                    self.btn_clipboard,
+                    self.btn_help):
+            btn.Enable(False)
+        
+    def OnAbort(self, event):
+        """!Abort running command"""
+        event = goutput.wxCmdAbort(aborted = True)
+        wx.PostEvent(self.goutput, event)
+
+    def OnCopy(self, event):
+        """!Copy the command"""
+        cmddata = wx.TextDataObject()
+        # list -> string
+        cmdstring = ' '.join(self.createCmd(ignoreErrors = True))
+        cmddata.SetText(cmdstring)
+        if wx.TheClipboard.Open():
+#            wx.TheClipboard.UsePrimarySelection(True)
+            wx.TheClipboard.SetData(cmddata)
+            wx.TheClipboard.Close()
+            self.SetStatusText(_("'%s' copied to clipboard") % \
+                                    (cmdstring))
+
+    def OnCancel(self, event):
+        """!Cancel button pressed"""
+        self.MakeModal(False)
+        
+        if self.get_dcmd and \
+                self.parent and \
+                self.parent.GetName() in ('LayerTree',
+                                          'MapWindow'):
+            # display decorations and 
+            # pressing OK or cancel after setting layer properties
+            if self.task.name in ['d.barscale','d.legend','d.histogram'] \
+                or len(self.parent.GetPyData(self.layer)[0]['cmd']) >=  1:
+                self.Hide()
+            # canceled layer with nothing set
+            elif len(self.parent.GetPyData(self.layer)[0]['cmd']) < 1:
+                self.parent.Delete(self.layer)
+                self.Destroy()
+        else:
+            # cancel for non-display commands
+            self.Destroy()
+
+    def OnHelp(self, event):
+        """!Show manual page (switch to the 'Manual' notebook page)"""
+        if self.notebookpanel.notebook.GetPageIndexByName('manual') > -1:
+            self.notebookpanel.notebook.SetSelectionByName('manual')
+            self.notebookpanel.OnPageChange(None)
+        
+        if event:    
+            event.Skip()
+        
+    def createCmd(self, ignoreErrors = False, ignoreRequired = False):
+        """!Create command string (python list)"""
+        return self.notebookpanel.createCmd(ignoreErrors = ignoreErrors,
+                                            ignoreRequired = ignoreRequired)
+
+class cmdPanel(wx.Panel):
+    """!A panel containing a notebook dividing in tabs the different
+    guisections of the GRASS cmd.
+    """
+    def __init__(self, parent, task, id = wx.ID_ANY, mainFrame = None, *args, **kwargs):
+        if mainFrame:
+            self.parent = mainFrame
+        else:
+            self.parent = parent
+        self.task = task
+        
+        wx.Panel.__init__(self, parent, id = id, *args, **kwargs)
+        
+        # Determine tab layout
+        sections = []
+        is_section = {}
+        not_hidden = [ p for p in self.task.params + self.task.flags if not p.get('hidden', False) ==  True ]
+
+        self.label_id = [] # wrap titles on resize
+
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        
+        for task in not_hidden:
+            if task.get('required', False):
+                # All required go into Main, even if they had defined another guisection
+                task['guisection'] = _('Required')
+            if task.get('guisection','') ==  '':
+                # Undefined guisections end up into Options
+                task['guisection'] = _('Optional')
+            if task['guisection'] not in is_section:
+                # We do it like this to keep the original order, except for Main which goes first
+                is_section[task['guisection']] = 1
+                sections.append(task['guisection'])
+            else:
+                is_section[ task['guisection'] ] +=  1
+        del is_section
+
+        # 'Required' tab goes first, 'Optional' as the last one
+        for (newidx,content) in [ (0,_('Required')), (len(sections)-1,_('Optional')) ]:
+            if content in sections:
+                idx = sections.index(content)
+                sections[idx:idx+1] = []
+                sections[newidx:newidx] =  [content]
+
+        panelsizer = wx.BoxSizer(orient = wx.VERTICAL)
+
+        # Build notebook
+        self.notebook = GNotebook(self, style = globalvar.FNPageStyle)
+        self.notebook.SetTabAreaColour(globalvar.FNPageColor)
+        self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange)
+
+        tab = {}
+        tabsizer = {}
+        for section in sections:
+            tab[section] = scrolled.ScrolledPanel(parent = self.notebook)
+            tab[section].SetScrollRate(10, 10)
+            tabsizer[section] = wx.BoxSizer(orient = wx.VERTICAL)
+            self.notebook.AddPage(page = tab[section], text = section)
+
+        # are we running from command line?
+        ### add 'command output' tab regardless standalone dialog
+        if self.parent.GetName() ==  "MainFrame" and self.parent.get_dcmd is None:
+            self.goutput = goutput.GMConsole(parent = self, margin = False)
+            self.outpage = self.notebook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
+        else:
+            self.goutput = None
+        
+        self.manual_tab = HelpPanel(parent = self, grass_command = self.task.name)
+        if not self.manual_tab.IsFile():
+            self.manual_tab.Hide()
+        else:
+            self.notebook.AddPage(page = self.manual_tab, text = _("Manual"), name = 'manual')
+        
+        self.notebook.SetSelection(0)
+
+        panelsizer.Add(item = self.notebook, proportion = 1, flag = wx.EXPAND)
+
+        #
+        # flags
+        #
+        text_style = wx.FONTWEIGHT_NORMAL
+        visible_flags = [ f for f in self.task.flags if not f.get('hidden', False) ==  True ]
+        for f in visible_flags:
+            which_sizer = tabsizer[ f['guisection'] ]
+            which_panel = tab[ f['guisection'] ]
+            # if label is given: description -> tooltip
+            if f.get('label','') !=  '':
+                title = text_beautify(f['label'])
+                tooltip = text_beautify(f['description'], width = -1)
+            else:
+                title = text_beautify(f['description'])
+                tooltip = None
+            title_sizer = wx.BoxSizer(wx.HORIZONTAL)
+            rtitle_txt = wx.StaticText(parent = which_panel,
+                                       label = '(' + f['name'] + ')')
+            chk = wx.CheckBox(parent = which_panel, label = title, style = wx.NO_BORDER)
+            self.label_id.append(chk.GetId())
+            if tooltip:
+                chk.SetToolTipString(tooltip)
+            chk.SetValue(f.get('value', False))
+            title_sizer.Add(item = chk, proportion = 1,
+                            flag = wx.EXPAND)
+            title_sizer.Add(item = rtitle_txt, proportion = 0,
+                            flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
+            which_sizer.Add(item = title_sizer, proportion = 0,
+                            flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
+            f['wxId'] = [ chk.GetId(), ]
+            chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
+            
+            if self.parent.GetName() ==  'MainFrame' and self.parent.modeler:
+                parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
+                                     label = _("Parameterized in model"))
+                parChk.SetName('ModelParam')
+                parChk.SetValue(f.get('parameterized', False))
+                if 'wxId' in f:
+                    f['wxId'].append(parChk.GetId())
+                else:
+                    f['wxId'] = [ parChk.GetId() ]
+                parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
+                which_sizer.Add(item = parChk, proportion = 0,
+                                flag = wx.LEFT, border = 20)
+            
+            if f['name'] in ('verbose', 'quiet'):
+                chk.Bind(wx.EVT_CHECKBOX, self.OnVerbosity)
+                vq = UserSettings.Get(group = 'cmd', key = 'verbosity', subkey = 'selection')
+                if f['name'] ==  vq:
+                    chk.SetValue(True)
+                    f['value'] = True
+            elif f['name'] ==  'overwrite' and 'value' not in f:
+                chk.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
+                f['value'] = UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled')
+                
+        #
+        # parameters
+        #
+        visible_params = [ p for p in self.task.params if not p.get('hidden', False) ==  True ]
+        
+        try:
+            first_param = visible_params[0]
+        except IndexError:
+            first_param = None
+        
+        for p in visible_params:
+            which_sizer = tabsizer[ p['guisection'] ]
+            which_panel = tab[ p['guisection'] ]
+            # if label is given -> label and description -> tooltip
+            # otherwise description -> lavel
+            if p.get('label','') !=  '':
+                title = text_beautify(p['label'])
+                tooltip = text_beautify(p['description'], width = -1)
+            else:
+                title = text_beautify(p['description'])
+                tooltip = None
+            txt = None
+
+            # text style (required -> bold)
+            if not p.get('required', False):
+                text_style = wx.FONTWEIGHT_NORMAL
+            else:
+                text_style = wx.FONTWEIGHT_BOLD
+
+            # title sizer (description, name, type)
+            if (len(p.get('values', [])) > 0) and \
+                    p.get('multiple', False) and \
+                    p.get('gisprompt',False) ==  False and \
+                    p.get('type', '') ==  'string':
+                title_txt = wx.StaticBox(parent = which_panel, id = wx.ID_ANY)
+            else:
+                title_sizer = wx.BoxSizer(wx.HORIZONTAL)
+                title_txt = wx.StaticText(parent = which_panel)
+                if p['key_desc']:
+                    ltype = ','.join(p['key_desc'])
+                else:
+                    ltype = p['type']
+                rtitle_txt = wx.StaticText(parent = which_panel,
+                                           label = '(' + p['name'] + '=' + ltype + ')')
+                title_sizer.Add(item = title_txt, proportion = 1,
+                                flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
+                title_sizer.Add(item = rtitle_txt, proportion = 0,
+                                flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
+                which_sizer.Add(item = title_sizer, proportion = 0,
+                                flag = wx.EXPAND)
+            self.label_id.append(title_txt.GetId())
+
+            # title expansion
+            if p.get('multiple', False) and len(p.get('values','')) ==  0:
+                title = _("[multiple]") + " " + title
+                if p.get('value','') ==   '' :
+                    p['value'] = p.get('default','')
+
+            if (len(p.get('values', [])) > 0):
+                valuelist      = map(str, p.get('values',[]))
+                valuelist_desc = map(unicode, p.get('values_desc',[]))
+
+                if p.get('multiple', False) and \
+                        p.get('gisprompt',False) ==  False and \
+                        p.get('type', '') ==  'string':
+                    title_txt.SetLabel(" %s: (%s, %s) " % (title, p['name'], p['type']))
+                    if valuelist_desc:
+                        hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.VERTICAL)
+                    else:
+                        hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.HORIZONTAL)
+                    isEnabled = {}
+                    # copy default values
+                    if p['value'] ==  '':
+                        p['value'] = p.get('default', '')
+                        
+                    for defval in p.get('value', '').split(','):
+                        isEnabled[ defval ] = 'yes'
+                        # for multi checkboxes, this is an array of all wx IDs
+                        # for each individual checkbox
+                        p[ 'wxId' ] = list()
+                    idx = 0
+                    for val in valuelist:
+                        try:
+                            label = valuelist_desc[idx]
+                        except IndexError:
+                            label = val
+                        
+                        chkbox = wx.CheckBox(parent = which_panel,
+                                             label = text_beautify(label))
+                        p[ 'wxId' ].append(chkbox.GetId())
+                        if val in isEnabled:
+                            chkbox.SetValue(True)
+                        hSizer.Add(item = chkbox, proportion = 0,
+                                    flag = wx.ADJUST_MINSIZE | wx.ALL, border = 1)
+                        chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti)
+                        idx +=  1
+                        
+                    which_sizer.Add(item = hSizer, proportion = 0,
+                                    flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, border = 5)
+                elif p.get('gisprompt', False) ==  False:
+                    if len(valuelist) ==  1: # -> textctrl
+                        title_txt.SetLabel("%s (%s %s):" % (title, _('valid range'),
+                                                            str(valuelist[0])))
+                        
+                        if p.get('type', '') ==  'integer' and \
+                                not p.get('multiple', False):
+
+                            # for multiple integers use textctrl instead of spinsctrl
+                            try:
+                                minValue, maxValue = map(int, valuelist[0].split('-'))
+                            except ValueError:
+                                minValue = -1e6
+                                maxValue = 1e6
+                            txt2 = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY, size = globalvar.DIALOG_SPIN_SIZE,
+                                               min = minValue, max = maxValue)
+                            txt2.SetName("SpinCtrl")
+                            style = wx.BOTTOM | wx.LEFT
+                        else:
+                            txt2 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
+                            txt2.SetName("TextCtrl")
+                            style = wx.EXPAND | wx.BOTTOM | wx.LEFT
+                        
+                        value = self._getValue(p)
+                        # parameter previously set
+                        if value:
+                            if txt2.GetName() ==  "SpinCtrl":
+                                txt2.SetValue(int(value))
+                            else:
+                                txt2.SetValue(value)
+                        
+                        which_sizer.Add(item = txt2, proportion = 0,
+                                        flag = style, border = 5)
+
+                        p['wxId'] = [ txt2.GetId(), ]
+                        txt2.Bind(wx.EVT_TEXT, self.OnSetValue)
+                    else:
+                        # list of values (combo)
+                        title_txt.SetLabel(title + ':')
+                        cb = wx.ComboBox(parent = which_panel, id = wx.ID_ANY, value = p.get('default',''),
+                                         size = globalvar.DIALOG_COMBOBOX_SIZE,
+                                         choices = valuelist, style = wx.CB_DROPDOWN)
+                        value = self._getValue(p)
+                        if value:
+                            cb.SetValue(value) # parameter previously set
+                        which_sizer.Add(item = cb, proportion = 0,
+                                        flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
+                        p['wxId'] = [cb.GetId(),]
+                        cb.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                        cb.Bind(wx.EVT_TEXT, self.OnSetValue)
+            
+            # text entry
+            if (p.get('type','string') in ('string','integer','float')
+                and len(p.get('values',[])) ==  0
+                and p.get('gisprompt',False) ==  False
+                and p.get('prompt','') !=  'color'):
+
+                title_txt.SetLabel(title + ':')
+                if p.get('multiple', False) or \
+                        p.get('type', 'string') ==  'string' or \
+                        len(p.get('key_desc', [])) > 1:
+                    txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
+                    
+                    value = self._getValue(p)
+                    if value:
+                        # parameter previously set
+                        txt3.SetValue(str(value))
+                    
+                    txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
+                    style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
+                else:
+                    minValue = -1e9
+                    maxValue = 1e9
+                    if p.get('type', '') ==  'integer':
+                        txt3 = wx.SpinCtrl(parent = which_panel, value = p.get('default',''),
+                                           size = globalvar.DIALOG_SPIN_SIZE,
+                                           min = minValue, max = maxValue)
+                        style = wx.BOTTOM | wx.LEFT | wx.RIGHT
+                        
+                        value = self._getValue(p)
+                        if value:
+                            txt3.SetValue(int(value)) # parameter previously set
+                        
+                        txt3.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
+                    else:
+                        txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
+                                           validator = FloatValidator())
+                        style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
+                        
+                        value = self._getValue(p)
+                        if value:
+                            txt3.SetValue(str(value)) # parameter previously set
+                    
+                txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
+                
+                which_sizer.Add(item = txt3, proportion = 0,
+                                flag = style, border = 5)
+                p['wxId'] = [ txt3.GetId(), ]
+
+            #
+            # element selection tree combobox (maps, icons, regions, etc.)
+            #
+            if p.get('gisprompt', False) ==  True:
+                title_txt.SetLabel(title + ':')
+                # GIS element entry
+                if p.get('prompt','') not in ('color',
+                                              'color_none',
+                                              'subgroup',
+                                              'dbdriver',
+                                              'dbname',
+                                              'dbtable',
+                                              'dbcolumn',
+                                              'layer',
+                                              'layer_all',
+                                              'location',
+                                              'mapset',
+                                              'dbase') and \
+                                              p.get('element', '') not in ('file', 'dir'):
+                    multiple = p.get('multiple', False)
+                    if p.get('age', '') ==  'new':
+                        mapsets = [grass.gisenv()['MAPSET'],]
+                    else:
+                        mapsets = None
+                    if self.task.name in ('r.proj', 'v.proj') \
+                            and p.get('name', '') ==  'input':
+                        if self.task.name ==  'r.proj':
+                            isRaster = True
+                        else:
+                            isRaster = False
+                        selection = gselect.ProjSelect(parent = which_panel,
+                                                       isRaster = isRaster)
+                        p['wxId'] = [ selection.GetId(), ]
+                        selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                        formatSelector = False
+                        selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                    else:
+                        selection = gselect.Select(parent = which_panel, id = wx.ID_ANY,
+                                                   size = globalvar.DIALOG_GSELECT_SIZE,
+                                                   type = p.get('element', ''),
+                                                   multiple = multiple, mapsets = mapsets,
+                                                   fullyQualified = p.get('age', 'old') == 'old')
+                        
+                        value = self._getValue(p)
+                        if value:
+                            selection.SetValue(value)
+                        
+                        formatSelector = True
+                        # A select.Select is a combobox with two children: a textctl and a popupwindow;
+                        # we target the textctl here
+                        textWin = selection.GetTextCtrl()
+                        p['wxId'] = [ textWin.GetId(), ]
+                        textWin.Bind(wx.EVT_TEXT, self.OnSetValue)
+                    
+                    if p.get('prompt', '') ==  'vector':
+                        selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                        
+                        # if formatSelector and p.get('age', 'old') ==  'old':
+                        #     # OGR supported (read-only)
+                        #     self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
+                            
+                        #     self.hsizer.Add(item = selection,
+                        #                     flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_TOP,
+                        #                     border = 5)
+                            
+                        #     # format (native / ogr)
+                        #     rbox = wx.RadioBox(parent = which_panel, id = wx.ID_ANY,
+                        #                        label = " %s " % _("Format"),
+                        #                        style = wx.RA_SPECIFY_ROWS,
+                        #                        choices = [_("Native / Linked OGR"), _("Direct OGR")])
+                        #     if p.get('value', '').lower().rfind('@ogr') > -1:
+                        #         rbox.SetSelection(1)
+                        #     rbox.SetName('VectorFormat')
+                        #     rbox.Bind(wx.EVT_RADIOBOX, self.OnVectorFormat)
+                            
+                        #     self.hsizer.Add(item = rbox,
+                        #                     flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT |
+                        #                     wx.RIGHT | wx.ALIGN_TOP,
+                        #                     border = 5)
+                            
+                        #     ogrSelection = gselect.GdalSelect(parent = self, panel = which_panel, ogr = True,
+                        #                                       default = 'dir',
+                        #                                       exclude = ['file'])
+                        #     self.Bind(gselect.EVT_GDALSELECT, self.OnUpdateSelection)
+                        #     self.Bind(gselect.EVT_GDALSELECT, self.OnSetValue)
+                            
+                        #     ogrSelection.SetName('OgrSelect')
+                        #     ogrSelection.Hide()
+                            
+                        #     which_sizer.Add(item = self.hsizer, proportion = 0)
+                            
+                        #     p['wxId'].append(rbox.GetId())
+                        #     p['wxId'].append(ogrSelection.GetId())
+                        #     for win in ogrSelection.GetDsnWin():
+                        #         p['wxId'].append(win.GetId())
+                        # else:
+                        which_sizer.Add(item = selection, proportion = 0,
+                                        flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
+                                        border = 5)
+                    elif p.get('prompt', '') ==  'group':
+                        selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                        which_sizer.Add(item = selection, proportion = 0,
+                                        flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
+                                        border = 5)
+                    else:
+                        which_sizer.Add(item = selection, proportion = 0,
+                                        flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
+                                        border = 5)
+                # subgroup
+                elif p.get('prompt', '') ==  'subgroup':
+                    selection = gselect.SubGroupSelect(parent = which_panel)
+                    p['wxId'] = [ selection.GetId() ]
+                    selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                    which_sizer.Add(item = selection, proportion = 0,
+                                    flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
+                                    border = 5)
+
+                # layer, dbdriver, dbname, dbcolumn, dbtable entry
+                elif p.get('prompt', '') in ('dbdriver',
+                                             'dbname',
+                                             'dbtable',
+                                             'dbcolumn',
+                                             'layer',
+                                             'layer_all',
+                                             'location',
+                                             'mapset',
+                                             'dbase'):
+                    if p.get('multiple', 'no') ==  'yes':
+                        win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
+                                          size = globalvar.DIALOG_TEXTCTRL_SIZE)
+                        win.Bind(wx.EVT_TEXT, self.OnSetValue)
+                    else:
+                        value = self._getValue(p)
+                        
+                        if p.get('prompt', '') in ('layer',
+                                                   'layer_all'):
+                            if p.get('prompt', '') ==  'layer_all':
+                                all = True
+                            else:
+                                all = False
+                            if p.get('age', 'old') ==  'old':
+                                win = gselect.LayerSelect(parent = which_panel,
+                                                          all = all,
+                                                          default = p['default'])
+                                win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                                win.Bind(wx.EVT_TEXT, self.OnSetValue)
+                                win.SetValue(str(value))    # default or previously set value
+                            else:
+                                win = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY,
+                                                  min = 1, max = 100, initial = int(p['default']))
+                                win.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
+                                win.SetValue(int(value))    # default or previously set value
+
+                            p['wxId'] = [ win.GetId() ]
+
+                        elif p.get('prompt', '') ==  'dbdriver':
+                            win = gselect.DriverSelect(parent = which_panel,
+                                                       choices = p.get('values', []),
+                                                       value = value)
+                            win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
+                            win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                        elif p.get('prompt', '') ==  'dbname':
+                            win = gselect.DatabaseSelect(parent = which_panel,
+                                                         value = value)
+                            win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                            win.Bind(wx.EVT_TEXT, self.OnSetValue)
+                        elif p.get('prompt', '') ==  'dbtable':
+                            if p.get('age', 'old') ==  'old':
+                                win = gselect.TableSelect(parent = which_panel)
+                                win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
+                                win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                            else:
+                                win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
+                                                  size = globalvar.DIALOG_TEXTCTRL_SIZE)
+                                win.Bind(wx.EVT_TEXT, self.OnSetValue)
+                        elif p.get('prompt', '') ==  'dbcolumn':
+                            win = gselect.ColumnSelect(parent = which_panel,
+                                                       value = value,
+                                                       param = p)
+                            win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
+                            win.Bind(wx.EVT_TEXT,     self.OnSetValue)
+
+                        elif p.get('prompt', '') ==  'location':
+                            win = gselect.LocationSelect(parent = which_panel,
+                                                         value = value)
+                            win.Bind(wx.EVT_COMBOBOX,     self.OnUpdateSelection)
+                            win.Bind(wx.EVT_COMBOBOX,     self.OnSetValue)
+                        
+                        elif p.get('prompt', '') == 'mapset':
+                            if p.get('age', 'old') == 'old':
+                                new = False
+                            else:
+                                new = True
+                            win = gselect.MapsetSelect(parent = which_panel,
+                                                       value = value, new = new)
+                            win.Bind(wx.EVT_COMBOBOX,     self.OnUpdateSelection)
+                            win.Bind(wx.EVT_COMBOBOX,     self.OnSetValue) 
+                            win.Bind(wx.EVT_TEXT,         self.OnSetValue)
+                            
+                        elif p.get('prompt', '') ==  'dbase':
+                            win = gselect.DbaseSelect(parent = which_panel,
+                                                      changeCallback = self.OnSetValue)
+                            win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
+                            p['wxId'] = [ win.GetChildren()[1].GetId() ]
+                            
+                    if 'wxId' not in p:
+                        try:
+                            p['wxId'] = [ win.GetId(), ]
+                        except AttributeError:
+                            pass
+                    
+                    which_sizer.Add(item = win, proportion = 0,
+                                    flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
+                # color entry
+                elif p.get('prompt', '') in ('color',
+                                             'color_none'):
+                    default_color = (200,200,200)
+                    label_color = _("Select Color")
+                    if p.get('default','') !=  '':
+                        default_color, label_color = color_resolve(p['default'])
+                    if p.get('value','') !=  '': # parameter previously set
+                        default_color, label_color = color_resolve(p['value'])
+                    if p.get('prompt', '') ==  'color_none':
+                        this_sizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+                    else:
+                        this_sizer = which_sizer
+                    btn_colour = csel.ColourSelect(parent = which_panel, id = wx.ID_ANY,
+                                                   label = label_color, colour = default_color,
+                                                   pos = wx.DefaultPosition, size = (150,-1))
+                    this_sizer.Add(item = btn_colour, proportion = 0,
+                                   flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
+                    # For color selectors, this is a two-member array, holding the IDs of
+                    # the selector proper and either a "transparent" button or None
+                    p['wxId'] = [btn_colour.GetId(),]
+                    btn_colour.Bind(csel.EVT_COLOURSELECT,  self.OnColorChange)
+                    if p.get('prompt', '') ==  'color_none':
+                        none_check = wx.CheckBox(which_panel, wx.ID_ANY, _("Transparent"))
+                        if p.get('value','') !=  '' and p.get('value',[''])[0] ==  "none":
+                            none_check.SetValue(True)
+                        else:
+                            none_check.SetValue(False)
+                        this_sizer.Add(item = none_check, proportion = 0,
+                                       flag = wx.ADJUST_MINSIZE | wx.LEFT | wx.RIGHT | wx.TOP, border = 5)
+                        which_sizer.Add(this_sizer)
+                        none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange)
+                        p['wxId'].append(none_check.GetId())
+                    else:
+                        p['wxId'].append(None)
+                # file selector
+                elif p.get('prompt','') !=  'color' and p.get('element', '') ==  'file':
+                    if p.get('age', 'new') == 'new':
+                        fmode = wx.SAVE
+                    else:
+                        fmode = wx.OPEN
+                    fbb = filebrowse.FileBrowseButton(parent = which_panel, id = wx.ID_ANY, fileMask = '*',
+                                                      size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
+                                                      dialogTitle = _('Choose %s') % \
+                                                          p.get('description',_('File')),
+                                                      buttonText = _('Browse'),
+                                                      startDirectory = os.getcwd(), fileMode = fmode,
+                                                      changeCallback = self.OnSetValue)
+                    value = self._getValue(p)
+                    if value:
+                        fbb.SetValue(value) # parameter previously set
+                    which_sizer.Add(item = fbb, proportion = 0,
+                                    flag = wx.EXPAND | wx.RIGHT, border = 5)
+                    
+                    # A file browse button is a combobox with two children:
+                    # a textctl and a button;
+                    # we have to target the button here
+                    p['wxId'] = [ fbb.GetChildren()[1].GetId() ]
+                    if p.get('age', 'new') == 'old' and \
+                            UserSettings.Get(group = 'cmd', key = 'interactiveInput', subkey = 'enabled'):
+                        # widget for interactive input
+                        ifbb = wx.TextCtrl(parent = which_panel, id = wx.ID_ANY,
+                                           style = wx.TE_MULTILINE,
+                                           size = (-1, 75))
+                        if p.get('value', '') and os.path.isfile(p['value']):
+                            f = open(p['value'])
+                            ifbb.SetValue(''.join(f.readlines()))
+                            f.close()
+                        
+                        ifbb.Bind(wx.EVT_TEXT, self.OnFileText)
+                        
+                        btnLoad = wx.Button(parent = which_panel, id = wx.ID_ANY, label = _("&Load"))
+                        btnLoad.Bind(wx.EVT_BUTTON, self.OnFileLoad)
+                        btnSave = wx.Button(parent = which_panel, id = wx.ID_SAVEAS)
+                        btnSave.Bind(wx.EVT_BUTTON, self.OnFileSave)
+                        
+                        which_sizer.Add(item = wx.StaticText(parent = which_panel, id = wx.ID_ANY,
+                                                             label = _('or enter values interactively')),
+                                        proportion = 0,
+                                        flag = wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM, border = 5)
+                        which_sizer.Add(item = ifbb, proportion = 1,
+                                        flag = wx.EXPAND | wx.RIGHT | wx.LEFT, border = 5)
+                        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+                        btnSizer.Add(item = btnLoad, proportion = 0,
+                                     flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 10)
+                        btnSizer.Add(item = btnSave, proportion = 0,
+                                     flag = wx.ALIGN_RIGHT)
+                        which_sizer.Add(item = btnSizer, proportion = 0,
+                                        flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
+                        
+                        p['wxId'].append(ifbb.GetId())
+                        p['wxId'].append(btnLoad.GetId())
+                        p['wxId'].append(btnSave.GetId())
+                
+                # directory selector
+                elif p.get('prompt','') != 'color' and p.get('element', '') ==  'dir':
+                    fbb = filebrowse.DirBrowseButton(parent = which_panel, id = wx.ID_ANY,
+                                                     size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
+                                                     dialogTitle = _('Choose %s') % \
+                                                         p.get('description', _('Directory')),
+                                                     buttonText = _('Browse'),
+                                                     startDirectory = os.getcwd(),
+                                                     changeCallback = self.OnSetValue)
+                    value = self._getValue(p)
+                    if value:
+                        fbb.SetValue(value) # parameter previously set
+                    which_sizer.Add(item = fbb, proportion = 0,
+                                    flag = wx.EXPAND | wx.RIGHT, border = 5)
+                    
+                    # A file browse button is a combobox with two children:
+                    # a textctl and a button;
+                    # we have to target the button here
+                    p['wxId'] = [ fbb.GetChildren()[1].GetId() ]
+
+            if self.parent.GetName() == 'MainFrame' and self.parent.modeler:
+                parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
+                                     label = _("Parameterized in model"))
+                parChk.SetName('ModelParam')
+                parChk.SetValue(p.get('parameterized', False))
+                if 'wxId' in p:
+                    p['wxId'].append(parChk.GetId())
+                else:
+                    p['wxId'] = [ parChk.GetId() ]
+                parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
+                which_sizer.Add(item = parChk, proportion = 0,
+                                flag = wx.LEFT, border = 20)
+                
+            if title_txt is not None:
+                # create tooltip if given
+                if len(p['values_desc']) > 0:
+                    if tooltip:
+                        tooltip +=  2 * os.linesep
+                    else:
+                        tooltip = ''
+                    if len(p['values']) ==  len(p['values_desc']):
+                        for i in range(len(p['values'])):
+                            tooltip +=  p['values'][i] + ': ' + p['values_desc'][i] + os.linesep
+                    tooltip.strip(os.linesep)
+                if tooltip:
+                    title_txt.SetToolTipString(tooltip)
+
+            if p ==  first_param:
+                if 'wxId' in p and len(p['wxId']) > 0:
+                    win = self.FindWindowById(p['wxId'][0])
+                    win.SetFocus()
+        
+        #
+        # set widget relations for OnUpdateSelection
+        #
+        pMap = None
+        pLayer = []
+        pDriver = None
+        pDatabase = None
+        pTable = None
+        pColumn = []
+        pGroup = None
+        pSubGroup = None
+        pDbase = None
+        pLocation = None
+        pMapset = None
+        for p in self.task.params:
+            if p.get('gisprompt', False) ==  False:
+                continue
+            
+            guidep = p.get('guidependency', '')
+            
+            if guidep:
+                # fixed options dependency defined
+                options = guidep.split(',')
+                for opt in options:
+                    pOpt = self.task.get_param(opt, element = 'name', raiseError = False)
+                    if id:
+                        if 'wxId-bind' not in p:
+                            p['wxId-bind'] = list()
+                        p['wxId-bind'] +=  pOpt['wxId']
+                continue
+            
+            prompt = p.get('element', '')
+            if prompt in ('cell', 'vector'):
+                name = p.get('name', '')
+                if name in ('map', 'input'):
+                    pMap = p
+            elif prompt ==  'layer':
+                pLayer.append(p)
+            elif prompt ==  'dbcolumn':
+                pColumn.append(p)
+            elif prompt ==  'dbdriver':
+                pDriver = p
+            elif prompt ==  'dbname':
+                pDatabase = p
+            elif prompt ==  'dbtable':
+                pTable = p
+            elif prompt ==  'group':
+                pGroup = p
+            elif prompt ==  'subgroup':
+                pSubGroup = p
+            elif prompt ==  'dbase':
+                pDbase = p
+            elif prompt ==  'location':
+                pLocation = p
+            elif prompt ==  'mapset':
+                pMapset = p
+        
+        # collect ids
+        pColumnIds = []
+        for p in pColumn:
+            pColumnIds +=  p['wxId']
+        pLayerIds = []
+        for p in pLayer:
+            pLayerIds +=  p['wxId']
+        
+        # set wxId-bindings
+        if pMap:
+            pMap['wxId-bind'] = copy.copy(pColumnIds)
+            if pLayer:
+                pMap['wxId-bind'] +=  pLayerIds
+        if pLayer:
+            for p in pLayer:
+                p['wxId-bind'] = copy.copy(pColumnIds)
+        
+        if pDriver and pTable:
+            pDriver['wxId-bind'] = pTable['wxId']
+
+        if pDatabase and pTable:
+            pDatabase['wxId-bind'] = pTable['wxId']
+
+        if pTable and pColumnIds:
+            pTable['wxId-bind'] = pColumnIds
+        
+        if pGroup and pSubGroup:
+            pGroup['wxId-bind'] = pSubGroup['wxId']
+
+        if pDbase and pLocation:
+            pDbase['wxId-bind'] = pLocation['wxId']
+
+        if pLocation and pMapset:
+            pLocation['wxId-bind'] = pMapset['wxId']
+        
+        if pLocation and pMapset and pMap:
+            pLocation['wxId-bind'] +=  pMap['wxId']
+            pMapset['wxId-bind'] = pMap['wxId']
+        
+	#
+	# determine panel size
+	#
+        maxsizes = (0, 0)
+        for section in sections:
+            tab[section].SetSizer(tabsizer[section])
+            tabsizer[section].Fit(tab[section])
+            tab[section].Layout()
+            minsecsizes = tabsizer[section].GetSize()
+            maxsizes = map(lambda x: max(maxsizes[x], minsecsizes[x]), (0, 1))
+
+        # TODO: be less arbitrary with these 600
+        self.panelMinHeight = 100
+        self.constrained_size = (min(600, maxsizes[0]) + 25, min(400, maxsizes[1]) + 25)
+        for section in sections:
+            tab[section].SetMinSize((self.constrained_size[0], self.panelMinHeight))
+        
+        if self.manual_tab.IsLoaded():
+            self.manual_tab.SetMinSize((self.constrained_size[0], self.panelMinHeight))
+        
+        self.SetSizer(panelsizer)
+        panelsizer.Fit(self.notebook)
+        
+        self.Bind(EVT_DIALOG_UPDATE, self.OnUpdateDialog)
+
+    def _getValue(self, p):
+        """!Get value or default value of given parameter
+
+        @param p parameter directory
+        """
+        if p.get('value', '') !=  '':
+            return p['value']
+        return p.get('default', '')
+        
+    def OnFileLoad(self, event):
+        """!Load file to interactive input"""
+        me = event.GetId()
+        win = dict()
+        for p in self.task.params:
+            if 'wxId' in p and me in p['wxId']:
+                win['file'] = self.FindWindowById(p['wxId'][0])
+                win['text'] = self.FindWindowById(p['wxId'][1])
+                break
+        
+        if not win:
+            return
+        
+        path = win['file'].GetValue()
+        if not path:
+            gcmd.GMessage(parent = self,
+                          message = _("Nothing to load."))
+            return
+        
+        data = ''
+        f = open(path, "r")
+        try:
+            data = f.read()
+        finally:
+            f.close()
+        
+        win['text'].SetValue(data)
+        
+    def OnFileSave(self, event):
+        """!Save interactive input to the file"""
+        wId = event.GetId()
+        win = {}
+        for p in self.task.params:
+            if wId in p.get('wxId', []):
+                win['file'] = self.FindWindowById(p['wxId'][0])
+                win['text'] = self.FindWindowById(p['wxId'][1])
+                break
+        
+        if not win:
+            return
+
+        text = win['text'].GetValue()
+        if not text:
+            gcmd.GMessage(parent = self,
+                          message = _("Nothing to save."))
+            return
+        
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Save input as..."),
+                            defaultDir = os.getcwd(),
+                            style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
+
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            f = open(path, "w")
+            try:
+                f.write(text + os.linesep)
+            finally:
+                f.close()
+            
+            win['file'].SetValue(path)
+        
+        dlg.Destroy()
+        
+    def OnFileText(self, event):
+        """File input interactively entered"""
+        text = event.GetString()
+        p = self.task.get_param(value = event.GetId(), element = 'wxId', raiseError = False)
+        if not p:
+            return # should not happen
+        win = self.FindWindowById(p['wxId'][0])
+        if text:
+            filename = win.GetValue()
+            if not filename:
+                # outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
+                filename = grass.tempfile()
+                win.SetValue(filename)
+                
+            f = open(filename, "w")
+            try:
+                f.write(text)
+                if text[-1] != os.linesep:
+                    f.write(os.linesep)
+            finally:
+                f.close()
+        else:
+            win.SetValue('')
+        
+    def OnVectorFormat(self, event):
+        """!Change vector format (native / ogr)"""
+        sel = event.GetSelection()
+        idEvent = event.GetId()
+        p = self.task.get_param(value = idEvent, element = 'wxId', raiseError = False)
+        if not p:
+            return # should not happen
+        
+        # detect windows
+        winNative = None
+        winOgr = None
+        for id in p['wxId']:
+            if id ==  idEvent:
+                continue
+            name = self.FindWindowById(id).GetName()
+            if name ==  'Select':
+                winNative = self.FindWindowById(id + 1)  # fix the mystery (also in nviz_tools.py)
+            elif name ==  'OgrSelect':
+                winOgr = self.FindWindowById(id)
+        
+        # enable / disable widgets & update values
+        rbox = self.FindWindowByName('VectorFormat')
+        self.hsizer.Remove(rbox)
+        if sel ==  0:   # -> native
+            winOgr.Hide()
+            self.hsizer.Remove(winOgr)
+            
+            self.hsizer.Add(item = winNative,
+                            flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_TOP,
+                            border = 5)
+            winNative.Show()
+            p['value'] = winNative.GetValue()
+        
+        elif sel ==  1: # -> OGR
+            sizer = wx.BoxSizer(wx.VERTICAL)
+            
+            winNative.Hide()
+            self.hsizer.Remove(winNative)
+
+            sizer.Add(item = winOgr)
+            winOgr.Show()
+            p['value'] = winOgr.GetDsn()
+            
+            self.hsizer.Add(item = sizer,
+                            flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_TOP,
+                            border = 5)
+        
+        self.hsizer.Add(item = rbox,
+                        flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT |
+                        wx.RIGHT | wx.ALIGN_TOP,
+                        border = 5)
+        
+        self.hsizer.Layout()
+        self.Layout()
+        self.OnUpdateValues()
+        self.OnUpdateSelection(event)
+        
+    def OnUpdateDialog(self, event):
+        for fn, kwargs in event.data.iteritems():
+            fn(**kwargs)
+        
+        self.parent.updateValuesHook()
+        
+    def OnVerbosity(self, event):
+        """!Verbosity level changed"""
+        verbose = self.FindWindowById(self.task.get_flag('verbose')['wxId'][0])
+        quiet = self.FindWindowById(self.task.get_flag('quiet')['wxId'][0])
+        if event.IsChecked():
+            if event.GetId() ==  verbose.GetId():
+                if quiet.IsChecked():
+                    quiet.SetValue(False)
+                    self.task.get_flag('quiet')['value'] = False
+            else:
+                if verbose.IsChecked():
+                    verbose.SetValue(False)
+                    self.task.get_flag('verbose')['value'] = False
+
+        event.Skip()
+
+    def OnPageChange(self, event):
+        if not event:
+            sel = self.notebook.GetSelection()
+        else:
+            sel = event.GetSelection()
+        
+        idx = self.notebook.GetPageIndexByName('manual')
+        if idx > -1 and sel ==  idx:
+            # calling LoadPage() is strangely time-consuming (only first call)
+            # FIXME: move to helpPage.__init__()
+            if not self.manual_tab.IsLoaded():
+                wx.Yield()
+                self.manual_tab.LoadPage()
+
+        self.Layout()
+
+    def OnColorChange(self, event):
+        myId = event.GetId()
+        for p in self.task.params:
+            if 'wxId' in p and myId in p['wxId']:
+                has_button = p['wxId'][1] is not None
+                if has_button and wx.FindWindowById(p['wxId'][1]).GetValue() ==  True:
+                    p[ 'value' ] = 'none'
+                else:
+                    colorchooser = wx.FindWindowById(p['wxId'][0])
+                    new_color = colorchooser.GetValue()[:]
+                    # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple
+                    # under wx2.8.1
+                    new_label = rgb2str.get(new_color, ':'.join(map(str,new_color)))
+                    colorchooser.SetLabel(new_label)
+                    colorchooser.SetColour(new_color)
+                    colorchooser.Refresh()
+                    p[ 'value' ] = colorchooser.GetLabel()
+        self.OnUpdateValues()
+
+    def OnUpdateValues(self, event = None):
+        """!If we were part of a richer interface, report back the
+        current command being built.
+
+        This method should be set by the parent of this panel if
+        needed. It's a hook, actually.  Beware of what is 'self' in
+        the method def, though. It will be called with no arguments.
+        """
+        pass
+
+    def OnCheckBoxMulti(self, event):
+        """!Fill the values as a ','-separated string according to
+        current status of the checkboxes.
+        """
+        me = event.GetId()
+        theParam = None
+        for p in self.task.params:
+            if 'wxId' in p and me in p['wxId']:
+                theParam = p
+                myIndex = p['wxId'].index(me)
+
+        # Unpack current value list
+        currentValues = {}
+        for isThere in theParam.get('value', '').split(','):
+            currentValues[isThere] = 1
+        theValue = theParam['values'][myIndex]
+
+        if event.Checked():
+            currentValues[ theValue ] = 1
+        else:
+            del currentValues[ theValue ]
+
+        # Keep the original order, so that some defaults may be recovered
+        currentValueList = [] 
+        for v in theParam['values']:
+            if v in currentValues:
+                currentValueList.append(v)
+
+        # Pack it back
+        theParam['value'] = ','.join(currentValueList)
+
+        self.OnUpdateValues()
+
+    def OnSetValue(self, event):
+        """!Retrieve the widget value and set the task value field
+        accordingly.
+
+        Use for widgets that have a proper GetValue() method, i.e. not
+        for selectors.
+        """
+        myId = event.GetId()
+        me = wx.FindWindowById(myId)
+        name = me.GetName()
+
+        found = False
+        for porf in self.task.params + self.task.flags:
+            if 'wxId' not in porf:
+                continue
+            if myId in porf['wxId']:
+                found = True
+                break
+        
+        if not found:
+            return
+        
+        if name ==  'GdalSelect':
+            porf['value'] = event.dsn
+        elif name ==  'ModelParam':
+            porf['parameterized'] = me.IsChecked()
+        else:
+            porf['value'] = me.GetValue()
+
+        self.OnUpdateValues(event)
+        
+        event.Skip()
+        
+    def OnUpdateSelection(self, event):
+        """!Update dialog (layers, tables, columns, etc.)
+        """
+        if not hasattr(self.parent, "updateThread"):
+            if event:
+                event.Skip()
+            return
+        if event:
+            self.parent.updateThread.Update(UpdateDialog,
+                                            self,
+                                            event,
+                                            event.GetId(),
+                                            self.task)
+        else:
+            self.parent.updateThread.Update(UpdateDialog,
+                                            self,
+                                            None,
+                                            None,
+                                            self.task)
+            
+    def createCmd(self, ignoreErrors = False, ignoreRequired = False):
+        """!Produce a command line string (list) or feeding into GRASS.
+
+        @param ignoreErrors True then it will return whatever has been
+        built so far, even though it would not be a correct command
+        for GRASS
+        """
+        try:
+            cmd = self.task.get_cmd(ignoreErrors = ignoreErrors,
+                                   ignoreRequired = ignoreRequired)
+        except ValueError, err:
+            dlg = wx.MessageDialog(parent = self,
+                                   message = unicode(err),
+                                   caption = _("Error in %s") % self.task.name,
+                                   style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
+            dlg.ShowModal()
+            dlg.Destroy()
+            cmd = None
+        
+        return cmd
+    
+    def OnSize(self, event):
+        width = event.GetSize()[0]
+        fontsize = self.GetFont().GetPointSize()
+        text_width = max(width / (fontsize - 3), 70)
+        
+        for id in self.label_id:
+            win = self.FindWindowById(id)
+            label = win.GetLabel()
+            label_new = '\n'.join(textwrap.wrap(label, text_width))
+            win.SetLabel(label_new)
+            
+        event.Skip()
+        
+class GrassGUIApp(wx.App):
+    """!Stand-alone GRASS command GUI
+    """
+    def __init__(self, grass_task):
+        self.grass_task = grass_task
+        wx.App.__init__(self, False)
+        
+    def OnInit(self):
+        msg = self.grass_task.get_error_msg()
+        if msg:
+            gcmd.GError(msg + '\n\nTry to set up GRASS_ADDON_PATH variable.')
+            return True
+        
+        self.mf = mainFrame(parent = None, ID = wx.ID_ANY, task_description = self.grass_task)
+        self.mf.CentreOnScreen()
+        self.mf.Show(True)
+        self.SetTopWindow(self.mf)
+        
+        return True
+
+class GUI:
+    def __init__(self, parent = None, show = True, modal = False,
+                 centreOnParent = False, checkError = False):
+        """!Parses GRASS commands when module is imported and used from
+        Layer Manager.
+        """
+        self.parent = parent
+        self.show   = show
+        self.modal  = modal
+        self.centreOnParent = centreOnParent
+        self.checkError     = checkError
+        
+        self.grass_task = None
+        self.cmd = list()
+        
+        global _blackList
+        if self.parent:
+            _blackList['enabled'] = True
+        else:
+            _blackList['enabled'] = False
+        
+    def GetCmd(self):
+        """!Get validated command"""
+        return self.cmd
+    
+    def ParseCommand(self, cmd, gmpath = None, completed = None):
+        """!Parse command
+        
+        Note: cmd is given as list
+        
+        If command is given with options, return validated cmd list:
+         - add key name for first parameter if not given
+         - change mapname to mapname at mapset
+        """
+        start = time.time()
+        dcmd_params = {}
+        if completed == None:
+            get_dcmd = None
+            layer = None
+            dcmd_params = None
+        else:
+            get_dcmd = completed[0]
+            layer = completed[1]
+            if completed[2]:
+                dcmd_params.update(completed[2])
+        
+        # parse the interface decription
+        try:
+            global _blackList
+            self.grass_task = gtask.parse_interface(cmd[0],
+                                                    blackList = _blackList)
+        except ValueError, e: #grass.ScriptError, e:
+            gcmd.GError(e.value)
+            return
+        
+        # if layer parameters previously set, re-insert them into dialog
+        if completed is not None:
+            if 'params' in dcmd_params:
+                self.grass_task.params = dcmd_params['params']
+            if 'flags' in dcmd_params:
+                self.grass_task.flags = dcmd_params['flags']
+        
+        err = list()
+        # update parameters if needed && validate command
+        if len(cmd) > 1:
+            i = 0
+            cmd_validated = [cmd[0]]
+            for option in cmd[1:]:
+                if option[0] ==  '-': # flag
+                    if option[1] ==  '-':
+                        self.grass_task.set_flag(option[2:], True)
+                    else:
+                        self.grass_task.set_flag(option[1], True)
+                    cmd_validated.append(option)
+                else: # parameter
+                    try:
+                        key, value = option.split('=', 1)
+                    except:
+                        if self.grass_task.firstParam:
+                            if i == 0: # add key name of first parameter if not given
+                                key = self.grass_task.firstParam
+                                value = option
+                            else:
+                                raise gcmd.GException, _("Unable to parse command '%s'") % ' '.join(cmd)
+                        else:
+                            continue
+                    
+                    element = self.grass_task.get_param(key, raiseError = False)
+                    if not element:
+                        err.append(_("%(cmd)s: parameter '%(key)s' not available") % \
+                                       { 'cmd' : cmd[0],
+                                         'key' : key })
+                        continue
+                    element = element['element']
+                    
+                    if element in ['cell', 'vector']:
+                        # mapname -> mapname at mapset
+                        try:
+                            name, mapset = value.split('@')
+                        except ValueError:
+                            mapset = grass.find_file(value, element)['mapset']
+                            curr_mapset = grass.gisenv()['MAPSET']
+                            if mapset and mapset !=  curr_mapset:
+                                value = value + '@' + mapset
+                    
+                    self.grass_task.set_param(key, value)
+                    cmd_validated.append(key + '=' + value)
+                    i += 1
+            
+            # update original command list
+            cmd = cmd_validated
+        
+        if self.show is not None:
+            self.mf = mainFrame(parent = self.parent, ID = wx.ID_ANY,
+                                task_description = self.grass_task,
+                                get_dcmd = get_dcmd, layer = layer)
+        else:
+            self.mf = None
+        
+        if get_dcmd is not None:
+            # update only propwin reference
+            get_dcmd(dcmd = None, layer = layer, params = None,
+                     propwin = self.mf)
+        
+        if self.show is not None:
+            self.mf.notebookpanel.OnUpdateSelection(None)
+            if self.show is True:
+                if self.parent and self.centreOnParent:
+                    self.mf.CentreOnParent()
+                else:
+                    self.mf.CenterOnScreen()
+                self.mf.Show(self.show)
+                self.mf.MakeModal(self.modal)
+            else:
+                self.mf.OnApply(None)
+        
+        self.cmd = cmd
+        
+        if self.checkError:
+            return self.grass_task, err
+        else:
+            return self.grass_task
+    
+    def GetCommandInputMapParamKey(self, cmd):
+        """!Get parameter key for input raster/vector map
+        
+        @param cmd module name
+        
+        @return parameter key
+        @return None on failure
+        """
+        # parse the interface decription
+        if not self.grass_task:
+            enc = locale.getdefaultlocale()[1]
+            if enc and enc.lower() == "cp932":
+                p = re.compile('encoding="' + enc + '"', re.IGNORECASE)
+                tree = etree.fromstring(p.sub('encoding="utf-8"',
+                                              gtask.get_interface_description(cmd).decode(enc).encode('utf-8')))
+            else:
+                tree = etree.fromstring(gtask.get_interface_description(cmd))
+            self.grass_task = gtask.processTask(tree).get_task()
+            
+            for p in self.grass_task.params:
+                if p.get('name', '') in ('input', 'map'):
+                    age = p.get('age', '')
+                    prompt = p.get('prompt', '')
+                    element = p.get('element', '') 
+                    if age ==  'old' and \
+                            element in ('cell', 'grid3', 'vector') and \
+                            prompt in ('raster', '3d-raster', 'vector'):
+                        return p.get('name', None)
+        return None
+
+class FloatValidator(wx.PyValidator):
+    """!Validator for floating-point input"""
+    def __init__(self):
+        wx.PyValidator.__init__(self)
+        
+        self.Bind(wx.EVT_TEXT, self.OnText) 
+        
+    def Clone(self):
+        """!Clone validator"""
+        return FloatValidator()
+
+    def Validate(self):
+        """Validate input"""
+        textCtrl = self.GetWindow()
+        text = textCtrl.GetValue()
+
+        if text:
+            try:
+                float(text)
+            except ValueError:
+                textCtrl.SetBackgroundColour("grey")
+                textCtrl.SetFocus()
+                textCtrl.Refresh()
+                return False
+        
+        sysColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
+        textCtrl.SetBackgroundColour(sysColor)
+        
+        textCtrl.Refresh()
+        
+        return True
+
+    def OnText(self, event):
+        """!Do validation"""
+        self.Validate()
+        
+        event.Skip()
+        
+    def TransferToWindow(self):
+        return True # Prevent wxDialog from complaining.
+    
+    def TransferFromWindow(self):
+        return True # Prevent wxDialog from complaining.
+
+if __name__ ==  "__main__":
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+    
+    if len(sys.argv) ==  1:
+        sys.exit(_("usage: %s <grass command>") % sys.argv[0])
+    
+    if sys.argv[1] !=  'test':
+        q = wx.LogNull()
+        cmd = utils.split(sys.argv[1])
+        task = gtask.grassTask(cmd[0], blackList = _blackList)
+        task.set_options(cmd[1:])
+        app = GrassGUIApp(task)
+        app.MainLoop()
+    else: #Test
+        # Test grassTask from within a GRASS session
+        if os.getenv("GISBASE") is not None:
+            task = gtask.grassTask("d.vect")
+            task.get_param('map')['value'] = "map_name"
+            task.get_flag('v')['value'] = True
+            task.get_param('layer')['value'] = 1
+            task.get_param('bcolor')['value'] = "red"
+            assert ' '.join(task.get_cmd()) ==  "d.vect -v map = map_name layer = 1 bcolor = red"
+        # Test interface building with handmade grassTask,
+        # possibly outside of a GRASS session.
+        task = gtask.grassTask()
+        task.name = "TestTask"
+        task.description = "This is an artificial grassTask() object intended for testing purposes."
+        task.keywords = ["grass","test","task"]
+        task.params = [
+            {
+            "name" : "text",
+            "description" : "Descriptions go into tooltips if labels are present, like this one",
+            "label" : "Enter some text",
+            },{
+            "name" : "hidden_text",
+            "description" : "This text should not appear in the form",
+            "hidden" : True
+            },{
+            "name" : "text_default",
+            "description" : "Enter text to override the default",
+            "default" : "default text"
+            },{
+            "name" : "text_prefilled",
+            "description" : "You should see a friendly welcome message here",
+            "value" : "hello, world"
+            },{
+            "name" : "plain_color",
+            "description" : "This is a plain color, and it is a compulsory parameter",
+            "required" : False,
+            "gisprompt" : True,
+            "prompt" : "color"
+            },{
+            "name" : "transparent_color",
+            "description" : "This color becomes transparent when set to none",
+            "guisection" : "tab",
+            "gisprompt" : True,
+            "prompt" : "color"
+            },{
+            "name" : "multi",
+            "description" : "A multiple selection",
+            'default': u'red,green,blue',
+            'gisprompt': False,
+            'guisection': 'tab',
+            'multiple': u'yes',
+            'type': u'string',
+            'value': '',
+            'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other']
+            },{
+            "name" : "single",
+            "description" : "A single multiple-choice selection",
+            'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'],
+            "guisection" : "tab"
+            },{
+            "name" : "large_multi",
+            "description" : "A large multiple selection",
+            "gisprompt" : False,
+            "multiple" : "yes",
+            # values must be an array of strings
+            "values" : str2rgb.keys() + map(str, str2rgb.values())
+            },{
+            "name" : "a_file",
+            "description" : "A file selector",
+            "gisprompt" : True,
+            "element" : "file"
+            }
+            ]
+        task.flags = [
+            {
+            "name" : "a",
+            "description" : "Some flag, will appear in Main since it is required",
+            "required" : True
+            },{
+            "name" : "b",
+            "description" : "pre-filled flag, will appear in options since it is not required",
+            "value" : True
+            },{
+            "name" : "hidden_flag",
+            "description" : "hidden flag, should not be changeable",
+            "hidden" : "yes",
+            "value" : True
+            }
+            ]
+        q = wx.LogNull()
+        GrassGUIApp(task).MainLoop()
+

Copied: grass/trunk/gui/wxpython/gui_core/ghelp.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/ghelp.py)
===================================================================
--- grass/trunk/gui/wxpython/gui_core/ghelp.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/ghelp.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,1299 @@
+"""!
+ at package gui_core.ghelp
+
+ at brief Help window
+
+Classes:
+ - SearchModuleWindow
+ - ItemTree
+ - MenuTreeWindow
+ - MenuTree
+ - AboutWindow
+ - InstallExtensionWindow
+ - ExtensionTree
+ - HelpFrame
+ - HelpWindow
+ - HelpPanel
+
+(C) 2008-2011 by the GRASS Development Team
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Martin Landa <landa.martin gmail.com>
+"""
+
+import os
+import codecs
+
+import wx
+try:
+    import wx.lib.agw.customtreectrl as CT
+    from wx.lib.agw.hyperlink import HyperLinkCtrl
+except ImportError:
+    import wx.lib.customtreectrl as CT
+    from wx.lib.hyperlink import HyperLinkCtrl
+import wx.lib.flatnotebook as FN
+import  wx.lib.scrolledpanel as scrolled
+
+import grass.script as grass
+from grass.script import task as gtask
+
+from core             import globalvar
+from core             import utils
+from lmgr.menudata    import ManagerData
+from core.gcmd        import GError, RunCommand, DecodeString
+from gui_core.widgets import GNotebook
+from core.forms       import GUI
+from gui_core.dialogs import StaticWrapText
+
+class HelpFrame(wx.Frame):
+    """!GRASS Quickstart help window"""
+    def __init__(self, parent, id, title, size, file):
+        wx.Frame.__init__(self, parent = parent, id = id, title = title, size = size)
+        
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # text
+        content = HelpPanel(parent = self)
+        content.LoadPage(file)
+        
+        sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
+        
+        self.SetAutoLayout(True)
+        self.SetSizer(sizer)
+        self.Layout()
+
+class SearchModuleWindow(wx.Panel):
+    """!Search module window (used in MenuTreeWindow)"""
+    def __init__(self, parent, id = wx.ID_ANY, cmdPrompt = None,
+                 showChoice = True, showTip = False, **kwargs):
+        self.showTip    = showTip
+        self.showChoice = showChoice
+        self.cmdPrompt  = cmdPrompt
+        
+        wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
+        
+        self._searchDict = { _('description') : 'description',
+                             _('command')     : 'command',
+                             _('keywords')    : 'keywords' }
+        
+        self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                label = " %s " % _("Find module(s)"))
+        
+        self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY,
+                                  choices = [_('description'),
+                                             _('keywords'),
+                                             _('command')])
+        self.searchBy.SetSelection(0)
+        
+        self.search = wx.TextCtrl(parent = self, id = wx.ID_ANY,
+                                  value = "", size = (-1, 25),
+                                  style = wx.TE_PROCESS_ENTER)
+        self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
+        
+        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)
+            if self.cmdPrompt:
+                self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
+            self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
+        
+        self._layout()
+
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
+        gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
+        gridSizer.AddGrowableCol(1)
+        
+        gridSizer.Add(item = self.searchBy,
+                      flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
+        gridSizer.Add(item = self.search,
+                      flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
+        row = 1
+        if self.showTip:
+            gridSizer.Add(item = self.searchTip,
+                          flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
+            row += 1
+        
+        if self.showChoice:
+            gridSizer.Add(item = self.searchChoice,
+                          flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
+        
+        sizer.Add(item = gridSizer, proportion = 1)
+        
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def GetSelection(self):
+        """!Get selected element"""
+        selection = self.searchBy.GetStringSelection()
+        
+        return self._searchDict[selection]
+
+    def SetSelection(self, i):
+        """!Set selection element"""
+        self.searchBy.SetSelection(i)
+
+    def OnSearchModule(self, event):
+        """!Search module by keywords or description"""
+        if not self.cmdPrompt:
+            event.Skip()
+            return
+        
+        text = event.GetString()
+        if not text:
+            self.cmdPrompt.SetFilter(None)
+            mList = self.cmdPrompt.GetCommandItems()
+            self.searchChoice.SetItems(mList)
+            if self.showTip:
+                self.searchTip.SetLabel(_("%d modules found") % len(mList))
+            event.Skip()
+            return
+        
+        modules = dict()
+        iFound = 0
+        for module, data in self.cmdPrompt.moduleDesc.iteritems():
+            found = False
+            sel = self.searchBy.GetSelection()
+            if sel == 0: # -> description
+                if text in data['desc']:
+                    found = True
+            elif sel == 1: # keywords
+                if text in ','.join(data['keywords']):
+                    found = True
+            else: # command
+                if module[:len(text)] == text:
+                    found = True
+            
+            if found:
+                iFound += 1
+                try:
+                    group, name = module.split('.')
+                except ValueError:
+                    continue # TODO
+                
+                if group not in modules:
+                    modules[group] = list()
+                modules[group].append(name)
+                
+        self.cmdPrompt.SetFilter(modules)
+        self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
+        if self.showTip:
+            self.searchTip.SetLabel(_("%d modules found") % iFound)
+        
+        event.Skip()
+        
+    def OnSelectModule(self, event):
+        """!Module selected from choice, update command prompt"""
+        cmd  = event.GetString().split(' ', 1)[0]
+        text = cmd + ' '
+        pos = len(text)
+
+        if self.cmdPrompt:
+            self.cmdPrompt.SetText(text)
+            self.cmdPrompt.SetSelectionStart(pos)
+            self.cmdPrompt.SetCurrentPos(pos)
+            self.cmdPrompt.SetFocus()
+        
+        desc = self.cmdPrompt.GetCommandDesc(cmd)
+        if self.showTip:
+            self.searchTip.SetLabel(desc)
+    
+    def Reset(self):
+        """!Reset widget"""
+        self.searchBy.SetSelection(0)
+        self.search.SetValue('')
+        if self.showTip:
+            self.searchTip.SetLabel('')
+        
+class MenuTreeWindow(wx.Panel):
+    """!Show menu tree"""
+    def __init__(self, parent, id = wx.ID_ANY, **kwargs):
+        self.parent = parent # LayerManager
+        
+        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 to run command)"))
+        # tree
+        self.tree = MenuTree(parent = self, data = ManagerData())
+        self.tree.Load()
+
+        # search widget
+        self.search = SearchModuleWindow(parent = self, showChoice = False)
+        
+        # buttons
+        self.btnRun   = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
+        self.btnRun.SetToolTipString(_("Run selected command"))
+        self.btnRun.Enable(False)
+        
+        # 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.Bind(wx.EVT_TEXT_ENTER,        self.OnShowItem)
+        self.search.Bind(wx.EVT_TEXT,              self.OnUpdateStatusBar)
+        
+        self._layout()
+        
+        self.search.SetFocus()
+        
+    def _layout(self):
+        """!Do dialog layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        # body
+        dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
+        dataSizer.Add(item = self.tree, proportion =1,
+                      flag = wx.EXPAND)
+        
+        # buttons
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(item = self.btnRun, proportion = 0)
+        
+        sizer.Add(item = dataSizer, proportion = 1,
+                  flag = wx.EXPAND | wx.ALL, border = 5)
+
+        sizer.Add(item = self.search, proportion = 0,
+                  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
+        
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT, border = 5)
+        
+        sizer.Fit(self)
+        sizer.SetSizeHints(self)
+        
+        self.SetSizer(sizer)
+        
+        self.Fit()
+        self.SetAutoLayout(True)        
+        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
+        
+        data = self.tree.GetPyData(self.tree.GetSelected())
+        if not data:
+            return
+
+        handler = 'self.parent.' + data['handler'].lstrip('self.')
+        if data['handler'] == 'self.OnXTerm':
+            wx.MessageBox(parent = self,
+                          message = _('You must run this command from the menu or command line',
+                                      'This command require an XTerm'),
+                          caption = _('Message'), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
+        elif data['command']:
+            eval(handler)(event = None, cmd = data['command'].split())
+        else:
+            eval(handler)(None)
+
+    def OnShowItem(self, event):
+        """!Show selected item"""
+        self.tree.OnShowItem(event)
+        if self.tree.GetSelected():
+            self.btnRun.Enable()
+        else:
+            self.btnRun.Enable(False)
+        
+    def OnItemActivated(self, event):
+        """!Item activated (double-click)"""
+        item = event.GetItem()
+        if not item or not item.IsOk():
+            return
+        
+        data = self.tree.GetPyData(item)
+        if not data or 'command' not in data:
+            return
+        
+        self.tree.itemSelected = item
+        
+        self.OnRun(None)
+        
+    def OnItemSelected(self, event):
+        """!Item selected"""
+        item = event.GetItem()
+        if not item or not item.IsOk():
+            return
+        
+        data = self.tree.GetPyData(item)
+        if not data or 'command' not in data:
+            return
+        
+        if data['command']:
+            label = data['command'] + ' -- ' + data['description']
+        else:
+            label = data['description']
+        
+        self.parent.SetStatusText(label, 0)
+        
+    def OnUpdateStatusBar(self, event):
+        """!Update statusbar text"""
+        element = self.search.GetSelection()
+        self.tree.SearchItems(element = element,
+                              value = event.GetString())
+        
+        nItems = len(self.tree.itemsMarked)
+        if event.GetString():
+            self.parent.SetStatusText(_("%d modules match") % nItems, 0)
+        else:
+            self.parent.SetStatusText("", 0)
+        
+        event.Skip()
+        
+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 MenuTree(ItemTree):
+    """!Menu tree class"""
+    def __init__(self, parent, data, **kwargs):
+        self.parent   = parent
+        self.menudata = data
+
+        super(MenuTree, self).__init__(parent, **kwargs)
+        
+    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]:
+                    itemNew = self.AppendItem(parentId = item,
+                                              text = eachItem[0])
+                    
+                    data = { 'item'        : eachItem[0],
+                             'description' : eachItem[1],
+                             'handler'  : eachItem[2],
+                             'command'  : eachItem[3],
+                             'keywords' : eachItem[4] }
+                    
+                    self.SetPyData(itemNew, data)
+        
+class AboutWindow(wx.Frame):
+    """!Create custom About Window
+
+    @todo improve styling
+    """
+    def __init__(self, parent, size = (750, 400), 
+                 title = _('About GRASS GIS'), **kwargs):
+        wx.Frame.__init__(self, parent = parent, id = wx.ID_ANY, size = size, **kwargs)
+        
+        panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        
+        # icon
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
+
+        # get version and web site
+        vInfo = grass.version()
+        
+        infoTxt = wx.Panel(parent = panel, id = wx.ID_ANY)
+        infoSizer = wx.BoxSizer(wx.VERTICAL)
+        infoGridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
+        infoGridSizer.AddGrowableCol(0)
+        infoGridSizer.AddGrowableCol(1)
+        logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass.ico")
+        logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
+                                     bitmap = wx.Bitmap(name = logo,
+                                                        type = wx.BITMAP_TYPE_ICO))
+        infoSizer.Add(item = logoBitmap, proportion = 0,
+                      flag = wx.ALL | wx.ALIGN_CENTER, border = 25)
+        
+        info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                             label = 'GRASS GIS ' + vInfo['version'] + '\n\n')
+        info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
+        infoSizer.Add(item = info, proportion = 0,
+                          flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 15)
+
+        row = 0
+        infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                                               label = _('Official GRASS site:')),
+                          pos = (row, 0),
+                          flag = wx.ALIGN_RIGHT)
+
+        infoGridSizer.Add(item = HyperLinkCtrl(parent = infoTxt, id = wx.ID_ANY,
+                                               label = 'http://grass.osgeo.org'),
+                          pos = (row, 1),
+                          flag = wx.ALIGN_LEFT)
+
+        row += 2
+        infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                                               label = _('SVN Revision:')),
+                          pos = (row, 0),
+                          flag = wx.ALIGN_RIGHT)
+        
+        infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                                               label = vInfo['revision']),
+                          pos = (row, 1),
+                          flag = wx.ALIGN_LEFT)
+        
+        row += 1
+        infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                                               label = _('GIS Library Revision:')),
+                          pos = (row, 0),
+                          flag = wx.ALIGN_RIGHT)
+        
+        infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
+                                               label = vInfo['libgis_revision'] + ' (' +
+                                               vInfo['libgis_date'].split(' ')[0] + ')'),
+                          pos = (row, 1),
+                          flag = wx.ALIGN_LEFT)
+
+        infoSizer.Add(item = infoGridSizer,
+                      proportion = 1,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL,
+                      border = 25)
+        
+        # create a flat notebook for displaying information about GRASS
+        aboutNotebook = GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON) 
+        aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
+        
+        for title, win in ((_("Info"), infoTxt),
+                           (_("Copyright"), self._pageCopyright()),
+                           (_("License"), self._pageLicense()),
+                           (_("Authors"), self._pageCredit()),
+                           (_("Contributors"), self._pageContributors()),
+                           (_("Extra contributors"), self._pageContributors(extra = True)),
+                           (_("Translators"), self._pageTranslators())):
+            aboutNotebook.AddPage(page = win, text = title)
+        wx.CallAfter(aboutNotebook.SetSelection, 0)
+        
+        # buttons
+        btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(item = btnClose, proportion = 0,
+                     flag = wx.ALL | wx.ALIGN_RIGHT,
+                     border = 5)
+        # bindings
+        btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
+        
+        infoTxt.SetSizer(infoSizer)
+        infoSizer.Fit(infoTxt)
+        
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(item = aboutNotebook, proportion = 1,
+                  flag = wx.EXPAND | wx.ALL, border = 1)
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
+        panel.SetSizer(sizer)
+        self.Layout()
+    
+    def _pageCopyright(self):
+        """Copyright information"""
+        copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
+        if os.path.exists(copyfile):
+            copyrightFile = open(copyfile, 'r')
+            copytext = copyrightFile.read()
+            copyrightFile.close()
+        else:
+            copytext = _('%s file missing') % 'COPYING'
+        
+        # put text into a scrolling panel
+        copyrightwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY, 
+                                              size = wx.DefaultSize,
+                                              style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
+        copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
+        copyrightwin.SetAutoLayout(True)
+        copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
+        copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
+                               flag = wx.EXPAND | wx.ALL, border = 3)
+        copyrightwin.SetSizer(copyrightwin.sizer)
+        copyrightwin.Layout()
+        copyrightwin.SetupScrolling()
+        
+        return copyrightwin
+    
+    def _pageLicense(self):
+        """Licence about"""
+        licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
+        if os.path.exists(licfile):
+            licenceFile = open(licfile, 'r')
+            license = ''.join(licenceFile.readlines())
+            licenceFile.close()
+        else:
+            license = _('%s file missing') % 'GPL.TXT'
+        # put text into a scrolling panel
+        licensewin = scrolled.ScrolledPanel(self, id = wx.ID_ANY, 
+                                            style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
+        licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
+        licensewin.SetAutoLayout(True)
+        licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
+        licensewin.sizer.Add(item = licensetxt, proportion = 1,
+                flag = wx.EXPAND | wx.ALL, border = 3)
+        licensewin.SetSizer(licensewin.sizer)
+        licensewin.Layout()
+        licensewin.SetupScrolling()
+        
+        return licensewin
+    
+    def _pageCredit(self):
+        """Credit about"""
+                # credits
+        authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
+        if os.path.exists(authfile):
+            authorsFile = open(authfile, 'r')
+            authors = unicode(''.join(authorsFile.readlines()), "utf-8")
+            authorsFile.close()
+        else:
+            authors = _('%s file missing') % 'AUTHORS'
+        authorwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY, 
+                                           style  =  wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
+        authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
+        authorwin.SetAutoLayout(1)
+        authorwin.SetupScrolling()
+        authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
+        authorwin.sizer.Add(item = authortxt, proportion = 1,
+                flag = wx.EXPAND | wx.ALL, border = 3)
+        authorwin.SetSizer(authorwin.sizer)
+        authorwin.Layout()      
+        
+        return authorwin
+
+    def _pageContributors(self, extra = False):
+        """Contributors info"""
+        if extra:
+            contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
+        else:
+            contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
+        if os.path.exists(contribfile):
+            contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
+            contribs = list()
+            errLines = list()
+            for line in contribFile.readlines()[1:]:
+                line = line.rstrip('\n')
+                try:
+                    if extra:
+                        name, email, rfc2_agreed = line.split(',')
+                    else:
+                        cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
+                except ValueError:
+                    errLines.append(line)
+                    continue
+                if extra:
+                    contribs.append((name, email))
+                else:
+                    contribs.append((name, email, country, osgeo_id))
+            
+            contribFile.close()
+            
+            if errLines:
+                GError(parent = self,
+                       message = _("Error when reading file '%s'.") % contribfile + \
+                           "\n\n" + _("Lines:") + " %s" % \
+                           os.linesep.join(map(utils.DecodeString, errLines)))
+        else:
+            contribs = None
+        
+        contribwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY, 
+                                           style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
+        contribwin.SetAutoLayout(True)
+        contribwin.SetupScrolling()
+        contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        if not contribs:
+            contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
+                                       label = _('%s file missing') % contribfile)
+            contribwin.sizer.Add(item = contribtxt, proportion = 1,
+                                 flag = wx.EXPAND | wx.ALL, border = 3)
+        else:
+            if extra:
+                items = (_('Name'), _('E-mail'))
+            else:
+                items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
+            contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
+            for item in items:
+                contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
+                                                    label = item))
+            for vals in sorted(contribs, key = lambda x: x[0]):
+                for item in vals:
+                    contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
+                                                        label = item))
+            contribwin.sizer.Add(item = contribBox, proportion = 1,
+                                 flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        contribwin.SetSizer(contribwin.sizer)
+        contribwin.Layout()      
+        
+        return contribwin
+
+    def _pageTranslators(self):
+        """Translators info"""
+        translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
+        if os.path.exists(translatorsfile):
+            translatorsFile = open(translatorsfile, 'r')
+            translators = dict()
+            errLines = list()
+            for line in translatorsFile.readlines()[1:]:
+                line = line.rstrip('\n')
+                try:
+                    name, email, languages = line.split(',')
+                except ValueError:
+                    errLines.append(line)
+                    continue
+                for language in languages.split(' '):
+                    if language not in translators:
+                        translators[language] = list()
+                    translators[language].append((name, email))
+            translatorsFile.close()
+            
+            if errLines:
+                GError(parent = self,
+                       message = _("Error when reading file '%s'.") % translatorsfile + \
+                           "\n\n" + _("Lines:") + " %s" % \
+                           os.linesep.join(map(utils.DecodeString, errLines)))
+        else:
+            translators = None
+        
+        translatorswin = scrolled.ScrolledPanel(self, id = wx.ID_ANY, 
+                                           style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
+        translatorswin.SetAutoLayout(1)
+        translatorswin.SetupScrolling()
+        translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
+        
+        if not translators:
+            translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
+                                           label = _('%s file missing') % 'translators.csv')
+            translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
+                                 flag = wx.EXPAND | wx.ALL, border = 3)
+        else:
+            translatorsBox = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
+            languages = translators.keys()
+            languages.sort()
+            translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                    label = _('Name')))
+            translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                    label = _('E-mail')))
+            translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                    label = _('Language')))
+            for lang in languages:
+                for translator in translators[lang]:
+                    name, email = translator
+                    translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                            label =  unicode(name, "utf-8")))
+                    translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                            label = email))
+                    translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
+                                                            label = lang))
+            
+            translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
+                                 flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        translatorswin.SetSizer(translatorswin.sizer)
+        translatorswin.Layout()      
+        
+        return translatorswin
+    
+    def OnCloseWindow(self, event):
+        """!Close window"""
+        self.Close()
+
+class InstallExtensionWindow(wx.Frame):
+    def __init__(self, parent, id = wx.ID_ANY,
+                 title = _("Fetch & install extension from GRASS Addons"), **kwargs):
+        self.parent = parent
+        self.options = dict() # list of options
+        
+        wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
+        
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
+
+        self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                    label = " %s " % _("Repository"))
+        self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                    label = " %s " % _("List of extensions"))
+        
+        self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
+        self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                    label = _("Fetch full info including description and keywords (takes time)"))
+        self.fullDesc.SetValue(True)
+        
+        self.search = SearchModuleWindow(parent = self.panel)
+        self.search.SetSelection(0) 
+        
+        self.tree   = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
+        
+        self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
+                                      label = " %s " % _("Options"))
+        task = gtask.parse_interface('g.extension')
+        for f in task.get_options()['flags']:
+            name = f.get('name', '')
+            desc = f.get('label', '')
+            if not desc:
+                desc = f.get('description', '')
+            if not name and not desc:
+                continue
+            if name in ('l', 'c', 'g', 'quiet', 'verbose'):
+                continue
+            self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
+                                             label = desc)
+        self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
+                                                                'http://svn.osgeo.org/grass/grass-addons/grass7'))
+        
+        self.statusbar = self.CreateStatusBar(number = 1)
+        
+        self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                  label = _("&Fetch"))
+        self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
+        self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
+        self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                    label = _("&Install"))
+        self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
+        self.btnInstall.Enable(False)
+        self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                label = _("Command dialog"))
+        self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
+
+        self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
+        self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
+        self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
+        self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
+        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.search.Bind(wx.EVT_TEXT,              self.OnUpdateStatusBar)
+
+        self._layout()
+
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
+        repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
+        repo1Sizer.Add(item = self.repo, proportion = 1,
+                      flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
+        repo1Sizer.Add(item = self.btnFetch, proportion = 0,
+                      flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
+        repoSizer.Add(item = repo1Sizer,
+                      flag = wx.EXPAND)
+        repoSizer.Add(item = self.fullDesc)
+        
+        findSizer = wx.BoxSizer(wx.HORIZONTAL)
+        findSizer.Add(item = self.search, proportion = 1)
+        
+        treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
+        treeSizer.Add(item = self.tree, proportion = 1,
+                      flag = wx.ALL | wx.EXPAND, border = 1)
+
+        # options
+        optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
+        for key in self.options.keys():
+            optionSizer.Add(item = self.options[key], proportion = 0)
+        
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        btnSizer.Add(item = self.btnCmd, proportion = 0,
+                     flag = wx.RIGHT, border = 5)
+        btnSizer.AddSpacer(10)
+        btnSizer.Add(item = self.btnClose, proportion = 0,
+                     flag = wx.RIGHT, border = 5)
+        btnSizer.Add(item = self.btnInstall, proportion = 0)
+        
+        sizer.Add(item = repoSizer, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 3)
+        sizer.Add(item = findSizer, proportion = 0,
+                  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+        sizer.Add(item = treeSizer, proportion = 1,
+                  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+        sizer.Add(item = optionSizer, proportion = 0,
+                        flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
+        
+        self.panel.SetSizer(sizer)
+        sizer.Fit(self.panel)
+        
+        self.Layout()
+
+    def _getCmd(self):
+        item = self.tree.GetSelected()
+        if not item or not item.IsOk():
+            return ['g.extension']
+        
+        name = self.tree.GetItemText(item)
+        if not name:
+            GError(_("Extension not defined"), parent = self)
+            return
+        
+        flags = list()
+        for key in self.options.keys():
+            if self.options[key].IsChecked():
+                flags.append('-%s' % key)
+        
+        return ['g.extension'] + flags + ['extension=' + name,
+                                          'svnurl=' + self.repo.GetValue().strip()]
+    
+    def OnUpdateStatusBar(self, event):
+        """!Update statusbar text"""
+        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.GetString())
+        
+        nItems = len(self.tree.itemsMarked)
+        if event.GetString():
+            self.SetStatusText(_("%d items match") % nItems, 0)
+        else:
+            self.SetStatusText("", 0)
+        
+        event.Skip()
+    
+    def OnCloseWindow(self, event):
+        """!Close window"""
+        self.Destroy()
+
+    def OnFetch(self, event):
+        """!Fetch list of available extensions"""
+        wx.BeginBusyCursor()
+        self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
+        self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
+        self.SetStatusText("", 0)
+        wx.EndBusyCursor()
+
+    def OnItemActivated(self, event):
+        item = event.GetItem()
+        data = self.tree.GetPyData(item)
+        if data and 'command' in data:
+            self.OnInstall(event = None)
+        
+    def OnInstall(self, event):
+        """!Install selected extension"""
+        log = self.parent.GetLogWindow()
+        log.RunCmd(self._getCmd(), onDone = self.OnDone)
+        
+    def OnDone(self, cmd, returncode):
+        item = self.tree.GetSelected()
+        if not item or not item.IsOk() or \
+                returncode != 0 or \
+                not os.getenv('GRASS_ADDON_PATH'):
+            return
+        
+        name = self.tree.GetItemText(item)
+        globalvar.grassCmd['all'].append(name)
+        
+    def OnItemSelected(self, event):
+        """!Item selected"""
+        item = event.GetItem()
+        self.tree.itemSelected = item
+        data = self.tree.GetPyData(item)
+        if not data:
+            self.SetStatusText('', 0)
+            self.btnInstall.Enable(False)
+        else:
+            self.SetStatusText(data.get('description', ''), 0)
+            self.btnInstall.Enable(True)
+
+    def OnShowItem(self, event):
+        """!Show selected item"""
+        self.tree.OnShowItem(event)
+        if self.tree.GetSelected():
+            self.btnInstall.Enable()
+        else:
+            self.btnInstall.Enable(False)
+
+    def OnCmdDialog(self, event):
+        """!Shows command dialog"""
+        GUI(parent = self).ParseCommand(cmd = self._getCmd())
+        
+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):
+        for prefix in ('display', 'database',
+                       'general', 'imagery',
+                       'misc', 'postscript', 'paint',
+                       'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
+            self.AppendItem(parentId = self.root,
+                            text = prefix)
+        self._loaded = False
+        
+    def _expandPrefix(self, c):
+        name = { 'd'  : 'display',
+                 'db' : 'database',
+                 'g'  : 'general',
+                 'i'  : 'imagery',
+                 'm'  : 'misc',
+                 'ps' : 'postscript',
+                 'p'  : 'paint',
+                 'r'  : 'raster',
+                 'r3' : 'raster3D',
+                 's'  : 'sites',
+                 'v'  : 'vector',
+                 'wx' : 'wxGUI',
+                 'u'  : 'other' }
+        
+        if c in name:
+            return name[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 = False):
+        """!Load list of extensions"""
+        self.DeleteAllItems()
+        self.root = self.AddRoot(_("Menu tree"))
+        self._initTree()
+        
+        if full:
+            flags = 'g'
+        else:
+            flags = 'l'
+        ret = RunCommand('g.extension', read = True,
+                         svnurl = url,
+                         flags = flags, quiet = True)
+        if not ret:
+            return
+        
+        mdict = dict()
+        for line in ret.splitlines():
+            if full:
+                key, value = line.split('=', 1)
+                if key == 'name':
+                    try:
+                        prefix, name = value.split('.', 1)
+                    except ValueError:
+                        prefix = 'u'
+                        name = value
+                    if prefix not in mdict:
+                        mdict[prefix] = dict()
+                    mdict[prefix][name] = dict()
+                else:
+                    mdict[prefix][name][key] = value
+            else:
+                try:
+                    prefix, name = line.strip().split('.', 1)
+                except:
+                    prefix = 'unknown'
+                    name = line.strip()
+                
+                if self._expandPrefix(prefix) == prefix:
+                    prefix = 'unknown'
+                    
+                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:
+                new = self.AppendItem(parentId = item,
+                                      text = prefix + '.' + name)
+                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
+
+class HelpWindow(wx.html.HtmlWindow):
+    """!This panel holds the text from GRASS docs.
+    
+    GISBASE must be set in the environment to find the html docs dir.
+    The SYNOPSIS section is skipped, since this Panel is supposed to
+    be integrated into the cmdPanel and options are obvious there.
+    """
+    def __init__(self, parent, grass_command, text, skip_description,
+                 **kwargs):
+        """!If grass_command is given, the corresponding HTML help
+        file will be presented, with all links pointing to absolute
+        paths of local files.
+
+        If 'skip_description' is True, the HTML corresponding to
+        SYNOPSIS will be skipped, thus only presenting the help file
+        from the DESCRIPTION section onwards.
+
+        If 'text' is given, it must be the HTML text to be presented
+        in the Panel.
+        """
+        self.parent = parent
+        wx.InitAllImageHandlers()
+        wx.html.HtmlWindow.__init__(self, parent = parent, **kwargs)
+        
+        gisbase = os.getenv("GISBASE")
+        self.loaded = False
+        self.history = list()
+        self.historyIdx = 0
+        self.fspath = os.path.join(gisbase, "docs", "html")
+        
+        self.SetStandardFonts (size = 10)
+        self.SetBorders(10)
+        
+        if text is None:
+            if skip_description:
+                url = os.path.join(self.fspath, grass_command + ".html")
+                self.fillContentsFromFile(url,
+                                          skip_description = skip_description)
+                self.history.append(url)
+                self.loaded = True
+            else:
+                ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
+                # self.LoadPage(self.fspath + grass_command + ".html")
+                self.loaded = False
+        else:
+            self.SetPage(text)
+            self.loaded = True
+        
+    def OnLinkClicked(self, linkinfo):
+        url = linkinfo.GetHref()
+        if url[:4] != 'http':
+            url = os.path.join(self.fspath, url)
+        self.history.append(url)
+        self.historyIdx += 1
+        self.parent.OnHistory()
+        
+        super(HelpWindow, self).OnLinkClicked(linkinfo)
+        
+    def fillContentsFromFile(self, htmlFile, skip_description = True):
+        """!Load content from file"""
+        aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
+        imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
+        try:
+            contents = []
+            skip = False
+            for l in file(htmlFile, "rb").readlines():
+                if "DESCRIPTION" in l:
+                    skip = False
+                if not skip:
+                    # do skip the options description if requested
+                    if "SYNOPSIS" in l:
+                        skip = skip_description
+                    else:
+                        # FIXME: find only first item
+                        findALink = aLink.search(l)
+                        if findALink is not None: 
+                            contents.append(aLink.sub(findALink.group(1)+
+                                                      self.fspath+findALink.group(2),l))
+                        findImgLink = imgLink.search(l)
+                        if findImgLink is not None: 
+                            contents.append(imgLink.sub(findImgLink.group(1)+
+                                                        self.fspath+findImgLink.group(2),l))
+                        
+                        if findALink is None and findImgLink is None:
+                            contents.append(l)
+            self.SetPage("".join(contents))
+            self.loaded = True
+        except: # The Manual file was not found
+            self.loaded = False
+        
+class HelpPanel(wx.Panel):
+    def __init__(self, parent, grass_command = "index", text = None,
+                 skip_description = False, **kwargs):
+        self.grass_command = grass_command
+        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
+        
+        self.content = HelpWindow(self, grass_command, text,
+                                  skip_description)
+        
+        self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
+                                 label = _("&Next"))
+        self.btnNext.Enable(False)
+        self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
+                                 label = _("&Previous"))
+        self.btnPrev.Enable(False)
+        
+        self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
+        self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
+        
+        self._layout()
+
+    def _layout(self):
+        """!Do layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        btnSizer.Add(item = self.btnPrev, proportion = 0,
+                     flag = wx.ALL, border = 5)
+        btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
+        btnSizer.Add(item = self.btnNext, proportion = 0,
+                     flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
+        
+        sizer.Add(item = self.content, proportion = 1,
+                  flag = wx.EXPAND)
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.EXPAND)
+        
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def LoadPage(self, path = None):
+        """!Load page"""
+        if not path:
+            path = os.path.join(self.content.fspath, self.grass_command + ".html")
+        self.content.history.append(path)
+        self.content.LoadPage(path)
+        
+    def IsFile(self):
+        """!Check if file exists"""
+        return os.path.isfile(os.path.join(self.content.fspath, self.grass_command + ".html"))
+
+    def IsLoaded(self):
+        return self.content.loaded
+
+    def OnHistory(self):
+        """!Update buttons"""
+        nH = len(self.content.history)
+        iH = self.content.historyIdx
+        if iH == nH - 1:
+            self.btnNext.Enable(False)
+        elif iH > -1:
+            self.btnNext.Enable(True)
+        if iH < 1:
+            self.btnPrev.Enable(False)
+        else:
+            self.btnPrev.Enable(True)
+
+    def OnNext(self, event):
+        """Load next page"""
+        self.content.historyIdx += 1
+        idx = self.content.historyIdx
+        path = self.content.history[idx]
+        self.content.LoadPage(path)
+        self.OnHistory()
+        
+        event.Skip()
+        
+    def OnPrev(self, event):
+        """Load previous page"""
+        self.content.historyIdx -= 1
+        idx = self.content.historyIdx
+        path = self.content.history[idx]
+        self.content.LoadPage(path)
+        self.OnHistory()
+        
+        event.Skip()

Copied: grass/trunk/gui/wxpython/gui_core/goutput.py (from rev 49324, grass/trunk/gui/wxpython/gui_modules/goutput.py)
===================================================================
--- grass/trunk/gui/wxpython/gui_core/goutput.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/gui_core/goutput.py	2011-11-24 11:46:55 UTC (rev 49347)
@@ -0,0 +1,1154 @@
+"""!
+ at package gui_core.goutput
+
+ at brief Command output widgets
+
+Classes:
+ - CmdThread
+ - GMConsole
+ - GMStc
+ - GMStdout
+ - GMStderr
+
+(C) 2007-2011 by the GRASS Development Team
+This program is free software under the GNU General Public
+License (>=v2). Read the file COPYING that comes with GRASS
+for details.
+
+ at author Michael Barton (Arizona State University)
+ at author Martin Landa <landa.martin gmail.com>
+ at author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
+"""
+
+import os
+import sys
+import textwrap
+import time
+import threading
+import Queue
+import codecs
+import locale
+
+import wx
+import wx.stc
+from wx.lib.newevent import NewEvent
+
+import grass.script as grass
+from   grass.script import task as gtask
+
+from core            import globalvar
+from core            import utils
+from core.gcmd       import CommandThread, GMessage, GError, GException, EncodeString
+from gui_core.forms  import GUI
+from gui_core.prompt import GPromptSTC
+from core.debug      import Debug
+from core.settings   import UserSettings, Settings
+from gui_core.ghelp  import SearchModuleWindow
+
+wxCmdOutput,   EVT_CMD_OUTPUT   = NewEvent()
+wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
+wxCmdRun,      EVT_CMD_RUN      = NewEvent()
+wxCmdDone,     EVT_CMD_DONE     = NewEvent()
+wxCmdAbort,    EVT_CMD_ABORT    = NewEvent()
+wxCmdPrepare,  EVT_CMD_PREPARE  = NewEvent()
+
+def GrassCmd(cmd, stdout = None, stderr = None):
+    """!Return GRASS command thread"""
+    return CommandThread(cmd,
+                         stdout = stdout, stderr = stderr)
+
+class CmdThread(threading.Thread):
+    """!Thread for GRASS commands"""
+    requestId = 0