[GRASS-SVN] r54454 - in grass/trunk/gui: scripts wxpython/core wxpython/gcp wxpython/gui_core wxpython/lmgr wxpython/mapdisp

svn_grass at osgeo.org svn_grass at osgeo.org
Sat Dec 29 02:45:35 PST 2012


Author: martinl
Date: 2012-12-29 02:45:35 -0800 (Sat, 29 Dec 2012)
New Revision: 54454

Added:
   grass/trunk/gui/scripts/d.wms.py
   grass/trunk/gui/wxpython/core/ws.py
Modified:
   grass/trunk/gui/wxpython/core/gconsole.py
   grass/trunk/gui/wxpython/core/render.py
   grass/trunk/gui/wxpython/core/utils.py
   grass/trunk/gui/wxpython/gcp/mapdisplay.py
   grass/trunk/gui/wxpython/gui_core/mapwindow.py
   grass/trunk/gui/wxpython/lmgr/frame.py
   grass/trunk/gui/wxpython/lmgr/layertree.py
   grass/trunk/gui/wxpython/mapdisp/frame.py
   grass/trunk/gui/wxpython/mapdisp/mapwindow.py
   grass/trunk/gui/wxpython/mapdisp/statusbar.py
Log:
wxGUI: add support for WMS layers (work in progress)
       author: Stepan Turek


Added: grass/trunk/gui/scripts/d.wms.py
===================================================================
--- grass/trunk/gui/scripts/d.wms.py	                        (rev 0)
+++ grass/trunk/gui/scripts/d.wms.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+#
+############################################################################
+#
+# MODULE:       d.wms
+#
+# AUTHOR(S):    Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+#
+# PURPOSE:      Wrapper for wxGUI to add WMS into layer tree
+#
+# COPYRIGHT:    (C) 2012 by Stepan Turek, and 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.
+#
+#############################################################################
+
+#%module
+#% description: Downloads and displays data from WMS server.
+#% keywords: raster
+#% keywords: import
+#% keywords: wms
+#%end
+
+#%option
+#% key: url
+#% type: string
+#% description: URL of WMS server 
+#% required: yes
+#%end
+
+#%option
+#% key: layers
+#% type: string
+#% description: Layers to request from WMS server
+#% multiple: yes
+#% required: yes
+#%end
+
+#%option
+#% key: map
+#% type: string
+#% description: Name for output WMS layer in the layer tree
+#% required : yes
+#%end
+
+#%option
+#% key: srs
+#% type: integer
+#% description: EPSG number of source projection for request 
+#% answer:4326 
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: wms_version
+#% type: string
+#% description: WMS standard
+#% options: 1.1.1,1.3.0
+#% answer: 1.1.1
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: format
+#% type: string
+#% description: Image format requested from the server
+#% options: geotiff,tiff,jpeg,gif,png
+#% answer: geotiff
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: method
+#% type: string
+#% description: Reprojection method to use
+#% options:near,bilinear,cubic,cubicspline
+#% answer:near
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: maxcols
+#% type:integer
+#% description: Maximum columns to request at a time
+#% answer:400
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: maxrows
+#% type: integer
+#% description: Maximum rows to request at a time
+#% answer: 300
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: urlparams
+#% type:string
+#% description: Additional query parameters for server
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: username
+#% type:string
+#% description: Username for server connection
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: password
+#% type:string
+#% description: Password for server connection
+#% guisection: Request properties
+#%end
+
+#%option
+#% key: styles
+#% type: string
+#% description: Styles to request from map server
+#% multiple: yes
+#% guisection: Map style
+#%end
+
+#%option
+#% key: bgcolor
+#% type: string
+#% description: Color of map background
+#% guisection: Map style
+#%end
+
+#%flag
+#% key: o
+#% description: Don't request transparent data
+#% guisection: Map style
+#%end
+
+#%option
+#% key: driver
+#% type:string
+#% description: Driver for communication with server
+#% descriptions: WMS_GDAL;Download data using GDAL WMS driver;WMS_GRASS;Download data using native GRASS-WMS driver;WMTS_GRASS;Download data using native GRASS-WMTS driver;OnEarth_GRASS;Download data using native GRASS-OnEarth driver;
+#% options:WMS_GDAL, WMS_GRASS, WMTS_GRASS, OnEarth_GRASS
+#% answer:WMS_GRASS
+#%end
+
+#%option G_OPT_F_INPUT
+#% key: capfile
+#% required: no
+#% gisprompt: old,file,bin_input
+#% description: Capabilities file to load
+
+import os
+import sys
+
+from grass.script import core as grass
+
+sys.path.append(os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms"))
+                
+def GetRegion():
+    """!Parse region from GRASS_REGION env var.
+    """
+    region = os.environ["GRASS_REGION"]
+    conv_reg_vals = {'east' : 'e',
+                     'north' : 'n',
+                     'west' : 'w',
+                     'south' : 's',
+                     'rows' : 'rows',
+                     'cols' : 'cols',
+                     'e-w resol' : 'ewres',
+                     'n-s resol' : 'nsres'}
+
+    keys_to_convert = conv_reg_vals.keys()
+
+    conv_region = {}
+    region = region.split(';')
+
+    for r in region:
+        r = r.split(':')
+        r[0] = r[0].strip()
+        
+        if r[0] in keys_to_convert:
+            conv_region[conv_reg_vals[r[0]]] = float(r[1])
+
+    return conv_region
+
+def main():
+    options['region'] = GetRegion()
+
+    if 'GRASS' in options['driver']:
+        grass.debug("Using GRASS driver")
+        from wms_drv import WMSDrv
+        wms = WMSDrv()
+    elif 'GDAL' in options['driver']:
+        grass.debug("Using GDAL WMS driver")
+        from wms_gdal_drv import WMSGdalDrv
+        wms = WMSGdalDrv()
+    
+    temp_map = wms.GetMap(options, flags) 
+    os.rename(temp_map, os.environ["GRASS_PNGFILE"])
+
+    return 0
+
+if __name__ == "__main__":
+    options, flags = grass.parser()
+    sys.exit(main())


Property changes on: grass/trunk/gui/scripts/d.wms.py
___________________________________________________________________
Added: svn:mime-type
   + text/x-python
Added: svn:eol-style
   + native

Modified: grass/trunk/gui/wxpython/core/gconsole.py
===================================================================
--- grass/trunk/gui/wxpython/core/gconsole.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/core/gconsole.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -79,6 +79,8 @@
 
         self.setDaemon(True)
 
+        self.requestCmd = None
+
         self.receiver = receiver
         self._want_abort_all = False
 
@@ -99,6 +101,10 @@
 
         return CmdThread.requestId
 
+    def GetId(self):
+         """!Get id for next command"""
+         return CmdThread.requestId + 1
+
     def SetId(self, id):
         """!Set starting id"""
         CmdThread.requestId = id
@@ -135,7 +141,7 @@
 
             time.sleep(.1)
             self.requestCmd = vars()['callable'](*args, **kwds)
-            if self._want_abort_all:
+            if self._want_abort_all and self.requestCmd is not None:
                 self.requestCmd.abort()
                 if self.requestQ.empty():
                     self._want_abort_all = False
@@ -197,11 +203,11 @@
         """!Abort command(s)"""
         if abortall:
             self._want_abort_all = True
-        self.requestCmd.abort()
+        if self.requestCmd is not None:
+            self.requestCmd.abort()
         if self.requestQ.empty():
             self._want_abort_all = False
 
-
 class GStdout:
     """!GConsole standard output
 

Modified: grass/trunk/gui/wxpython/core/render.py
===================================================================
--- grass/trunk/gui/wxpython/core/render.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/core/render.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -3,6 +3,9 @@
 
 @brief Rendering map layers and overlays into map composition image.
 
+ at todo Implement RenderManager also for other layers (see WMS
+implementation for details)
+
 Classes:
  - render::Layer
  - render::MapLayer
@@ -33,6 +36,7 @@
 from grass.script import core as grass
 
 from core          import utils
+from core.ws       import RenderWMSMgr
 from core.gcmd     import GException, GError, RunCommand
 from core.debug    import Debug
 from core.settings import UserSettings
@@ -47,8 +51,10 @@
     
     - For map layer use MapLayer class.
     - For overlays use Overlay class.
+
+    @todo needs refactoring (avoid parent)
     """
-    def __init__(self, type, cmd, name = None,
+    def __init__(self, type, cmd, parent, name = None,
                  active = True, hidden = False, opacity = 1.0):
         """!Create new instance
         
@@ -62,7 +68,24 @@
         @param hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         """
-        self.type  = type
+        self.parent = parent
+        
+        # generated file for each layer
+        if USE_GPNMCOMP or type == 'overlay':
+            tmpfile = tempfile.mkstemp()[1]
+            self.maskfile = tmpfile + '.pgm'
+            if type == 'overlay':
+                self.mapfile  = tmpfile + '.png'
+            else:
+                self.mapfile  = tmpfile + '.ppm'
+            grass.try_remove(tmpfile)
+        else:
+            self.mapfile = self.maskfile = None
+        
+        # stores class which manages rendering instead of simple command - e. g. wms
+        self.renderMgr = None
+        
+        self.SetType(type)
         self.name  = name
         
         if self.type == 'command':
@@ -76,25 +99,13 @@
         self.hidden  = hidden
         self.opacity = opacity
         
-        self.force_render = True
+        self.forceRender = 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)))
@@ -120,13 +131,12 @@
                       'vector','thememap','themechart',
                       'grid', 'geodesic', 'rhumb', 'labels',
                       'command', 'rastleg','maplegend',
-                      'overlay')
+                      'overlay', 'wms')
         
         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
         
@@ -135,10 +145,7 @@
             if self.type == 'command':
                 read = False
                 for c in self.cmd:
-                    ret, msg = RunCommand(c[0],
-                                          getErrorMsg = True,
-                                          quiet = True,
-                                          **c[1])
+                    ret, msg = self._runCommand(c)
                     if ret != 0:
                         break
                     if not read:
@@ -146,11 +153,7 @@
                 
                 os.environ["GRASS_PNG_READ"] = "FALSE"
             else:
-                ret, msg = RunCommand(self.cmd[0],
-                                      getErrorMsg = True,
-                                      quiet = True,
-                                      **self.cmd[1])
-                
+                ret, msg = self._runCommand(self.cmd)
             if ret != 0:
                 sys.stderr.write(_("Command '%s' failed\n") % self.GetCmd(string = True))
                 if msg:
@@ -169,10 +172,27 @@
         if self.mapfile and "GRASS_PNGFILE" in os.environ:
             del os.environ["GRASS_PNGFILE"]
         
-        self.force_render = False
+        self.forceRender = False
         
         return self.mapfile
     
+    def _runCommand(self, cmd):
+        """!Run command to render data
+
+        @todo catch error for wms (?)
+        """ 
+        if self.type == 'wms':
+            ret = 0
+            msg = ''
+            self.renderMgr.Render(cmd)
+        else:
+            ret, msg = RunCommand(self.cmd[0],
+                                  getErrorMsg = True,
+                                  quiet = True,
+                                  **self.cmd[1])
+        
+        return ret, msg
+    
     def GetCmd(self, string = False):
         """!Get GRASS command as list of string.
         
@@ -238,16 +258,21 @@
         """!Check if layer is activated for rendering"""
         return self.active
     
-    def SetType(self, type):
+    def SetType(self, ltype):
         """!Set layer type"""
-        if type not in ('raster', '3d-raster', 'vector',
+        if ltype 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)
+                        'geodesic','rhumb', 'wms'):
+            raise GException(_("Unsupported map layer type '%s'") % ltype)
         
-        self.type = type
+        if ltype == 'wms' and not isinstance(self.renderMgr, RenderWMSMgr):
+            self.renderMgr = RenderWMSMgr(self, self.mapfile, self.maskfile)
+        elif self.type == 'wms' and ltype != 'wms':
+            self.renderMgr = None
+        
+        self.type = ltype
 
     def SetName(self, name):
         """!Set layer name"""
@@ -281,10 +306,24 @@
         Debug.msg(3, "Layer.SetCmd(): cmd='%s'" % self.GetCmd(string = True))
         
         # for re-rendering
-        self.force_render = True
-        
+        self.forceRender = True
+
+    def IsDownloading(self):
+        """!Is data downloading from web server e. g. wms"""
+        if self.renderMgr is None:
+            return False
+        else:
+            return self.renderMgr.IsDownloading()
+
+    def AbortDownload(self):
+        """!Abort downloading data"""
+        if self.renderMgr is None:
+            return
+        else:
+            self.renderMgr.Abort()
+
 class MapLayer(Layer):
-    def __init__(self, type, cmd, name = None,
+    def __init__(self, type, cmd, parent, name = None,
                  active = True, hidden = False, opacity = 1.0): 
         """!Represents map layer in the map canvas
         
@@ -296,7 +335,7 @@
         @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,
+        Layer.__init__(self, type, cmd, parent, name,
                        active, hidden, opacity)
         
     def GetMapset(self):
@@ -314,7 +353,7 @@
             return self.name
         
 class Overlay(Layer):
-    def __init__(self, id, type, cmd,
+    def __init__(self, id, type, cmd, parent,
                  active = True, hidden = True, opacity = 1.0):
         """!Represents overlay displayed in map canvas
         
@@ -326,9 +365,8 @@
         @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,
+        Layer.__init__(self, 'overlay', cmd, parent, type,
                        active, hidden, opacity)
-        
         self.id = id
         
 class Map(object):
@@ -362,6 +400,9 @@
         self._initGisEnv() # g.gisenv
         self.GetWindow()
 
+        # reference to mapWindow, which contains this Map instance
+        self.mapWin = None
+
         # GRASS environment variable (for rendering)
         self.env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
                "GRASS_COMPRESSION"     : "0",
@@ -376,6 +417,9 @@
         # projection info
         self.projinfo = self._projInfo()
 
+        # is some layer being downloaded?
+        self.downloading = False
+
     def _runCommand(self, cmd, **kwargs):
         """!Run command in environment defined by self.gisrc if
         defined"""
@@ -834,28 +878,37 @@
         masks = list()
         opacities = list()
         # render map layers
-        ilayer = 1
         if overlaysOnly:
             layers = self.overlays
         else:
             layers = self.layers + self.overlays
         
+        self.downloading = False
+        #event = wxUpdateProgressBar(layer = None, map = self)
+        # When using Event for progress update, the event is handled after 
+        # rendering. Maybe there exists some better solution than calling 
+        # the method directly.
+        mapWindow.GetProgressBar().UpdateProgress(None, self)
+        #wx.PostEvent(mapWindow, event)
         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 force or layer.forceRender:
                 if not layer.Render():
                     continue
-            
+
+            if layer.IsDownloading():
+                self.downloading = True     
             if mapWindow:
                 # update progress bar
                 ### wx.SafeYield(mapWindow)
-                event = wxUpdateProgressBar(value = ilayer)
-                wx.PostEvent(mapWindow, event)
-            
+                mapWindow.GetProgressBar().UpdateProgress(layer, self)
+                #event = wxUpdateProgressBar(layer = layer, map = self)
+                #wx.PostEvent(mapWindow, event)
+
             # skip map layers when rendering fails
             if not os.path.exists(layer.mapfile):
                 continue
@@ -867,7 +920,6 @@
                 opacities.append(str(layer.opacity))
             
             Debug.msg(3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name))
-            ilayer += 1
         
         return maps, masks, opacities
         
@@ -985,7 +1037,7 @@
             l_opacity = 0
         elif l_opacity > 1:
             l_opacity = 1
-        layer = MapLayer(type = type, name = name, cmd = command,
+        layer = MapLayer(type = type, name = name, cmd = command, parent = self,
                          active = l_active, hidden = l_hidden, opacity = l_opacity)
         
         # add maplayer to the list of layers
@@ -1052,8 +1104,8 @@
         
         layerNameList = ""
         for layer in self.layers:
-            if layer.name:
-                layerNameList += layer.name + ','
+            if layer.GetName():
+                layerNameList += layer.GetName() + ','
         Debug.msg (4, "Map.ReoderLayers(): layers=%s" % \
                    (layerNameList))
         
@@ -1192,7 +1244,7 @@
         @return None on failure
         """
         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, l_render))
-        overlay = Overlay(id = id, type = type, cmd = command,
+        overlay = Overlay(id = id, type = type, cmd = command, parent = self,
                           active = l_active, hidden = l_hidden, opacity = l_opacity)
         
         # add maplayer to the list of layers
@@ -1300,5 +1352,17 @@
     def RenderOverlays(self, force):
         """!Render overlays only (for nviz)"""
         for layer in self.overlays:
-            if force or layer.force_render:
+            if force or layer.forceRender:
                 layer.Render()
+
+    def SetParentMapWindow(self, mapWin):
+        """!Set reference to parent MapWindow"""
+        self.mapWin = mapWin
+
+    def GetParentMapWindow(self):
+        """!Get reference to parent MapWindow"""
+        return self.mapWin
+
+    def IsDownloading(self):
+        """!Is any layer downloading data from web server e. g. wms"""
+        return self.downloading

Modified: grass/trunk/gui/wxpython/core/utils.py
===================================================================
--- grass/trunk/gui/wxpython/core/utils.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/core/utils.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -462,32 +462,40 @@
         return coef * (float(d) + fm + fs)
     
 def GetCmdString(cmd):
-    """
-    Get GRASS command as string.
+    """!Get GRASS command as string.
     
-    @param cmd GRASS command given as dictionary
+    @param cmd GRASS command given as tuple
     
     @return command string
     """
-    scmd = ''
+    return ' '.join(CmdTupleToList(cmd))
+
+def CmdTupleToList(cmd):
+    """!Convert command tuple to list.
+    
+    @param cmd GRASS command given as tuple
+    
+    @return command in list
+    """
+    cmdList = []
     if not cmd:
-        return scmd
+        return cmdList
     
-    scmd = cmd[0]
+    cmdList.append(cmd[0])
     
     if 'flags' in cmd[1]:
         for flag in cmd[1]['flags']:
-            scmd += ' -' + flag
+            cmdList.append('-' + flag)
     for flag in ('verbose', 'quiet', 'overwrite'):
         if flag in cmd[1] and cmd[1][flag] is True:
-            scmd += ' --' + flag
+            cmdList.append('--' + flag)
     
     for k, v in cmd[1].iteritems():
         if k in ('flags', 'verbose', 'quiet', 'overwrite'):
             continue
-        scmd += ' %s=%s' % (k, v)
+        cmdList.append('%s=%s' % (k, v))
             
-    return scmd
+    return cmdList
 
 def CmdToTuple(cmd):
     """!Convert command list to tuple for gcmd.RunCommand()"""

Added: grass/trunk/gui/wxpython/core/ws.py
===================================================================
--- grass/trunk/gui/wxpython/core/ws.py	                        (rev 0)
+++ grass/trunk/gui/wxpython/core/ws.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -0,0 +1,347 @@
+"""!
+ at package core.ws
+
+ at brief Fetching and preparation of web service data for rendering.
+
+Note: Currently only WMS is implemented.
+
+Classes:
+ - ws::RenderWMSMgr
+ - ws::StdErr
+ - ws::GDALRasterMerger
+
+(C) 2012 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+ at author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
+"""
+import os
+import sys
+
+import wx
+
+from grass.script import core as grass
+
+from core          import utils
+from core.debug    import Debug
+
+from core.gconsole import CmdThread, EVT_CMD_DONE
+from core.gcmd     import GException
+
+try:
+    haveGdal = True
+    from osgeo import gdal
+    from osgeo import gdalconst 
+except ImportError:
+    haveGdal = False
+
+class RenderWMSMgr(wx.EvtHandler):
+    """!Fetch and prepare WMS data for rendering.
+
+    @todo statusbar: updtade by event or call method ???
+    """
+    def __init__(self, parent, mapfile, maskfile):
+        if not haveGdal:
+            sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\
+                               "WMS layers can not be displayed without the bindings.\n"))
+        
+        self.parent = parent
+
+        # thread for d.wms commands
+        self.thread = CmdThread(self)
+        wx.EvtHandler.__init__(self)
+        self.Bind(EVT_CMD_DONE, self.OnDataFetched)
+
+        self.downloading = False
+        self.renderedRegion = None
+        self.updateMap = True
+
+        self.cmdStdErr = StdErr()
+
+        self.mapfile = mapfile
+        self.maskfile = maskfile
+        self.tempMap = grass.tempfile()
+        self.dstSize = {}
+
+    def __del__(self):
+        grass.try_remove(self.tempMap)
+
+    def Render(self, cmd):
+        """!If it is needed, download missing WMS data.
+
+        @todo lmgr deletes mapfile and maskfile when order of layers
+        was changed (drag and drop) - if deleted, fetch data again
+        """
+        if not haveGdal:
+            return
+
+        self.dstSize['cols'] = int(os.environ["GRASS_WIDTH"])
+        self.dstSize['rows'] = int(os.environ["GRASS_HEIGHT"])
+
+        region = self._getRegionDict()
+        self._fitAspect(region, self.dstSize)
+
+        self.updateMap = True
+        fetchData = False
+        zoomChanged = False
+
+        if self.renderedRegion is None:
+            fetchData = True
+        else:
+            for c in ['north', 'south', 'east', 'west']:
+                if self.renderedRegion and \
+                   region[c] != self.renderedRegion[c]:
+                    fetchData = True
+                    break
+
+            for c in ['e-w resol', 'n-s resol']:
+                if self.renderedRegion and \
+                    region[c] != self.renderedRegion[c]:
+                    zoomChanged = True
+                    break
+
+        if fetchData:
+            self.renderedRegion = region
+
+            grass.try_remove(self.mapfile)
+            grass.try_remove(self.tempMap)
+
+            self.currentPid = self.thread.GetId()
+            self.thread.abort()
+            self.downloading = True
+
+            cmdList = utils.CmdTupleToList(cmd)
+
+            if Debug.GetLevel() < 3:
+                cmdList.append('--quiet')
+                
+            tempPngfile = os.environ["GRASS_PNGFILE"]
+            os.environ["GRASS_PNGFILE"] = self.tempMap
+
+            tempRegion = os.environ["GRASS_REGION"]
+            os.environ["GRASS_REGION"] = self._createRegionStr(region)
+
+            self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
+
+            os.environ["GRASS_PNGFILE"] = tempPngfile
+            os.environ["GRASS_REGION"] = tempRegion
+
+    def OnDataFetched(self, event):
+        """!Fetch data
+
+        @todo ?
+        @todo needs refactoring -  self.parent.parent.mapWin.UpdateMap
+        """
+        if event.pid != self.currentPid:
+            return
+        self.downloading = False
+        if not self.updateMap:
+            # TODO
+            self.parent.parent.GetParentMapWindow().frame.GetProgressBar().UpdateProgress(self.parent, self.parent.parent)
+            self.renderedRegion = None
+            return
+
+        self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
+                                          bandsNum = 3, gdalDriver = 'PNM', fillValue = 0)
+        self.mapMerger.AddRasterBands(self.tempMap, {1 : 1, 2 : 2, 3 : 3})
+        del self.mapMerger
+
+        self.maskMerger = GDALRasterMerger(targetFile = self.maskfile, region = self.renderedRegion,
+                                           bandsNum = 1, gdalDriver = 'PNM', fillValue = 0)
+        #{4 : 1} alpha channel (4) to first and only channel (1) in mask
+        self.maskMerger.AddRasterBands(self.tempMap, {4 : 1}) 
+        del self.maskMerger
+
+        self.parent.parent.GetParentMapWindow().UpdateMap(render = True)
+
+    def _getRegionDict(self):
+        """!Parse string from GRASS_REGION env variable into dict.
+        """
+        region = {}
+        parsedRegion = os.environ["GRASS_REGION"].split(';')
+        for r in parsedRegion:
+            r = r.split(':')
+            r[0] = r[0].strip()
+            if len(r) < 2:
+                continue
+            try:
+                if r[0] in ['cols', 'rows']:
+                    region[r[0]] = int(r[1])
+                else:
+                    region[r[0]] = float(r[1])
+            except ValueError:
+                region[r[0]] = r[1]
+
+        return region
+
+    def _createRegionStr(self, region):
+        """!Create string for GRASS_REGION env variable from  dict created by _getRegionDict.
+        """
+        regionStr = ''
+        for k, v in region.iteritems():
+            item = k + ': ' + str(v)
+            if regionStr:
+                regionStr += '; '
+            regionStr += item
+
+        return regionStr
+
+    def IsDownloading(self):
+        """!Is it downloading any data from WMS server? 
+        """
+        return self.downloading
+
+    def _fitAspect(self, region, size):
+        """!Compute region parameters to have direction independent resolution.
+        """
+        if region['n-s resol'] > region['e-w resol']:
+            delta = region['n-s resol'] * size['cols'] / 2
+
+            center = (region['east'] - region['west'])/2
+
+            region['east'] = center + delta + region['west']
+            region['west'] = center - delta + region['west']
+            region['e-w resol'] = region['n-s resol']
+
+        else:
+            delta = region['e-w resol'] * size['rows'] / 2
+
+            center = (region['north'] - region['south'])/2 
+
+            region['north'] = center + delta + region['south']
+            region['south'] = center - delta + region['south']
+            region['n-s resol'] = region['e-w resol']
+
+    def Abort(self):
+        """!Abort process"""
+        self.updateMap = False
+        self.thread.abort(abortall = True)        
+
+class StdErr:
+    """!Redirect error output according to debug mode.
+
+    @todo: replace or move to the other module (gconsole???)
+    """
+    def flush(self):
+        pass
+    
+    def write(self, s):
+        if "GtkPizza" in s:
+            return
+        if Debug.GetLevel() == 0:
+            message = ''
+            for line in s.splitlines():
+                if len(line) == 0:
+                    continue
+                if 'GRASS_INFO_ERROR' in line:
+                    message += line.split(':', 1)[1].strip() + '\n'
+                elif 'ERROR:' in line:
+                    message = line
+                if message:
+                    sys.stderr.write(message)
+                    message = ''
+            sys.stderr.flush()
+
+        elif Debug.GetLevel() >= 1:
+            for line in s.splitlines():
+                if len(line) == 0:
+                    continue
+                Debug.msg(3, line)
+
+class GDALRasterMerger:
+    """!Merge rasters.
+
+        Based on gdal_merge.py utility.
+    """
+    def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
+        """!Create raster for merging.
+        """
+        self.gdalDrvType = gdalDriver
+
+        nsRes = (region['south'] - region['north']) / region['rows']
+        ewRes = (region['east'] - region['west']) / region['cols']
+
+        self.tGeotransform = [region['west'], ewRes, 0, region['north'], 0, nsRes]
+
+        self.tUlx, self.tUly, self.tLrx, self.tLry = self._getCorners(self.tGeotransform, region)
+
+        driver = gdal.GetDriverByName(self.gdalDrvType)
+        self.tDataset = driver.Create(targetFile, region['cols'], region['rows'], bandsNum,  gdal.GDT_Byte)
+
+        if fillValue is not None:
+            # fill raster bands with a constant value
+            for iBand in range(1, self.tDataset.RasterCount + 1):
+                self.tDataset.GetRasterBand(iBand).Fill(fillValue)
+
+    def AddRasterBands(self, sourceFile, sTBands):
+        """!Add raster bands from sourceFile into the merging raster.
+        """
+        sDataset = gdal.Open(sourceFile, gdal.GA_ReadOnly) 
+        if sDataset is None:
+            return
+
+        sGeotransform = sDataset.GetGeoTransform()
+
+        sSize = {
+                    'rows' :  sDataset.RasterYSize,
+                    'cols' :  sDataset.RasterXSize
+                 }
+
+        sUlx, sUly, sLrx, sLry = self._getCorners(sGeotransform, sSize)
+
+        # figure out intersection region
+        tIntsctUlx = max(self.tUlx,sUlx)
+        tIntsctLrx = min(self.tLrx,sLrx)
+        if self.tGeotransform[5] < 0:
+            tIntsctUly = min(self.tUly,sUly)
+            tIntsctLry = max(self.tLry,sLry)
+        else:
+            tIntsctUly = max(self.tUly,sUly)
+            tIntsctLry = min(self.tLry,sLry)
+        
+        # do they even intersect?
+        if tIntsctUlx >= tIntsctLrx:
+            return
+        if self.tGeotransform[5] < 0 and tIntsctUly <= tIntsctLry:
+            return
+        if self.tGeotransform[5] > 0 and tIntsctUly >= tIntsctLry:
+            return
+
+
+        # compute target window in pixel coordinates.
+        tXoff = int((tIntsctUlx - self.tGeotransform[0]) / self.tGeotransform[1] + 0.1)
+        tYoff = int((tIntsctUly - self.tGeotransform[3]) / self.tGeotransform[5] + 0.1)
+        tXsize = int((tIntsctLrx - self.tGeotransform[0])/self.tGeotransform[1] + 0.5) - tXoff
+        tYsize = int((tIntsctLry - self.tGeotransform[3])/self.tGeotransform[5] + 0.5) - tYoff
+
+        if tXsize < 1 or tYsize < 1:
+            return
+
+        # Compute source window in pixel coordinates.
+        sXoff = int((tIntsctUlx - sGeotransform[0]) / sGeotransform[1])
+        sYoff = int((tIntsctUly - sGeotransform[3]) / sGeotransform[5])
+        sXsize = int((tIntsctLrx - sGeotransform[0]) / sGeotransform[1] + 0.5) - sXoff
+        sYsize = int((tIntsctLry - sGeotransform[3]) / sGeotransform[5] + 0.5) - sYoff
+
+        if sXsize < 1 or sYsize < 1:
+            return
+
+        for sBandNnum, tBandNum in sTBands.iteritems():
+            bandData = sDataset.GetRasterBand(sBandNnum).ReadRaster(sXoff, sYoff, sXsize,
+                                                                    sYsize, tXsize, tYsize, gdal.GDT_Byte)
+            self.tDataset.GetRasterBand(tBandNum).WriteRaster(tXoff, tYoff, tXsize, tYsize, bandData, 
+                                                              tXsize, tYsize, gdal.GDT_Byte)
+
+    def _getCorners(self, geoTrans, size):
+
+        ulx = geoTrans[0]
+        uly = geoTrans[3]
+        lrx = geoTrans[0] + size['cols'] * geoTrans[1]
+        lry = geoTrans[3] + size['rows'] * geoTrans[5]
+
+        return ulx, uly, lrx, lry
+
+    def __del__(self):
+        self.tDataset = None


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

Modified: grass/trunk/gui/wxpython/gcp/mapdisplay.py
===================================================================
--- grass/trunk/gui/wxpython/gcp/mapdisplay.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/gcp/mapdisplay.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -256,7 +256,7 @@
         """
         Update progress bar info
         """
-        self.GetProgressBar().SetValue(event.value)
+        self.GetProgressBar().UpdateProgress(event.layer, event.map)
         
         event.Skip()
         

Modified: grass/trunk/gui/wxpython/gui_core/mapwindow.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/mapwindow.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/gui_core/mapwindow.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -39,6 +39,7 @@
     def __init__(self, parent, giface, Map, frame, **kwargs):
         self.parent = parent
         self.Map = Map
+        self.Map.SetParentMapWindow(self)
         self.frame = frame
         self._giface = giface
         

Modified: grass/trunk/gui/wxpython/lmgr/frame.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/frame.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/lmgr/frame.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -608,7 +608,8 @@
                          'd.rhumbline'    : 'rhumb',
                          'd.labels'       : 'labels',
                          'd.barscale'     : 'barscale',
-                         'd.redraw'       : 'redraw'}[command[0]]
+                         'd.redraw'       : 'redraw',
+                         'd.wms'          : 'wms'}[command[0]]
         except KeyError:
             GMessage(parent = self,
                      message = _("Command '%s' not yet implemented in the WxGUI. "
@@ -1502,8 +1503,7 @@
         """!Import data from OGC WMS server"""
         from ogc_services.wms import WMSDialog
         dlg = WMSDialog(parent = self)
-        dlg.CenterOnScreen()
-        
+        dlg.CenterOnScreen()         
         if dlg.ShowModal() == wx.ID_OK: # -> import layers
             layers = dlg.GetLayers()
             
@@ -1529,7 +1529,7 @@
                 
                 
         dlg.Destroy()
-        
+
     def OnShowAttributeTable(self, event, selection = None):
         """!Show attribute table of the given vector map layer
         """

Modified: grass/trunk/gui/wxpython/lmgr/layertree.py
===================================================================
--- grass/trunk/gui/wxpython/lmgr/layertree.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/lmgr/layertree.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -91,8 +91,10 @@
     'addRast3d'  : MetaIcon(img = 'layer-raster3d-add',
                             label = _('Add 3D raster map layer'),
                             desc  =  _('Note that 3D raster data are rendered only in 3D view mode')),
+    'wsImport'  :  MetaIcon(img = 'layer-wms-add',
+                            label = _('Add WMS layer.')),
     'layerOptions'  : MetaIcon(img = 'options',
-                               label = _('Set options')),
+                               label = _('Set options'))
     }
 
 class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
@@ -224,6 +226,9 @@
         
         trgif = LMIcons["addCmd"].GetBitmap(bmpsize)
         self.cmd_icon = il.Add(trgif)
+
+        trgif = LMIcons["wsImport"].GetBitmap(bmpsize)
+        self.ws_icon = il.Add(trgif)
         
         self.AssignImageList(il)
 
@@ -941,7 +946,10 @@
             self.SetItemImage(layer, self.folder, CT.TreeItemIcon_Normal)
             self.SetItemImage(layer, self.folder_open, CT.TreeItemIcon_Expanded)
             self.SetItemText(layer, grouptext)
-        
+        elif ltype == 'wms':            
+            self.SetItemImage(layer, self.ws_icon)
+            self.SetItemText(layer, '%s %s' % (_('wms'), label))
+    
         self.first = False
         
         if ltype != 'group':
@@ -1022,11 +1030,7 @@
         else:
             if ltype == 'group':
                 self.OnRenameLayer(None)
-        
-        # updated progress bar range (mapwindow statusbar)
-        if checked:
-            self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-        
+                
         return layer
 
     def PropertiesDialog(self, layer, show = True):
@@ -1104,7 +1108,10 @@
             
         elif ltype == 'labels':
             cmd = ['d.labels']
-        
+
+        elif ltype == 'wms':
+            cmd = ['d.wms']
+
         if cmd:
             GUI(parent = self, centreOnParent = False).ParseCommand(cmd,
                                                                     completed = (self.GetOptData,layer,params))
@@ -1156,9 +1163,6 @@
         if self.mapdisplay.GetToolbar('vdigit'):
             self.mapdisplay.toolbars['vdigit'].UpdateListOfLayers (updateTool = True)
 
-        # update progress bar range (mapwindow statusbar)
-        self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-
         # here was some dead code related to layer and nviz
         # however, in condition was rerender = False
         # but rerender is alway True
@@ -1206,9 +1210,6 @@
                     # ignore when map layer is edited
                     self.Map.ChangeLayerActive(mapLayer, checked)
         
-        # update progress bar range (mapwindow statusbar)
-        self.mapdisplay.GetProgressBar().SetRange(len(self.Map.GetListOfLayers(l_active = True)))
-        
         # nviz
         if self.lmgr.IsPaneShown('toolbarNviz') and \
                 self.GetPyData(item) is not None:

Modified: grass/trunk/gui/wxpython/mapdisp/frame.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/frame.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/mapdisp/frame.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -426,7 +426,7 @@
     def OnUpdateProgress(self, event):
         """!Update progress bar info
         """
-        self.GetProgressBar().SetValue(event.value)
+        self.GetProgressBar().UpdateProgress(event.layer, event.map)
         
         event.Skip()
         

Modified: grass/trunk/gui/wxpython/mapdisp/mapwindow.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/mapwindow.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/mapdisp/mapwindow.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -579,7 +579,6 @@
         @param renderVector re-render vector map layer enabled for editing (used for digitizer)
         """
         start = time.clock()
-        
         self.resize = False
         
         # was if self.Map.cmdfile and ...
@@ -587,14 +586,6 @@
             render = True
         
         #
-        # initialize process bar (only on 'render')
-        #
-        if render or renderVector:
-            self.frame.GetProgressBar().Show()
-            if self.frame.GetProgressBar().GetRange() > 0:
-                self.frame.GetProgressBar().SetValue(1)
-        
-        #
         # render background image if needed
         #
         # update layer dictionary if there has been a change in layers
@@ -707,18 +698,6 @@
             
         stop = time.clock()
         
-        #
-        # hide process bar
-        #
-        self.frame.GetProgressBar().Hide()
-
-        #
-        # update statusbar 
-        #
-        ### self.Map.SetRegion()
-        self.frame.StatusbarUpdate()
-        
-        
         Debug.msg (1, "BufferedWindow.UpdateMap(): render=%s, renderVector=%s -> time=%g" % \
                    (render, renderVector, (stop-start)))
         
@@ -779,6 +758,9 @@
         
         self.Draw(self.pdcDec, pdctype = 'clear')
         self.Draw(self.pdcTmp, pdctype = 'clear')
+
+        for layer in self.Map.GetListOfLayers(l_active = True):
+            layer.AbortDownload()
         
     def DragMap(self, moveto):
         """!Drag the entire map image for panning.

Modified: grass/trunk/gui/wxpython/mapdisp/statusbar.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/statusbar.py	2012-12-28 21:49:46 UTC (rev 54453)
+++ grass/trunk/gui/wxpython/mapdisp/statusbar.py	2012-12-29 10:45:35 UTC (rev 54454)
@@ -21,6 +21,8 @@
  - statusbar::SbRegionExtent
  - statusbar::SbCompRegionExtent
  - statusbar::SbProgress
+ - statusbar::SbRMSError
+ - statusbar::SbGoToGCP
 
 (C) 2006-2011 by the GRASS Development Team
 
@@ -83,7 +85,7 @@
         
         self._postInitialized = False
         
-        self.progressbar = SbProgress(self.mapFrame, self.statusbar)
+        self.progressbar = SbProgress(self.mapFrame, self.statusbar, self)
         
         self._hiddenItems = {}
     
@@ -116,7 +118,7 @@
     def AddStatusbarItem(self, item):
         """!Adds item to statusbar
         
-        If item position is 0, item is managed by choice.
+        If item position is 0, item is managed by choice.        
         
         @see AddStatusbarItemsByClass
         """
@@ -180,7 +182,9 @@
         
         @see Update
         """
-        self.statusbarItems[itemName].Show()
+        if self.statusbarItems[itemName].GetPosition() != 0 or \
+           not self.progressbar.IsShown():
+            self.statusbarItems[itemName].Show()
         
     def _postInit(self):
         """!Post-initialization method
@@ -208,16 +212,20 @@
 
         It always updates mask.
         """
+        self.progressbar.Update()
+
         if not self._postInitialized:
             self._postInit()
-        
         for item in self.statusbarItems.values():
             if item.GetPosition() == 0:
-                item.Hide()
+                if not self.progressbar.IsShown():
+                    item.Hide()
             else:
                 item.Update() # mask, render
-        
-        if self.choice.GetCount() > 0:
+
+        if self.progressbar.IsShown():
+            pass
+        elif self.choice.GetCount() > 0:
             item = self.choice.GetClientData(self.choice.GetSelection())
             item.Update()
         
@@ -233,7 +241,7 @@
             widgets.append((item.GetPosition(), item.GetWidget()))
             
         widgets.append((1, self.choice))
-        widgets.append((0, self.progressbar.GetWidget()))
+        widgets.append((1, self.progressbar.GetWidget()))
                 
         for idx, win in widgets:
             if not win:
@@ -242,8 +250,6 @@
             if idx == 0: # show region / mapscale / process bar
                 # -> size
                 wWin, hWin = win.GetBestSize()
-                if win == self.progressbar.GetWidget():
-                    wWin = rect.width - 6
                 # -> position
                 # if win == self.statusbarWin['region']:
                 # x, y = rect.x + rect.width - wWin, rect.y - 1
@@ -254,6 +260,8 @@
             else: # choice || auto-rendering
                 x, y = rect.x, rect.y
                 w, h = rect.width, rect.height + 1
+                if win == self.progressbar.GetWidget():
+                    wWin = rect.width - 6
                 if idx == 2: # mask
                     x += 5
                     y += 4
@@ -956,19 +964,21 @@
         return self.mapFrame.GetMap().GetRegion() # computational region
         
         
-class SbProgress(SbItem):
+class SbProgress(SbTextItem):
     """!General progress bar to show progress.
     
     Underlaying widget is wx.Gauge.
     """
-    def __init__(self, mapframe, statusbar, position = 0):
+    def __init__(self, mapframe, statusbar, sbManager, position = 0):
         SbItem.__init__(self, mapframe, statusbar, position)
         self.name = 'progress'
-
+        self.sbManager = sbManager
         # on-render gauge
         self.widget = wx.Gauge(parent = self.statusbar, id = wx.ID_ANY,
-                                      range = 0, style = wx.GA_HORIZONTAL)
+                               range = 0, style = wx.GA_HORIZONTAL)
         self.widget.Hide()
+
+        self.maps = {}
         
     def GetRange(self):
         """!Returns progress range."""
@@ -978,7 +988,86 @@
         """!Sets progress range."""
         self.widget.SetRange(range)
     
+    def IsShown(self):
+        """!Is progress bar shown
+        """
+        return self.widget.IsShown()
+                
+    def UpdateProgress(self, layer, map):
+        """!Update progress"""
+        
+        if map not in self.maps or layer is None:
+            # self.map holds values needed for progress info for every Render instance in mapframe
+            self.maps[map] = {'progresVal' : 0, # current progress value
+                              'downloading' : [], # layers, which are downloading data
+                              'rendered' : [], # already rendered layers
+                              'range' : len(map.GetListOfLayers(l_active = True))}
+        else:
+            if layer not in self.maps[map]['rendered']:
+                self.maps[map]['rendered'].append(layer)
+            if layer.IsDownloading() and \
+                    layer not in self.maps[map]['downloading']:
+                self.maps[map]['downloading'].append(layer)
+            else:
+                self.maps[map]['progresVal'] += 1
+                if layer in self.maps[map]['downloading']:
+                    self.maps[map]['downloading'].remove(layer)
+        
+        self.Update(map)
+        self.sbManager.Update()
+        
+    def Update(self, map = None):      
+        """!Update statusbar"""
+        activeMap = self.mapFrame.GetMap()
+        if map is None:
+                map = activeMap
+        if map not in self.maps:
+            return
+        if map != activeMap:
+            return
 
+        # update progress bar
+        if self.maps[map]['range'] == self.maps[map]['progresVal']:
+            self.widget.Hide()
+            return
+        elif self.maps[map]['range'] > 0:
+            if self.widget.GetRange() != self.maps[map]['range']:
+                self.widget.SetRange(self.maps[map]['range'])
+            self.widget.Show()
+        else:
+            return
+        
+        self.widget.SetValue(self.maps[map]['progresVal'])
+        
+        # update statusbar text
+        st_text = ''
+        first = True
+        for layer in self.maps[map]['downloading']:
+            if first:
+                st_text += _("Downloading data...")
+                first = False
+            else:
+                st_text += ', '
+            st_text += layer.GetName()
+        
+        if  self.maps[map]['range'] != len(self.maps[map]['rendered']):
+            if st_text:
+                st_text = _('Rendering & ') + st_text
+            else:
+                st_text = _('Rendering...')
+
+        self.statusbar.SetStatusText(st_text, self.position)
+
+    def GetValue(self):
+        return self.widget.GetValue()
+    
+    def GetWidget(self):
+        """!Returns underlaying winget.
+        
+        @return widget or None if doesn't exist
+        """
+        return self.widget
+    
 class SbGoToGCP(SbItem):
     """!SpinCtrl to select GCP to focus on
     
@@ -1074,3 +1163,4 @@
                                    { 'forw' : self.mapFrame.GetFwdError(),
                                      'back' : self.mapFrame.GetBkwError() })
         SbTextItem.Show(self)
+        



More information about the grass-commit mailing list