فهرست منبع

wxGUI: fully-featured support for web services (work in progress)
patch provided by Stepan Turek


git-svn-id: https://svn.osgeo.org/grass/grass/trunk@54607 15284696-431f-4ddb-bdfa-cd5b030d7da7

Martin Landa 12 سال پیش
والد
کامیت
28dbc8ea68

+ 2 - 2
gui/wxpython/Makefile

@@ -11,12 +11,12 @@ ETCDIR = $(ETC)/gui/wxpython
 
 
 SRCFILES := $(wildcard icons/*.* scripts/* xml/*) \
 SRCFILES := $(wildcard icons/*.* scripts/* xml/*) \
 	$(wildcard animation/* core/* dbmgr/* gcp/* gmodeler/* gui_core/* iclass/* lmgr/* location_wizard/* \
 	$(wildcard animation/* core/* dbmgr/* gcp/* gmodeler/* gui_core/* iclass/* lmgr/* location_wizard/* \
-	mapdisp/* modules/* nviz/* psmap/* mapswipe/* vdigit/* wxplot/* ogc_services/* rlisetup/* vnet/*) \
+	mapdisp/* modules/* nviz/* psmap/* mapswipe/* vdigit/* wxplot/* web_services/* rlisetup/* vnet/*) \
 	gis_set.py gis_set_error.py wxgui.py README
 	gis_set.py gis_set_error.py wxgui.py README
 DSTFILES := $(patsubst %,$(ETCDIR)/%,$(SRCFILES)) $(patsubst %.py,$(ETCDIR)/%.pyc,$(filter %.py,$(SRCFILES)))
 DSTFILES := $(patsubst %,$(ETCDIR)/%,$(SRCFILES)) $(patsubst %.py,$(ETCDIR)/%.pyc,$(filter %.py,$(SRCFILES)))
 
 
 PYDSTDIRS := $(patsubst %,$(ETCDIR)/%,animation core dbmgr gcp gmodeler gui_core iclass lmgr location_wizard \
 PYDSTDIRS := $(patsubst %,$(ETCDIR)/%,animation core dbmgr gcp gmodeler gui_core iclass lmgr location_wizard \
-	mapdisp modules nviz psmap mapswipe vdigit wxplot ogc_services rlisetup vnet)
+	mapdisp modules nviz psmap mapswipe vdigit wxplot web_services rlisetup vnet)
 DSTDIRS := $(patsubst %,$(ETCDIR)/%,icons scripts xml)
 DSTDIRS := $(patsubst %,$(ETCDIR)/%,icons scripts xml)
 
 
 default: $(DSTFILES)
 default: $(DSTFILES)

+ 11 - 0
gui/wxpython/core/events.py

@@ -44,3 +44,14 @@ gShowNotification, EVT_SHOW_NOTIFICATION = NewCommandEvent()
 gMapCreated, EVT_MAP_CREATED = NewCommandEvent()
 gMapCreated, EVT_MAP_CREATED = NewCommandEvent()
 
 
 gZoomChanged, EVT_ZOOM_CHANGED = NewEvent()
 gZoomChanged, EVT_ZOOM_CHANGED = NewEvent()
+
+# Post it to BufferedWindow instance, which you want to update.
+# For relevant attributes for the event see 
+# mapdisp.mapwindow.BufferedWindow UpdateMap method arguments.
+# If event does not contain attribute corresponding to argument with default
+# value, the default value will be used.
+# Arguments with no default value must be among event attributes
+# in order to be the event processed.
+# TODO implement to NVIZ GLWindow
+# TODO change direct calling of UpdateMap method to posting this event 
+gUpdateMap, EVT_UPDATE_MAP = NewEvent()

+ 7 - 0
gui/wxpython/core/gconsole.py

@@ -126,11 +126,15 @@ class CmdThread(threading.Thread):
             requestTime = time.time()
             requestTime = time.time()
 
 
             # prepare
             # prepare
+            if not self.receiver:
+                return
+
             event = wxCmdPrepare(cmd=args[0],
             event = wxCmdPrepare(cmd=args[0],
                                  time=requestTime,
                                  time=requestTime,
                                  pid=requestId,
                                  pid=requestId,
                                  onPrepare=vars()['onPrepare'],
                                  onPrepare=vars()['onPrepare'],
                                  userData=vars()['userData'])
                                  userData=vars()['userData'])
+
             wx.PostEvent(self.receiver, event)
             wx.PostEvent(self.receiver, event)
 
 
             # run command
             # run command
@@ -188,6 +192,9 @@ class CmdThread(threading.Thread):
                     self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
                     self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
                     self.resultQ.put((requestId, self.requestCmdColor.run()))
                     self.resultQ.put((requestId, self.requestCmdColor.run()))
 
 
+            if not self.receiver:
+                return
+
             event = wxCmdDone(cmd=args[0],
             event = wxCmdDone(cmd=args[0],
                               aborted=aborted,
                               aborted=aborted,
                               returncode=returncode,
                               returncode=returncode,

+ 60 - 50
gui/wxpython/core/render.py

@@ -6,6 +6,8 @@
 @todo Implement RenderManager also for other layers (see WMS
 @todo Implement RenderManager also for other layers (see WMS
 implementation for details)
 implementation for details)
 
 
+@todo Render classes should not care about updating statusbar (change emiting events).
+
 Classes:
 Classes:
  - render::Layer
  - render::Layer
  - render::MapLayer
  - render::MapLayer
@@ -36,13 +38,14 @@ from wx.lib.newevent import NewEvent
 from grass.script import core as grass
 from grass.script import core as grass
 
 
 from core          import utils
 from core          import utils
-from core.ws       import RenderWMSMgr
 from core.gcmd     import GException, GError, RunCommand
 from core.gcmd     import GException, GError, RunCommand
 from core.debug    import Debug
 from core.debug    import Debug
 from core.settings import UserSettings
 from core.settings import UserSettings
 
 
 wxUpdateProgressBar, EVT_UPDATE_PRGBAR = NewEvent()
 wxUpdateProgressBar, EVT_UPDATE_PRGBAR = NewEvent()
 
 
+from core.ws       import RenderWMSMgr
+
 USE_GPNMCOMP = True
 USE_GPNMCOMP = True
 
 
 class Layer(object):
 class Layer(object):
@@ -51,10 +54,8 @@ class Layer(object):
     
     
     - For map layer use MapLayer class.
     - For map layer use MapLayer class.
     - For overlays use Overlay class.
     - For overlays use Overlay class.
-
-    @todo needs refactoring (avoid parent)
     """
     """
-    def __init__(self, ltype, cmd, parent, name = None,
+    def __init__(self, ltype, cmd, Map, name = None,
                  active = True, hidden = False, opacity = 1.0):
                  active = True, hidden = False, opacity = 1.0):
         """!Create new instance
         """!Create new instance
         
         
@@ -63,12 +64,12 @@ class Layer(object):
         @param ltype layer type ('raster', 'vector', 'overlay', 'command', etc.)
         @param ltype layer type ('raster', 'vector', 'overlay', 'command', etc.)
         @param cmd GRASS command to render layer,
         @param cmd GRASS command to render layer,
         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
+        @param Map render.Map instance
         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree)
         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree)
         @param active layer is active, will be rendered only if True
         @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 hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        self.parent = parent
         
         
         # generated file for each layer
         # generated file for each layer
         if USE_GPNMCOMP or ltype == 'overlay':
         if USE_GPNMCOMP or ltype == 'overlay':
@@ -84,6 +85,8 @@ class Layer(object):
         
         
         # stores class which manages rendering instead of simple command - e. g. wms
         # stores class which manages rendering instead of simple command - e. g. wms
         self.renderMgr = None
         self.renderMgr = None
+
+        self.Map = Map
         self.type      = None
         self.type      = None
         self.SetType(ltype)
         self.SetType(ltype)
         self.name  = name
         self.name  = name
@@ -178,8 +181,6 @@ class Layer(object):
     
     
     def _runCommand(self, cmd):
     def _runCommand(self, cmd):
         """!Run command to render data
         """!Run command to render data
-
-        @todo catch error for wms (?)
         """ 
         """ 
         if self.type == 'wms':
         if self.type == 'wms':
             ret = 0
             ret = 0
@@ -268,7 +269,11 @@ class Layer(object):
             raise GException(_("Unsupported map layer type '%s'") % ltype)
             raise GException(_("Unsupported map layer type '%s'") % ltype)
         
         
         if ltype == 'wms' and not isinstance(self.renderMgr, RenderWMSMgr):
         if ltype == 'wms' and not isinstance(self.renderMgr, RenderWMSMgr):
-            self.renderMgr = RenderWMSMgr(self, self.mapfile, self.maskfile)
+            self.renderMgr = RenderWMSMgr(receiver = self.Map.GetReceiver(),
+                                          layer = self, 
+                                          Map = self.Map, 
+                                          mapfile = self.mapfile, 
+                                          maskfile = self.maskfile)
         elif self.type == 'wms' and ltype != 'wms':
         elif self.type == 'wms' and ltype != 'wms':
             self.renderMgr = None
             self.renderMgr = None
         
         
@@ -315,27 +320,32 @@ class Layer(object):
         else:
         else:
             return self.renderMgr.IsDownloading()
             return self.renderMgr.IsDownloading()
 
 
-    def AbortDownload(self):
-        """!Abort downloading data"""
+    def AbortThread(self):
+        """!Abort running thread e. g. downloading data"""
         if self.renderMgr is None:
         if self.renderMgr is None:
             return
             return
         else:
         else:
             self.renderMgr.Abort()
             self.renderMgr.Abort()
 
 
+    def GetRenderMgr(self):
+        """!Get render manager """
+        return self.renderMgr
+
 class MapLayer(Layer):
 class MapLayer(Layer):
-    def __init__(self, ltype, cmd, parent, name = None,
+    def __init__(self, ltype, cmd, Map, name = None,
                  active = True, hidden = False, opacity = 1.0): 
                  active = True, hidden = False, opacity = 1.0): 
         """!Represents map layer in the map canvas
         """!Represents map layer in the map canvas
         
         
         @param ltype layer type ('raster', 'vector', 'command', etc.)
         @param ltype layer type ('raster', 'vector', 'command', etc.)
         @param cmd GRASS command to render layer,
         @param cmd GRASS command to render layer,
         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
         given as list, e.g. ['d.rast', 'map=elevation@PERMANENT']
+        @param Map render.Map instance
         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree) or None
         @param name layer name, e.g. 'elevation@PERMANENT' (for layer tree) or None
         @param active layer is active, will be rendered only if True
         @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 hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        Layer.__init__(self, ltype, cmd, parent, name,
+        Layer.__init__(self, ltype, cmd, Map, name,
                        active, hidden, opacity)
                        active, hidden, opacity)
         
         
     def GetMapset(self):
     def GetMapset(self):
@@ -353,7 +363,7 @@ class MapLayer(Layer):
             return self.name
             return self.name
         
         
 class Overlay(Layer):
 class Overlay(Layer):
-    def __init__(self, id, ltype, cmd, parent,
+    def __init__(self, id, ltype, cmd, Map,
                  active = True, hidden = True, opacity = 1.0):
                  active = True, hidden = True, opacity = 1.0):
         """!Represents overlay displayed in map canvas
         """!Represents overlay displayed in map canvas
         
         
@@ -361,11 +371,12 @@ class Overlay(Layer):
         @param type overlay type ('barscale', 'legend', etc.)
         @param type overlay type ('barscale', 'legend', etc.)
         @param cmd GRASS command to render overlay,
         @param cmd GRASS command to render overlay,
         given as list, e.g. ['d.legend', 'map=elevation@PERMANENT']
         given as list, e.g. ['d.legend', 'map=elevation@PERMANENT']
+        @param Map render.Map instance
         @param active layer is active, will be rendered only if True
         @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 hidden layer is hidden, won't be listed in Layer Manager if True
         @param opacity layer opacity <0;1>
         @param opacity layer opacity <0;1>
         """
         """
-        Layer.__init__(self, 'overlay', cmd, parent, ltype,
+        Layer.__init__(self, 'overlay', cmd, Map, ltype,
                        active, hidden, opacity)
                        active, hidden, opacity)
         self.id = id
         self.id = id
         
         
@@ -400,8 +411,8 @@ class Map(object):
         self._initGisEnv() # g.gisenv
         self._initGisEnv() # g.gisenv
         self.GetWindow()
         self.GetWindow()
 
 
-        # reference to mapWindow, which contains this Map instance
-        self.mapWin = None
+        # receiver of events
+        self.receiver = None
 
 
         # GRASS environment variable (for rendering)
         # GRASS environment variable (for rendering)
         self.env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
         self.env = {"GRASS_BACKGROUNDCOLOR" : "FFFFFF",
@@ -607,7 +618,7 @@ class Map(object):
         @param update if True update current display region settings
         @param update if True update current display region settings
         @param add3d add 3d region settings
         @param add3d add 3d region settings
         
         
-        @return region settings as directory, e.g. {
+        @return region settings as dictionary, e.g. {
         'n':'4928010', 's':'4913700', 'w':'589980',...}
         'n':'4928010', 's':'4913700', 'w':'589980',...}
         
         
         @see GetCurrentRegion()
         @see GetCurrentRegion()
@@ -796,7 +807,7 @@ class Map(object):
         
         
         except:
         except:
             return None
             return None
-        
+    
     def GetListOfLayers(self, ltype = None, mapset = None, name = None,
     def GetListOfLayers(self, ltype = None, mapset = None, name = None,
                         active = None, hidden = None):
                         active = None, hidden = None):
         """!Returns list of layers of selected properties or list of
         """!Returns list of layers of selected properties or list of
@@ -865,11 +876,10 @@ class Map(object):
         
         
         return selected
         return selected
 
 
-    def _renderLayers(self, force = False, mapWindow = None, overlaysOnly = False):
+    def _renderLayers(self, force = False, overlaysOnly = False):
         """!Render all map layers into files
         """!Render all map layers into files
 
 
         @param force True to force rendering
         @param force True to force rendering
-        @param mapWindow GUI window or None (statusbar/progress bar)
         @param overlaysOnly True to render only overlays
         @param overlaysOnly True to render only overlays
 
 
         @return list of maps, masks and opacities
         @return list of maps, masks and opacities
@@ -884,13 +894,9 @@ class Map(object):
             layers = self.layers + self.overlays
             layers = self.layers + self.overlays
         
         
         self.downloading = False
         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.
-        if mapWindow:
-            mapWindow.GetProgressBar().UpdateProgress(None, self)
-        ### wx.PostEvent(mapWindow, event)
+        if self.receiver:
+            event = wxUpdateProgressBar(layer = None, map = self)
+            self.receiver.GetEventHandler().ProcessEvent(event)
         for layer in layers:
         for layer in layers:
             # skip non-active map layers
             # skip non-active map layers
             if not layer or not layer.active:
             if not layer or not layer.active:
@@ -903,12 +909,9 @@ class Map(object):
 
 
             if layer.IsDownloading():
             if layer.IsDownloading():
                 self.downloading = True     
                 self.downloading = True     
-            if mapWindow:
-                # update progress bar
-                ### wx.SafeYield(mapWindow)
-                mapWindow.GetProgressBar().UpdateProgress(layer, self)
-                #event = wxUpdateProgressBar(layer = layer, map = self)
-                #wx.PostEvent(mapWindow, event)
+            if self.receiver:
+                event = wxUpdateProgressBar(layer = layer, map = self)
+                self.receiver.GetEventHandler().ProcessEvent(event) 
 
 
             # skip map layers when rendering fails
             # skip map layers when rendering fails
             if not os.path.exists(layer.mapfile):
             if not os.path.exists(layer.mapfile):
@@ -924,22 +927,21 @@ class Map(object):
         
         
         return maps, masks, opacities
         return maps, masks, opacities
         
         
-    def GetMapsMasksAndOpacities(self, force, guiFrame, windres):
+    def GetMapsMasksAndOpacities(self, force, windres):
         """!
         """!
         Used by Render function.
         Used by Render function.
         
         
         @return maps, masks, opacities
         @return maps, masks, opacities
         """
         """
-        return self._renderLayers(force, guiFrame)
+        return self._renderLayers(force)
     
     
-    def Render(self, force = False, mapWindow = None, windres = False):
+    def Render(self, force = False, windres = False):
         """!Creates final image composite
         """!Creates final image composite
         
         
         This function can conditionaly use high-level tools, which
         This function can conditionaly use high-level tools, which
         should be avaliable in wxPython library
         should be avaliable in wxPython library
         
         
         @param force force rendering
         @param force force rendering
-        @param mapWindow reference for MapFrame or similar instance for progress bar
         @param windres use region resolution (True) otherwise display resolution
         @param windres use region resolution (True) otherwise display resolution
         
         
         @return name of file with rendered image or None
         @return name of file with rendered image or None
@@ -960,7 +962,7 @@ class Map(object):
         else:
         else:
             os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo"
             os.environ["GRASS_RENDER_IMMEDIATE"] = "cairo"
         
         
-        maps, masks, opacities = self.GetMapsMasksAndOpacities(force, mapWindow, windres)
+        maps, masks, opacities = self.GetMapsMasksAndOpacities(force, windres)
         
         
         # ugly hack for MSYS
         # ugly hack for MSYS
         if sys.platform != 'win32':
         if sys.platform != 'win32':
@@ -1038,7 +1040,7 @@ class Map(object):
             opacity = 0
             opacity = 0
         elif opacity > 1:
         elif opacity > 1:
             opacity = 1
             opacity = 1
-        layer = MapLayer(ltype = ltype, name = name, cmd = command, parent = self,
+        layer = MapLayer(ltype = ltype, name = name, cmd = command, Map = self,
                          active = active, hidden = hidden, opacity = opacity)
                          active = active, hidden = hidden, opacity = opacity)
         
         
         # add maplayer to the list of layers
         # add maplayer to the list of layers
@@ -1189,7 +1191,7 @@ class Map(object):
         Layer is defined by name@mapset or id.
         Layer is defined by name@mapset or id.
         
         
         @param name layer name (must be unique)
         @param name layer name (must be unique)
-        @param id layer index in layer list
+        @param id layer index in layer list    def __init__(self, targetFile, region, bandsNum, gdalDriver, fillValue = None):
 
 
         @return removed layer on success
         @return removed layer on success
         @return None on failure
         @return None on failure
@@ -1245,7 +1247,7 @@ class Map(object):
         @return None on failure
         @return None on failure
         """
         """
         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, render))
         Debug.msg (2, "Map.AddOverlay(): cmd=%s, render=%d" % (command, render))
-        overlay = Overlay(id = id, ltype = ltype, cmd = command, parent = self,
+        overlay = Overlay(id = id, ltype = ltype, cmd = command, Map = self,
                           active = active, hidden = hidden, opacity = opacity)
                           active = active, hidden = hidden, opacity = opacity)
         
         
         # add maplayer to the list of layers
         # add maplayer to the list of layers
@@ -1356,14 +1358,22 @@ class Map(object):
             if force or layer.forceRender:
             if force or layer.forceRender:
                 layer.Render()
                 layer.Render()
 
 
-    def SetParentMapWindow(self, mapWin):
-        """!Set reference to parent MapWindow"""
-        self.mapWin = mapWin
+    def GetReceiver(self):
+        """!Get event receiver"""
+        return self.receiver
 
 
-    def GetParentMapWindow(self):
-        """!Get reference to parent MapWindow"""
-        return self.mapWin
+    def SetReceiver(self, receiver):
+        """!Set events receiver
 
 
-    def IsDownloading(self):
-        """!Is any layer downloading data from web server e. g. wms"""
-        return self.downloading
+        @todo  If it will be needed to change receiver, take care of running threads.
+        """
+        self.receiver = receiver
+        for l in self.overlays + self.layers:
+            if l.GetRenderMgr():
+                l.GetRenderMgr().SetReceiver(self.receiver)
+
+    def AbortAllThreads(self, old_receiver = None):
+        """!Abort all layers threads e. g. donwloading data"""
+        for l in self.layers + self.overlays:
+            l.AbortThread(old_receiver)
+ 

+ 35 - 0
gui/wxpython/core/utils.py

@@ -19,6 +19,7 @@ import string
 import glob
 import glob
 import shlex
 import shlex
 import re
 import re
+import inspect
 
 
 from core.globalvar import ETCDIR
 from core.globalvar import ETCDIR
 sys.path.append(os.path.join(ETCDIR, "python"))
 sys.path.append(os.path.join(ETCDIR, "python"))
@@ -907,3 +908,37 @@ def color_resolve(color):
             rgb = (200, 200, 200)
             rgb = (200, 200, 200)
             label = _('Select Color')
             label = _('Select Color')
     return (rgb, label)
     return (rgb, label)
+
+def GetGEventAttribsForHandler(method, event):
+    """!Get attributes from event, which can be used by handler method. 
+
+    Be aware of event class attributes.
+
+    @param method - handler method (including self arg)
+    @param event - event
+
+    @return (valid kwargs for method, 
+             list of method's args without default value 
+             which were not found among event attributes)
+    """
+    args_spec = inspect.getargspec(method)
+
+    args = args_spec[0]
+
+    defaults =[]
+    if args_spec[3]:
+        defaults =  args_spec[3]
+
+    # number of arguments without def value
+    req_args = len(args) - 1 - len(defaults)
+
+    kwargs = {}
+    missing_args = []
+
+    for i, a in enumerate(args):
+        if hasattr(event, a):
+            kwargs[a] = getattr(event, a)
+        elif i < req_args:
+            missing_args.append(a)
+
+    return kwargs, missing_args

+ 60 - 49
gui/wxpython/core/ws.py

@@ -7,7 +7,6 @@ Note: Currently only WMS is implemented.
 
 
 Classes:
 Classes:
  - ws::RenderWMSMgr
  - ws::RenderWMSMgr
- - ws::StdErr
  - ws::GDALRasterMerger
  - ws::GDALRasterMerger
 
 
 (C) 2012 by the GRASS Development Team
 (C) 2012 by the GRASS Development Team
@@ -25,9 +24,11 @@ import wx
 from grass.script import core as grass
 from grass.script import core as grass
 
 
 from core          import utils
 from core          import utils
+from core.events   import gUpdateMap
+from core.render   import wxUpdateProgressBar
 from core.debug    import Debug
 from core.debug    import Debug
 
 
-from core.gconsole import CmdThread, EVT_CMD_DONE
+from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
 from core.gcmd     import GException
 from core.gcmd     import GException
 
 
 try:
 try:
@@ -39,31 +40,35 @@ except ImportError:
 
 
 class RenderWMSMgr(wx.EvtHandler):
 class RenderWMSMgr(wx.EvtHandler):
     """!Fetch and prepare WMS data for rendering.
     """!Fetch and prepare WMS data for rendering.
-
-    @todo statusbar: updtade by event or call method ???
     """
     """
-    def __init__(self, parent, mapfile, maskfile):
+    def __init__(self, receiver, layer, Map, mapfile, maskfile):
         if not haveGdal:
         if not haveGdal:
             sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\
             sys.stderr.write(_("Unable to load GDAL Python bindings.\n"\
                                "WMS layers can not be displayed without the bindings.\n"))
                                "WMS layers can not be displayed without the bindings.\n"))
-        
-        self.parent = parent
+    
+        self.Map = Map
+        self.receiver = receiver
+        self.layer = layer
+
+        wx.EvtHandler.__init__(self)
 
 
         # thread for d.wms commands
         # thread for d.wms commands
         self.thread = CmdThread(self)
         self.thread = CmdThread(self)
-        wx.EvtHandler.__init__(self)
         self.Bind(EVT_CMD_DONE, self.OnDataFetched)
         self.Bind(EVT_CMD_DONE, self.OnDataFetched)
 
 
         self.downloading = False
         self.downloading = False
         self.renderedRegion = None
         self.renderedRegion = None
         self.updateMap = True
         self.updateMap = True
+        self.fetched_data_cmd = None
 
 
-        self.cmdStdErr = StdErr()
+        self.cmdStdErr = GStderr(self)
 
 
         self.mapfile = mapfile
         self.mapfile = mapfile
         self.maskfile = maskfile
         self.maskfile = maskfile
         self.tempMap = grass.tempfile()
         self.tempMap = grass.tempfile()
         self.dstSize = {}
         self.dstSize = {}
+ 
+        self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
 
 
     def __del__(self):
     def __del__(self):
         grass.try_remove(self.tempMap)
         grass.try_remove(self.tempMap)
@@ -87,7 +92,8 @@ class RenderWMSMgr(wx.EvtHandler):
         fetchData = False
         fetchData = False
         zoomChanged = False
         zoomChanged = False
 
 
-        if self.renderedRegion is None:
+        if self.renderedRegion is None or \
+           cmd != self.fetched_data_cmd:
             fetchData = True
             fetchData = True
         else:
         else:
             for c in ['north', 'south', 'east', 'west']:
             for c in ['north', 'south', 'east', 'west']:
@@ -103,6 +109,7 @@ class RenderWMSMgr(wx.EvtHandler):
                     break
                     break
 
 
         if fetchData:
         if fetchData:
+            self.fetched_data_cmd = None
             self.renderedRegion = region
             self.renderedRegion = region
 
 
             grass.try_remove(self.mapfile)
             grass.try_remove(self.mapfile)
@@ -112,12 +119,15 @@ class RenderWMSMgr(wx.EvtHandler):
             self.thread.abort()
             self.thread.abort()
             self.downloading = True
             self.downloading = True
 
 
+            self.fetching_cmd = cmd
             cmdList = utils.CmdTupleToList(cmd)
             cmdList = utils.CmdTupleToList(cmd)
 
 
             if Debug.GetLevel() < 3:
             if Debug.GetLevel() < 3:
                 cmdList.append('--quiet')
                 cmdList.append('--quiet')
-                
-            tempPngfile = os.environ["GRASS_PNGFILE"]
+            
+            tempPngfile = None
+            if "GRASS_PNGFILE" in os.environ:  
+                tempPngfile = os.environ["GRASS_PNGFILE"]
             os.environ["GRASS_PNGFILE"] = self.tempMap
             os.environ["GRASS_PNGFILE"] = self.tempMap
 
 
             tempRegion = os.environ["GRASS_REGION"]
             tempRegion = os.environ["GRASS_REGION"]
@@ -125,22 +135,34 @@ class RenderWMSMgr(wx.EvtHandler):
 
 
             self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
             self.thread.RunCmd(cmdList, env = os.environ.copy(), stderr = self.cmdStdErr)
 
 
-            os.environ["GRASS_PNGFILE"] = tempPngfile
+            os.environ.pop("GRASS_PNGFILE")
+            if tempPngfile:
+                os.environ["GRASS_PNGFILE"] = tempPngfile
+
             os.environ["GRASS_REGION"] = tempRegion
             os.environ["GRASS_REGION"] = tempRegion
 
 
+    def OnCmdOutput(self, event):
+        """!Print cmd output according to debug level.
+        """
+        if Debug.GetLevel() == 0:
+            if event.type == 'error':
+                sys.stderr.write(event.text)
+                sys.stderr.flush()
+        else:
+            Debug.msg(1, event.text)
+
     def OnDataFetched(self, event):
     def OnDataFetched(self, event):
         """!Fetch data
         """!Fetch data
-
-        @todo ?
-        @todo needs refactoring -  self.parent.parent.mapWin.UpdateMap
         """
         """
         if event.pid != self.currentPid:
         if event.pid != self.currentPid:
             return
             return
         self.downloading = False
         self.downloading = False
         if not self.updateMap:
         if not self.updateMap:
-            # TODO
-            self.parent.parent.GetParentMapWindow().frame.GetProgressBar().UpdateProgress(self.parent, self.parent.parent)
+            if self.receiver:
+                event = wxUpdateProgressBar(layer = self.layer, map = self.Map)
+                self.receiver.GetEventHandler().ProcessEvent(event) 
             self.renderedRegion = None
             self.renderedRegion = None
+            self.fetched_data_cmd = None
             return
             return
 
 
         self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
         self.mapMerger = GDALRasterMerger(targetFile = self.mapfile, region = self.renderedRegion,
@@ -154,7 +176,11 @@ class RenderWMSMgr(wx.EvtHandler):
         self.maskMerger.AddRasterBands(self.tempMap, {4 : 1}) 
         self.maskMerger.AddRasterBands(self.tempMap, {4 : 1}) 
         del self.maskMerger
         del self.maskMerger
 
 
-        self.parent.parent.GetParentMapWindow().UpdateMap(render = True)
+        self.fetched_data_cmd = self.fetching_cmd
+
+        if self.receiver:
+            event = gUpdateMap()
+            wx.PostEvent(self.receiver, event)
 
 
     def _getRegionDict(self):
     def _getRegionDict(self):
         """!Parse string from GRASS_REGION env variable into dict.
         """!Parse string from GRASS_REGION env variable into dict.
@@ -219,36 +245,12 @@ class RenderWMSMgr(wx.EvtHandler):
         self.updateMap = False
         self.updateMap = False
         self.thread.abort(abortall = True)        
         self.thread.abort(abortall = True)        
 
 
-class StdErr:
-    """!Redirect error output according to debug mode.
+    def SetReceiver(self, receiver):
+        """!Set events receiver
 
 
-    @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)
+        @todo  If it will be needed to change receiver, take care of running threads.
+        """        
+        self.receiver = receiver
 
 
 class GDALRasterMerger:
 class GDALRasterMerger:
     """!Merge rasters.
     """!Merge rasters.
@@ -341,7 +343,16 @@ class GDALRasterMerger:
         lrx = geoTrans[0] + size['cols'] * geoTrans[1]
         lrx = geoTrans[0] + size['cols'] * geoTrans[1]
         lry = geoTrans[3] + size['rows'] * geoTrans[5]
         lry = geoTrans[3] + size['rows'] * geoTrans[5]
 
 
-        return ulx, uly, lrx, lry
+        return ulx, uly, lrx, lry 
+
+    def SetGeorefAndProj(self):
+        """!Set georeference and projection to target file
+        """
+        projection = grass.read_command('g.proj', 
+                                        flags = 'wf')
+        self.tDataset.SetProjection(projection)
+
+        self.tDataset.SetGeoTransform(self.tGeotransform)
 
 
     def __del__(self):
     def __del__(self):
         self.tDataset = None
         self.tDataset = None

+ 0 - 2
gui/wxpython/gcp/mapdisplay.py

@@ -23,7 +23,6 @@ from core import globalvar
 import wx
 import wx
 import wx.aui
 import wx.aui
 
 
-from core.render       import EVT_UPDATE_PRGBAR
 from mapdisp.toolbars  import MapToolbar
 from mapdisp.toolbars  import MapToolbar
 from gcp.toolbars      import GCPDisplayToolbar, GCPManToolbar
 from gcp.toolbars      import GCPDisplayToolbar, GCPManToolbar
 from mapdisp.gprint    import PrintOptions
 from mapdisp.gprint    import PrintOptions
@@ -134,7 +133,6 @@ class MapFrame(SingleMapFrame):
         #
         #
         # Bind various events
         # Bind various events
         #
         #
-        self.Bind(EVT_UPDATE_PRGBAR, self.OnUpdateProgress)
         self.activemap.Bind(wx.EVT_CHOICE, self.OnUpdateActive)
         self.activemap.Bind(wx.EVT_CHOICE, self.OnUpdateActive)
         
         
         #
         #

+ 41 - 151
gui/wxpython/gui_core/gselect.py

@@ -54,6 +54,8 @@ import grass.script as grass
 import grass.temporal as tgis
 import grass.temporal as tgis
 from   grass.script import task as gtask
 from   grass.script import task as gtask
 
 
+from gui_core.widgets  import ManageSettingsWidget, EVT_SETTINGS_CHANGED, EVT_SETTINGS_SAVING
+
 from core.gcmd     import RunCommand, GError, GMessage
 from core.gcmd     import RunCommand, GError, GMessage
 from core.utils    import GetListOfLocations, GetListOfMapsets, GetFormats
 from core.utils    import GetListOfLocations, GetListOfMapsets, GetFormats
 from core.utils    import GetSettingsPath, GetValidLayerName, ListSortLower
 from core.utils    import GetSettingsPath, GetValidLayerName, ListSortLower
@@ -1162,9 +1164,17 @@ class GdalSelect(wx.Panel):
         self.dest   = dest 
         self.dest   = dest 
         wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
         wx.Panel.__init__(self, parent = panel, id = wx.ID_ANY)
 
 
-        self.settingsBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                        label = " %s " % _("Settings"))
+        if self.ogr:
+            settingsFile = os.path.join(GetSettingsPath(), 'wxOGR')
+        else:
+            settingsFile = os.path.join(GetSettingsPath(), 'wxGDAL')
         
         
+        self.settsManager = ManageSettingsWidget(parent = self, 
+                                                 id = wx.ID_ANY,
+                                                 settingsFile = settingsFile)
+        self.settsManager.Bind(EVT_SETTINGS_CHANGED, self.OnSettingsChanged)
+        self.settsManager.Bind(EVT_SETTINGS_SAVING, self.OnSettingsSaving)
+
         self.inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY)
         self.inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY)
         if dest:
         if dest:
             self.inputBox.SetLabel(" %s " % _("Output settings"))
             self.inputBox.SetLabel(" %s " % _("Output settings"))
@@ -1208,21 +1218,6 @@ class GdalSelect(wx.Panel):
             sources.append(_("PostGIS (PG)"))
             sources.append(_("PostGIS (PG)"))
             self.sourceMap['db-pg'] = idx
             self.sourceMap['db-pg'] = idx
         
         
-        if self.ogr:
-            self.settingsFile = os.path.join(GetSettingsPath(), 'wxOGR')
-        else:
-            self.settingsFile = os.path.join(GetSettingsPath(), 'wxGDAL')
-
-        self.settingsChoice = wx.Choice(parent = self, id = wx.ID_ANY)
-        self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsLoad)
-        self._settings = self._loadSettings() # -> self.settingsChoice.SetItems()
-        self.btnSettingsSave = wx.Button(parent = self, id = wx.ID_SAVE)
-        self.btnSettingsSave.Bind(wx.EVT_BUTTON, self.OnSettingsSave)
-        self.btnSettingsSave.SetToolTipString(_("Save current settings"))
-        self.btnSettingsDel = wx.Button(parent = self, id = wx.ID_REMOVE)
-        self.btnSettingsDel.Bind(wx.EVT_BUTTON, self.OnSettingsDelete)
-        self.btnSettingsSave.SetToolTipString(_("Delete currently selected settings"))
-        
         self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
         self.source = wx.RadioBox(parent = self, id = wx.ID_ANY,
                                   style = wx.RA_SPECIFY_COLS,
                                   style = wx.RA_SPECIFY_COLS,
                                   choices = sources)
                                   choices = sources)
@@ -1360,23 +1355,7 @@ class GdalSelect(wx.Panel):
     def _layout(self):
     def _layout(self):
         """!Layout"""
         """!Layout"""
         mainSizer = wx.BoxSizer(wx.VERTICAL)
         mainSizer = wx.BoxSizer(wx.VERTICAL)
-        
-        settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
-        settingsSizer.Add(item = wx.StaticText(parent = self,
-                                               id = wx.ID_ANY,
-                                               label = _("Load settings:")),
-                          flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
-                          border  = 5)
-        settingsSizer.Add(item = self.settingsChoice,
-                          proportion = 1,
-                          flag = wx.EXPAND)
-        settingsSizer.Add(item = self.btnSettingsSave,
-                          flag = wx.LEFT | wx.RIGHT,
-                          border = 5)
-        settingsSizer.Add(item = self.btnSettingsDel,
-                          flag = wx.RIGHT,
-                          border = 5)
-        
+                
         inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
         inputSizer = wx.StaticBoxSizer(self.inputBox, wx.HORIZONTAL)
         
         
         self.dsnSizer = wx.GridBagSizer(vgap = 3, hgap = 3)
         self.dsnSizer = wx.GridBagSizer(vgap = 3, hgap = 3)
@@ -1417,7 +1396,7 @@ class GdalSelect(wx.Panel):
         inputSizer.Add(item=self.dsnSizer, proportion = 1,
         inputSizer.Add(item=self.dsnSizer, proportion = 1,
                        flag=wx.EXPAND | wx.BOTTOM, border = 10)
                        flag=wx.EXPAND | wx.BOTTOM, border = 10)
         
         
-        mainSizer.Add(item=settingsSizer, proportion=0,
+        mainSizer.Add(item=self.settsManager, proportion=0,
                       flag=wx.ALL | wx.EXPAND, border=5)
                       flag=wx.ALL | wx.EXPAND, border=5)
         mainSizer.Add(item=self.source, proportion=0,
         mainSizer.Add(item=self.source, proportion=0,
                       flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
                       flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
@@ -1427,133 +1406,44 @@ class GdalSelect(wx.Panel):
         self.SetSizer(mainSizer)
         self.SetSizer(mainSizer)
         mainSizer.Fit(self)
         mainSizer.Fit(self)
 
 
-    def _getExtPatternGlob(self, ext):
-        """!Get pattern for case-insensitive globing"""
-        pattern = '*.'
-        for c in ext:
-            pattern += '[%s%s]' % (c.lower(), c.upper())
-        return pattern
-    
-    def _getExtPattern(self, ext):
-        """!Get pattern for case-insensitive file mask"""
-        return '*.%s;*.%s' % (ext.lower(), ext.upper())
+    def OnSettingsChanged(self, event):
+        """!User changed setting"""
+        data = event.data
+
+        # data list: [type, dsn, format, options]
+        if len(data) == 3:
+            data.append('')
+        elif len < 3:
+            return        
 
 
-    def OnSettingsLoad(self, event):
-        """!Load named settings"""
-        name = event.GetString()
-        if name not in self._settings:
-            GError(parent = self,
-                   message = _("Settings <%s> not found") % name)
-            return
-        data = self._settings[name]
         self.OnSetType(event = None, sel = self.sourceMap[data[0]])
         self.OnSetType(event = None, sel = self.sourceMap[data[0]])
         self.OnSetFormat(event = None, format = data[2])
         self.OnSetFormat(event = None, format = data[2])
         self.OnSetDsn(event = None, path = data[1])
         self.OnSetDsn(event = None, path = data[1])
         self.creationOpt.SetValue(data[3])
         self.creationOpt.SetValue(data[3])
-        
-    def OnSettingsSave(self, event):
-        """!Save settings"""
-        dlg = wx.TextEntryDialog(parent = self,
-                                 message = _("Name:"),
-                                 caption = _("Save settings"))
-        if dlg.ShowModal() != wx.ID_OK:
-            return
-        
-        # check required params
-        if not dlg.GetValue():
-            GMessage(parent = self,
-                     message = _("Name not given, settings is not saved."))
-            return
-        
+
+    def OnSettingsSaving(self, event):
+        """!Saving data"""
         if not self.GetDsn():
         if not self.GetDsn():
             GMessage(parent = self,
             GMessage(parent = self,
-                     message = _("No data source defined, settings is not saved."))
+                     message = _("No data source defined, settings are not saved."))
             return
             return
-        
-        name = dlg.GetValue()
-        
-        # check if settings item already exists
-        if name in self._settings:
-            dlgOwt = wx.MessageDialog(self, message = _("Settings <%s> already exists. "
-                                                        "Do you want to overwrite the settings?") % name,
-                                      caption = _("Save settings"), style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
-            if dlgOwt.ShowModal() != wx.ID_YES:
-                dlgOwt.Destroy()
-                return
-        
-        self._settings[name] = (self.dsnType, self.GetDsn(),
-                                self.format.GetStringSelection(),
-                                self.creationOpt.GetValue())
-        
-        if self._saveSettings() == 0:
-            self._settings = self._loadSettings()
-            self.settingsChoice.SetStringSelection(name)
-        
-        dlg.Destroy()
- 
-    def OnSettingsDelete(self, event):
-        """!Save settings"""
-        name = self.settingsChoice.GetStringSelection()
-        if not name:
-            GMessage(parent = self,
-                     message = _("No settings is defined. Operation canceled."))
-            return
-        
-        self._settings.pop(name)
-        if self._saveSettings() == 0:
-            self._settings = self._loadSettings()
-        
-    def _saveSettings(self):
-        """!Save settings into the file
 
 
-        @return 0 on success
-        @return -1 on failure
-        """
-        try:
-            fd = open(self.settingsFile, 'w')
-            for key, value in self._settings.iteritems():
-                fd.write('%s;%s;%s;%s\n' % (key, value[0], value[1], value[2]))
-        except IOError:
-            GError(parent = self,
-                   message = _("Unable to save settings"))
-            return -1
-        else:
-            fd.close()
-        
-        return 0
+        self.settsManager.SetDataToSave((self.dsnType, self.GetDsn(),
+                                         self.format.GetStringSelection(),
+                                         self.creationOpt.GetValue()))
+        event.Skip()
 
 
-    def _loadSettings(self):
-        """!Load settings from the file
+    def _getExtPatternGlob(self, ext):
+        """!Get pattern for case-insensitive globing"""
+        pattern = '*.'
+        for c in ext:
+            pattern += '[%s%s]' % (c.lower(), c.upper())
+        return pattern
+    
+    def _getExtPattern(self, ext):
+        """!Get pattern for case-insensitive file mask"""
+        return '*.%s;*.%s' % (ext.lower(), ext.upper())
 
 
-        The file is defined by self.SettingsFile.
-        
-        @return parsed dict
-        @return empty dict on error
-        """
-        data = dict()
-        if not os.path.exists(self.settingsFile):
-            return data
-        
-        try:
-            fd = open(self.settingsFile, 'r')
-            for line in fd.readlines():
-                try:
-                    lineData = line.rstrip('\n').split(';')
-                    if len(lineData) > 4:
-                        # type, dsn, format, options
-                        data[lineData[0]] = (lineData[1], lineData[2], lineData[3], lineData[4])
-                    else:
-                        data[lineData[0]] = (lineData[1], lineData[2], lineData[3], '')
-                except ValueError:
-                    pass
-        except IOError:
-            return data
-        else:
-            fd.close()
-        
-        self.settingsChoice.SetItems(sorted(data.keys()))
-        
-        return data
 
 
     def OnSetType(self, event, sel = None):
     def OnSetType(self, event, sel = None):
         """!Datasource type changed"""
         """!Datasource type changed"""

+ 1 - 1
gui/wxpython/gui_core/mapwindow.py

@@ -39,9 +39,9 @@ class MapWindow(object):
     def __init__(self, parent, giface, Map, frame, **kwargs):
     def __init__(self, parent, giface, Map, frame, **kwargs):
         self.parent = parent
         self.parent = parent
         self.Map = Map
         self.Map = Map
-        self.Map.SetParentMapWindow(self)
         self.frame = frame
         self.frame = frame
         self._giface = giface
         self._giface = giface
+        self.Map.SetReceiver(self)
         
         
         # mouse attributes -- position on the screen, begin and end of
         # mouse attributes -- position on the screen, begin and end of
         # dragging, and type of drawing
         # dragging, and type of drawing

+ 303 - 0
gui/wxpython/gui_core/widgets.py

@@ -16,6 +16,7 @@ Classes:
  - widgets::ItemTree
  - widgets::ItemTree
  - widgets::GListCtrl
  - widgets::GListCtrl
  - widgets::SearchModuleWidget
  - widgets::SearchModuleWidget
+ - widgets::ManageSettingsWidget
 
 
 (C) 2008-2012 by the GRASS Development Team
 (C) 2008-2012 by the GRASS Development Team
 
 
@@ -25,6 +26,7 @@ This program is free software under the GNU General Public License
 @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010)
 @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010)
 @author Enhancements by Michael Barton <michael.barton asu.edu>
 @author Enhancements by Michael Barton <michael.barton asu.edu>
 @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011)
 @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011)
+@author Stepan Turek <stepan.turek seznam.cz> (ManageSettingsWidget - created from GdalSelect)
 """
 """
 
 
 import os
 import os
@@ -48,12 +50,17 @@ except ImportError:
     import wx.lib.customtreectrl as CT
     import wx.lib.customtreectrl as CT
 
 
 from core        import globalvar
 from core        import globalvar
+from core.gcmd   import GMessage
 from core.debug  import Debug
 from core.debug  import Debug
 from core.events import gShowNotification
 from core.events import gShowNotification
 
 
 from wx.lib.newevent import NewEvent
 from wx.lib.newevent import NewEvent
 wxSymbolSelectionChanged, EVT_SYMBOL_SELECTION_CHANGED  = NewEvent()
 wxSymbolSelectionChanged, EVT_SYMBOL_SELECTION_CHANGED  = NewEvent()
 
 
+# ManageSettingsWidget
+wxOnSettingsLoaded, EVT_SETTINGS_LOADED = NewEvent()
+wxOnSettingsChanged, EVT_SETTINGS_CHANGED = NewEvent()
+wxOnSettingsSaving, EVT_SETTINGS_SAVING = NewEvent()
 
 
 class NotebookController:
 class NotebookController:
     """!Provides handling of notebook page names.
     """!Provides handling of notebook page names.
@@ -874,3 +881,299 @@ class SearchModuleWidget(wx.Panel):
         self.search.SetValue('')
         self.search.SetValue('')
         if self.showTip:
         if self.showTip:
             self.searchTip.SetLabel('')
             self.searchTip.SetLabel('')
+
+class ManageSettingsWidget(wx.Panel):
+    """!Widget which allows loading and saving settings into file."""
+    def __init__(self, parent, settingsFile, id = wx.ID_ANY):
+        """
+        Events:
+            EVT_SETTINGS_CHANGED - called when users changes setting
+                                - event object has attribute 'data', with chosen setting data
+            EVT_SETTINGS_SAVING - called when settings are saving
+                                - If you bind instance of ManageSettingsWidget with this event,
+                                  you can use SetDataToSave method to set data for save and then call 
+                                  Skip() to save the data.
+                                  If you do not call Skip(), the data will not be saved. 
+            EVT_SETTINGS_LOADED - called when settings are loaded
+                                - event object has attribute 'settings', which is dict with loaded settings
+                                  {nameofsetting : settingdata, ....}
+
+        @param settingsFile - path to file, where settings will be saved and loaded from
+        """
+        self.settingsFile = settingsFile
+
+        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
+
+        self.settingsBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                        label = " %s " % _("Settings"))
+        
+        self.settingsChoice = wx.Choice(parent = self, id = wx.ID_ANY)
+        self.settingsChoice.Bind(wx.EVT_CHOICE, self.OnSettingsChanged)
+        self.btnSettingsSave = wx.Button(parent = self, id = wx.ID_SAVE)
+        self.btnSettingsSave.Bind(wx.EVT_BUTTON, self.OnSettingsSave)
+        self.btnSettingsSave.SetToolTipString(_("Save current settings"))
+        self.btnSettingsDel = wx.Button(parent = self, id = wx.ID_REMOVE)
+        self.btnSettingsDel.Bind(wx.EVT_BUTTON, self.OnSettingsDelete)
+        self.btnSettingsSave.SetToolTipString(_("Delete currently selected settings"))
+
+        self.Bind(EVT_SETTINGS_SAVING, self.OnSettingsSaving)
+
+        # escaping with '$' character - index in self.esc_chars
+        self.e_char_i = 0
+        self.esc_chars = ['$', ';']
+
+        self._settings = self._loadSettings() # -> self.settingsChoice.SetItems()
+        event = wxOnSettingsLoaded(settings = self._settings)
+        wx.PostEvent(self, event)
+
+        self.data_to_save = []
+
+        self._layout()
+
+    def _layout(self):
+
+        settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
+        settingsSizer.Add(item = wx.StaticText(parent = self,
+                                               id = wx.ID_ANY,
+                                               label = _("Load settings:")),
+                          flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
+                          border  = 5)
+        settingsSizer.Add(item = self.settingsChoice,
+                          proportion = 1,
+                          flag = wx.EXPAND)
+        settingsSizer.Add(item = self.btnSettingsSave,
+                          flag = wx.LEFT | wx.RIGHT,
+                          border = 5)
+        settingsSizer.Add(item = self.btnSettingsDel,
+                          flag = wx.RIGHT,
+                          border = 5)
+
+        self.SetSizer(settingsSizer)
+        settingsSizer.Fit(self)
+
+    def OnSettingsChanged(self, event):
+        """!Load named settings"""
+        name = event.GetString()
+        if name not in self._settings:
+            GError(parent = self,
+                   message = _("Settings <%s> not found") % name)
+            return
+
+        data = self._settings[name]
+        event = wxOnSettingsChanged(data = data)
+        wx.PostEvent(self, event)
+
+    def OnSettingsSave(self, event):
+        """!Save settings"""
+        dlg = wx.TextEntryDialog(parent = self,
+                                 message = _("Name:"),
+                                 caption = _("Save settings"))
+        if dlg.ShowModal() != wx.ID_OK:
+            return
+        
+        # check required params
+        if not dlg.GetValue():
+            GMessage(parent = self,
+                     message = _("Name not given, settings is not saved."))
+            return
+
+        name = dlg.GetValue()
+
+        event = wxOnSettingsSaving(name = name, dlg = dlg)
+        wx.PostEvent(self, event)
+  
+    def OnSettingsSaving(self, event):
+        # check if settings item already exists
+        if event.name in self._settings:
+            dlgOwt = wx.MessageDialog(self, message = _("Settings <%s> already exists. "
+                                                        "Do you want to overwrite the settings?") % event.name,
+                                      caption = _("Save settings"), style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
+            if dlgOwt.ShowModal() != wx.ID_YES:
+                dlgOwt.Destroy()
+                return
+
+        if self.data_to_save:
+            self._settings[event.name] = self.data_to_save
+
+        self.SaveSettings()
+        self.settingsChoice.SetStringSelection(event.name)
+
+        self.data_to_save = []
+        event.dlg.Destroy()
+ 
+    def SaveSettings(self):
+        """!Save settings"""
+        if self._saveSettings() == 0:
+            self._settings = self._loadSettings()
+
+    def SetDataToSave(self, data):
+        """!Set data for setting, which will be saved.
+
+        @param data - list of strings, which will be saved
+        """
+        self.data_to_save = data
+
+    def SetSettings(self, settings):
+        """!Set settings
+
+        @param settings - dict with all settigs {nameofsetting : settingdata, ....}
+        """
+        self._settings = settings
+        self.SaveSettings()
+
+    def OnSettingsDelete(self, event):
+        """!Save settings
+        """
+        name = self.settingsChoice.GetStringSelection()
+        if not name:
+            GMessage(parent = self,
+                     message = _("No settings is defined. Operation canceled."))
+            return
+        
+        self._settings.pop(name)
+        if self._saveSettings() == 0:
+            self._settings = self._loadSettings()
+        
+    def _saveSettings(self):
+        """!Save settings into the file
+
+        @return 0 on success
+        @return -1 on failure
+        """
+        try:
+            fd = open(self.settingsFile, 'w')
+            fd.write('format_version=2.0\n')
+            for key, values in self._settings.iteritems():
+                first = True
+                for v in values:
+                    # escaping characters
+                    for e_ch in self.esc_chars:
+                        v = v.replace(e_ch, self.esc_chars[self.e_char_i] + e_ch)
+                    if first:
+                        # escaping characters
+                        for e_ch in self.esc_chars:
+                            key = key.replace(e_ch, self.esc_chars[self.e_char_i] + e_ch)
+                        fd.write('%s;%s;' % (key, v))
+                        first = False
+                    else:
+                        fd.write('%s;' % (v))
+                fd.write('\n')
+
+        except IOError:
+            GError(parent = self,
+                   message = _("Unable to save settings"))
+            return -1
+        fd.close()
+        
+        return 0
+
+    def _loadSettings(self):
+        """!Load settings from the file
+
+        The file is defined by self.SettingsFile.
+        
+        @return parsed dict
+        @return empty dict on error
+        """
+
+        data = dict()
+        if not os.path.exists(self.settingsFile):
+            return data
+
+        try:
+            fd = open(self.settingsFile, 'r')
+        except IOError:
+            return data
+
+        fd_lines = fd.readlines()
+
+        if not fd_lines:
+            fd.close()
+            return data
+
+        if fd_lines[0].strip() == 'format_version=2.0':
+            data = self._loadSettings_v2(fd_lines)
+        else:
+            data = self._loadSettings_v1(fd_lines)
+
+        self.settingsChoice.SetItems(sorted(data.keys()))
+        fd.close()
+
+        event = wxOnSettingsLoaded(settings = data)
+        wx.PostEvent(self, event)
+
+        return data
+
+    def _loadSettings_v2(self, fd_lines):
+        """Load settings from the file in format version 2.0
+
+        The file is defined by self.SettingsFile.
+        
+        @return parsed dict
+        @return empty dict on error
+        """
+        data = dict()
+        
+        for line in fd_lines[1:]:
+            try:
+                lineData = []
+                line = line.rstrip('\n')
+                i_last_found = i_last = 0
+                key = ''
+                while True:
+                    idx = line.find(';', i_last)
+                    if idx < 0:
+                        break
+                    elif idx != 0:
+
+                        # find out whether it is separator
+                        # $$$$; - it is separator
+                        # $$$$$; - it is not separator
+                        i_esc_chars = 0
+                        while True:
+                            if line[idx - (i_esc_chars + 1)] == self.esc_chars[self.e_char_i]:
+                                i_esc_chars += 1
+                            else: 
+                                break
+                        if i_esc_chars%2 != 0:
+                            i_last = idx + 1
+                            continue
+
+                    lineItem = line[i_last_found : idx]
+                    # unescape characters
+                    for e_ch in self.esc_chars:
+                        lineItem = lineItem.replace(self.esc_chars[self.e_char_i] + e_ch, e_ch)
+                    if i_last_found == 0:
+                        key = lineItem
+                    else:
+                        lineData.append(lineItem)
+                    i_last_found = i_last = idx + 1
+                if key and lineData:
+                    data[key] = lineData
+            except ValueError:
+                pass
+
+        return data
+
+    def _loadSettings_v1(self, fd_lines):
+        """!Load settings from the file in format version 1.0 (backward compatibility)
+
+        The file is defined by self.SettingsFile.
+        
+        @return parsed dict
+        @return empty dict on error
+        """
+        data = dict()
+      
+        for line in fd_lines:
+            try:
+                lineData = line.rstrip('\n').split(';')
+                if len(lineData) > 4:
+                    # type, dsn, format, options
+                    data[lineData[0]] = (lineData[1], lineData[2], lineData[3], lineData[4])
+                else:
+                    data[lineData[0]] = (lineData[1], lineData[2], lineData[3], '')
+            except ValueError:
+                pass
+        
+        return data

+ 7 - 33
gui/wxpython/lmgr/frame.py

@@ -992,9 +992,7 @@ class GMFrame(wx.Frame):
                          (None, None),
                          (None, None),
                          ('vectImport',    self.OnImportOgrLayers),
                          ('vectImport',    self.OnImportOgrLayers),
                          ('vectLink',      self.OnLinkOgrLayers),
                          ('vectLink',      self.OnLinkOgrLayers),
-                         ('vectOut',       self.OnVectorOutputFormat),
-                         (None, None),
-                         ('wmsImport',     self.OnImportWMS)))
+                         ('vectOut',       self.OnVectorOutputFormat)))
         
         
     def OnWorkspaceNew(self, event = None):
     def OnWorkspaceNew(self, event = None):
         """!Create new workspace file
         """!Create new workspace file
@@ -1509,36 +1507,12 @@ class GMFrame(wx.Frame):
         dlg.CentreOnScreen()
         dlg.CentreOnScreen()
         dlg.Show()
         dlg.Show()
         
         
-    def OnImportWMS(self, event, cmd = None):
-        """!Import data from OGC WMS server"""
-        from ogc_services.wms import WMSDialog
-        dlg = WMSDialog(parent = self)
-        dlg.CenterOnScreen()         
-        if dlg.ShowModal() == wx.ID_OK: # -> import layers
-            layers = dlg.GetLayers()
-            
-            if len(layers.keys()) > 0:
-                for layer in layers.keys():
-                    cmd = ['r.in.wms',
-                           'mapserver=%s' % dlg.GetSettings()['server'],
-                           'layers=%s' % layer,
-                           'output=%s' % layer,
-                           'format=png',
-                           '--overwrite']
-                    styles = ','.join(layers[layer])
-                    if styles:
-                        cmd.append('styles=%s' % styles)
-                    self._gconsole.RunCmd(cmd, switchPage = True)
-
-                    self.GetLayerTree().AddLayer(ltype = 'raster',
-                                                    lname = layer,
-                                                    lcmd = ['d.rast', 'map=%s' % layer],
-                                                    multiple = False)
-            else:
-                self._gconsole.WriteWarning(_("Nothing to import. No WMS layer selected."))
-                
-                
-        dlg.Destroy()
+    def OnAddWS(self, event, cmd = None):
+        """!Add web services layer"""
+        from web_services.dialogs import AddWSDialog
+        dlg = AddWSDialog(parent = self, gmframe = self)
+        dlg.CentreOnScreen()
+        dlg.Show()
 
 
     def OnShowAttributeTable(self, event, selection = None):
     def OnShowAttributeTable(self, event, selection = None):
         """!Show attribute table of the given vector map layer
         """!Show attribute table of the given vector map layer

+ 35 - 19
gui/wxpython/lmgr/layertree.py

@@ -29,21 +29,23 @@ except ImportError:
 
 
 from grass.script import core as grass
 from grass.script import core as grass
 
 
-from core                import globalvar
-from gui_core.dialogs    import SqlQueryFrame, SetOpacityDialog, EVT_APPLY_OPACITY
-from gui_core.forms      import GUI
-from mapdisp.frame       import MapFrame
-from core.render         import Map
-from modules.histogram   import HistogramFrame
-from core.utils          import GetLayerNameFromCmd
-from wxplot.profile      import ProfileFrame
-from core.debug          import Debug
-from core.settings       import UserSettings, GetDisplayVectSettings
-from vdigit.main         import haveVDigit
-from core.gcmd           import GWarning, GError
-from gui_core.toolbars   import BaseIcons
-from icons.icon          import MetaIcon
-from modules.colorrules  import RasterColorTable
+from core                 import globalvar
+from gui_core.dialogs     import SqlQueryFrame, SetOpacityDialog, EVT_APPLY_OPACITY
+from gui_core.forms       import GUI
+from mapdisp.frame        import MapFrame
+from core.render          import Map
+from modules.histogram    import HistogramFrame
+from core.utils           import GetLayerNameFromCmd
+from wxplot.profile       import ProfileFrame
+from core.debug           import Debug
+from core.settings        import UserSettings, GetDisplayVectSettings
+from vdigit.main          import haveVDigit
+from core.gcmd            import GWarning, GError
+from gui_core.toolbars    import BaseIcons
+from icons.icon           import MetaIcon
+from modules.colorrules   import RasterColorTable
+from web_services.dialogs import SaveWMSLayerDialog
+
 
 
 TREE_ITEM_HEIGHT = 25
 TREE_ITEM_HEIGHT = 25
 
 
@@ -391,7 +393,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         if not hasattr (self, "popupID"):
         if not hasattr (self, "popupID"):
             self.popupID = dict()
             self.popupID = dict()
             for key in ('remove', 'rename', 'opacity', 'nviz', 'zoom',
             for key in ('remove', 'rename', 'opacity', 'nviz', 'zoom',
-                        'region', 'export', 'attr', 'edit0', 'edit1',
+                        'region', 'export', 'attr', 'edit0', 'edit1', 'save_ws',
                         'bgmap', 'topo', 'meta', 'null', 'zoom1', 'region1',
                         'bgmap', 'topo', 'meta', 'null', 'zoom1', 'region1',
                         'color', 'hist', 'univar', 'prof', 'properties', 'sql'):
                         'color', 'hist', 'univar', 'prof', 'properties', 'sql'):
                 self.popupID[key] = wx.NewId()
                 self.popupID[key] = wx.NewId()
@@ -532,9 +534,21 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
                 self.popupMenu.Append(self.popupID['meta'], _("Metadata"))
                 self.popupMenu.Append(self.popupID['meta'], _("Metadata"))
                 self.Bind (wx.EVT_MENU, self.OnMetadata, id = self.popupID['meta'])
                 self.Bind (wx.EVT_MENU, self.OnMetadata, id = self.popupID['meta'])
         
         
+        # web service layers (specific item)
+        elif mltype and mltype == "wms":
+            self.popupMenu.Append(self.popupID['save_ws'], text = _("Save web service layer"))
+            self.Bind(wx.EVT_MENU, self.OnSaveWs, id = self.popupID['save_ws'])
+
         self.PopupMenu(self.popupMenu)
         self.PopupMenu(self.popupMenu)
         self.popupMenu.Destroy()
         self.popupMenu.Destroy()
 
 
+    def OnSaveWs(self, event):
+        """!Show dialog for saving web service layer into GRASS vector/raster layer"""
+        mapLayer = self.GetLayerInfo(self.layer_selected, key = 'maplayer')
+        dlg = SaveWMSLayerDialog(parent = self, layer = mapLayer, ltree = self)
+        dlg.CentreOnScreen()
+        dlg.Show()
+
     def OnTopology(self, event):
     def OnTopology(self, event):
         """!Rebuild topology of selected vector map"""
         """!Rebuild topology of selected vector map"""
         mapLayer = self.GetLayerInfo(self.layer_selected, key = 'maplayer')
         mapLayer = self.GetLayerInfo(self.layer_selected, key = 'maplayer')
@@ -1035,12 +1049,14 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
 
 
     def PropertiesDialog(self, layer, show = True):
     def PropertiesDialog(self, layer, show = True):
         """!Launch the properties dialog"""
         """!Launch the properties dialog"""
+        ltype  = self.GetLayerInfo(layer, key = 'type')
         if 'propwin' in self.GetLayerInfo(layer) and \
         if 'propwin' in self.GetLayerInfo(layer) and \
                 self.GetLayerInfo(layer, key = 'propwin') is not None:
                 self.GetLayerInfo(layer, key = 'propwin') is not None:
             # recycle GUI dialogs
             # recycle GUI dialogs
             win = self.GetLayerInfo(layer, key = 'propwin')
             win = self.GetLayerInfo(layer, key = 'propwin')
-            # update properties (columns, layers)
-            win.notebookpanel.OnUpdateSelection(None)
+            if ltype != 'wms':
+                # update properties (columns, layers)
+                win.notebookpanel.OnUpdateSelection(None)
             if win.IsShown():
             if win.IsShown():
                 win.SetFocus()
                 win.SetFocus()
             else:
             else:
@@ -1049,13 +1065,13 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
             return
             return
         
         
         params = self.GetLayerParams(layer)
         params = self.GetLayerParams(layer)
-        ltype  = self.GetLayerInfo(layer, key = 'type')
                 
                 
         Debug.msg (3, "LayerTree.PropertiesDialog(): ltype=%s" % \
         Debug.msg (3, "LayerTree.PropertiesDialog(): ltype=%s" % \
                    ltype)
                    ltype)
 
 
         cmd = None
         cmd = None
         if self.GetLayerInfo(layer, key = 'cmd'):
         if self.GetLayerInfo(layer, key = 'cmd'):
+
             module = GUI(parent = self, show = show, centreOnParent = False)
             module = GUI(parent = self, show = show, centreOnParent = False)
             module.ParseCommand(self.GetLayerInfo(layer, key = 'cmd'),
             module.ParseCommand(self.GetLayerInfo(layer, key = 'cmd'),
                                 completed = (self.GetOptData,layer,params))
                                 completed = (self.GetOptData,layer,params))

+ 5 - 0
gui/wxpython/lmgr/toolbars.py

@@ -85,6 +85,8 @@ class LMDataToolbar(BaseToolbar):
             'addVect'    : BaseIcons['addVect'].SetLabel(_("Add vector map layer (Ctrl+Shift+V)")),
             'addVect'    : BaseIcons['addVect'].SetLabel(_("Add vector map layer (Ctrl+Shift+V)")),
             'vectMisc'   : MetaIcon(img = 'layer-vector-more',
             'vectMisc'   : MetaIcon(img = 'layer-vector-more',
                                     label = _('Add various vector map layers (thematic, chart...)')),
                                     label = _('Add various vector map layers (thematic, chart...)')),
+            'addWS'       : MetaIcon(img = 'layer-wms-add',
+                                     label = _('Add web service layer (WMS, WMTS, NASA OnEarth)')),
             'addGroup'   : MetaIcon(img = 'layer-group-add',
             'addGroup'   : MetaIcon(img = 'layer-group-add',
                                     label = _('Add group')),
                                     label = _('Add group')),
             'addOverlay' : MetaIcon(img = 'layer-more',
             'addOverlay' : MetaIcon(img = 'layer-more',
@@ -107,6 +109,9 @@ class LMDataToolbar(BaseToolbar):
                                       self.parent.OnAddGroup),
                                       self.parent.OnAddGroup),
                                      ('addovl',  icons["addOverlay"],
                                      ('addovl',  icons["addOverlay"],
                                       self.parent.OnAddOverlay),
                                       self.parent.OnAddOverlay),
+                                     ('addWS',  icons["addWS"],
+                                      self.parent.OnAddWS),
+                                     (None, ),
                                      ('delcmd',  icons["delCmd"],
                                      ('delcmd',  icons["delCmd"],
                                       self.parent.OnDeleteLayer),
                                       self.parent.OnDeleteLayer),
                                      ))
                                      ))

+ 31 - 9
gui/wxpython/mapdisp/mapwindow.py

@@ -34,8 +34,11 @@ from gui_core.dialogs   import SavedRegion
 from core.gcmd          import RunCommand, GException, GError, GMessage
 from core.gcmd          import RunCommand, GException, GError, GMessage
 from core.debug         import Debug
 from core.debug         import Debug
 from core.settings      import UserSettings
 from core.settings      import UserSettings
-from core.events        import gZoomChanged
+from core.events        import gZoomChanged, EVT_UPDATE_MAP
 from gui_core.mapwindow import MapWindow
 from gui_core.mapwindow import MapWindow
+from core.render        import EVT_UPDATE_PRGBAR
+from core.utils         import GetGEventAttribsForHandler
+
 try:
 try:
     import grass.lib.gis as gislib
     import grass.lib.gis as gislib
     haveCtypes = True
     haveCtypes = True
@@ -76,9 +79,13 @@ class BufferedWindow(MapWindow, wx.Window):
         self.plineid = None
         self.plineid = None
         
         
         # event bindings
         # event bindings
-        self.Bind(wx.EVT_PAINT,        self.OnPaint)
-        self.Bind(wx.EVT_SIZE,         self.OnSize)
-        self.Bind(wx.EVT_IDLE,         self.OnIdle)
+        self.Bind(wx.EVT_PAINT,           self.OnPaint)
+        self.Bind(wx.EVT_SIZE,            self.OnSize)
+        self.Bind(wx.EVT_IDLE,            self.OnIdle)
+        self.Bind(EVT_UPDATE_MAP,         self.OnUpdateMap)
+        if self.frame and hasattr(self.frame, 'OnUpdateProgress'):
+            self.Bind(EVT_UPDATE_PRGBAR,   self.frame.OnUpdateProgress)
+
         self._bindMouseEvents()
         self._bindMouseEvents()
         
         
         self.processMouse = True
         self.processMouse = True
@@ -571,10 +578,26 @@ class BufferedWindow(MapWindow, wx.Window):
     def IsAlwaysRenderEnabled(self):
     def IsAlwaysRenderEnabled(self):
         return self.alwaysRender
         return self.alwaysRender
 
 
+    def OnUpdateMap(self, event):
+        """!Called when this class receives core.events.gUpdateMap event. 
+        """
+        kwargs, missing_args = GetGEventAttribsForHandler(self.UpdateMap, event)
+
+        if missing_args:
+            Debug.msg (1, "Invalid call of EVT_UPDATE_MAP event.")
+            return
+
+        self.UpdateMap(self, **kwargs)
+
     def UpdateMap(self, render = True, renderVector = True):
     def UpdateMap(self, render = True, renderVector = True):
         """!Updates the canvas anytime there is a change to the
         """!Updates the canvas anytime there is a change to the
         underlaying images or to the geometry of the canvas.
         underlaying images or to the geometry of the canvas.
         
         
+        This method should not be called directly.
+        Post core.events.gUpdateMap event to instance of this class. 
+
+        @todo change direct calling of UpdateMap method to posting core.events.gUpdateMap
+
         @param render re-render map composition
         @param render re-render map composition
         @param renderVector re-render vector map layer enabled for editing (used for digitizer)
         @param renderVector re-render vector map layer enabled for editing (used for digitizer)
         """
         """
@@ -606,10 +629,10 @@ class BufferedWindow(MapWindow, wx.Window):
                 else:
                 else:
                     windres = False
                     windres = False
                 
                 
-                self.mapfile = self.Map.Render(force = True, mapWindow = self.frame,
+                self.mapfile = self.Map.Render(force = True,
                                                windres = windres)
                                                windres = windres)
             else:
             else:
-                self.mapfile = self.Map.Render(force = False, mapWindow = self.frame)
+                self.mapfile = self.Map.Render(force = False)
             
             
         except GException, e:
         except GException, e:
             GError(message = e.value)
             GError(message = e.value)
@@ -759,9 +782,8 @@ class BufferedWindow(MapWindow, wx.Window):
         self.Draw(self.pdcDec, pdctype = 'clear')
         self.Draw(self.pdcDec, pdctype = 'clear')
         self.Draw(self.pdcTmp, pdctype = 'clear')
         self.Draw(self.pdcTmp, pdctype = 'clear')
 
 
-        for layer in self.Map.GetListOfLayers(active = True):
-            layer.AbortDownload()
-        
+        self.Map.AbortAllThreads()
+
     def DragMap(self, moveto):
     def DragMap(self, moveto):
         """!Drag the entire map image for panning.
         """!Drag the entire map image for panning.
         
         

+ 0 - 300
gui/wxpython/ogc_services/wms.py

@@ -1,300 +0,0 @@
-"""!
-@package module.ogc_services
-
-@brief Dialogs for OGC services
-
-Currently only implemeted WMS.
-
-List of classes:
- - ogc_services::WMSDialog
- - ogc_services::LayersList
-
-(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.
-
-@author Martin Landa <landa.martin gmail.com>
-"""
-
-import wx
-from wx.gizmos import TreeListCtrl
-import wx.lib.mixins.listctrl as listmix
-
-from core.gcmd     import RunCommand
-from core.settings import UserSettings
-
-class WMSDialog(wx.Dialog):
-    def __init__(self, parent, service = 'wms',
-                 id=wx.ID_ANY,
-                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
-        """!Dialog to import data from WMS server"""
-        self.parent  = parent  # GMFrame 
-        self.service = service # currently only WMS is implemented
-        
-        wx.Dialog.__init__(self, parent, id, style=style)
-        if self.service == 'wms':
-            self.SetTitle(_("Import data from WMS server"))
-            
-        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
-        
-        self.__createWidgets()
-        
-        self.__doLayout()
-
-        self.SetMinSize((550, 400))
-        
-    def __createWidgets(self):
-        """!Create dialog widgets"""
-        #
-        # settings
-        #
-        self.settingsBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
-                                        label = _(" Server settings "))
-
-        self.serverText = wx.StaticText(parent = self.panel, id = wx.ID_ANY, label = _("Server:"))
-        self.server  = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
-        
-        #
-        # list of layers
-        #
-        self.layersBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
-                                label=_(" List of layers "))
-
-        self.list = LayersList(self.panel)
-        self.list.LoadData()
-
-        self.add = wx.CheckBox(parent=self.panel, id=wx.ID_ANY,
-                               label=_("Add imported layers into layer tree"))
-        self.add.SetValue(UserSettings.Get(group='cmd', key='addNewLayer', subkey='enabled'))
-                
-        #
-        # buttons
-        #
-        # cancel
-        self.btn_cancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
-        self.btn_cancel.SetToolTipString(_("Close dialog"))
-        # connect
-        self.btn_connect = wx.Button(parent = self.panel, id = wx.ID_ANY, label = _("&Connect"))
-        self.btn_connect.SetToolTipString(_("Connect to the server"))
-        self.btn_connect.SetDefault()
-        if not self.server.GetValue():
-            self.btn_connect.Enable(False)
-        # import
-        self.btn_import = wx.Button(parent = self.panel, id = wx.ID_OK, label = _("&Import"))
-        self.btn_import.SetToolTipString(_("Import selected layers"))
-        self.btn_import.Enable(False)
-        
-        #
-        # bindings
-        #
-        self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
-        self.btn_connect.Bind(wx.EVT_BUTTON, self.OnConnect)
-        self.server.Bind(wx.EVT_TEXT, self.OnServer)
-        
-    def __doLayout(self):
-        """!Do dialog layout"""
-        dialogSizer = wx.BoxSizer(wx.VERTICAL)
-        
-        #
-        # settings
-        #
-        settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.HORIZONTAL)
-        
-        gridSizer = wx.FlexGridSizer(cols=2, vgap=5, hgap=5)
-
-        gridSizer.Add(item=self.serverText,
-                      flag=wx.ALIGN_CENTER_VERTICAL)
-        gridSizer.AddGrowableCol(1)
-        gridSizer.Add(item=self.server,
-                      flag=wx.EXPAND | wx.ALL)
-        
-        settingsSizer.Add(item=gridSizer, proportion=1,
-                       flag=wx.EXPAND | wx.ALL)
-        
-        dialogSizer.Add(item=settingsSizer, proportion=0,
-                        flag=wx.ALL | wx.EXPAND, border=5)
-        
-        #
-        # list of layers
-        #
-        layersSizer = wx.StaticBoxSizer(self.layersBox, wx.HORIZONTAL)
-
-        layersSizer.Add(item=self.list, proportion=1,
-                        flag=wx.ALL | wx.EXPAND, border=5)
-        
-        dialogSizer.Add(item=layersSizer, proportion=1,
-                        flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5)
-
-        dialogSizer.Add(item=self.add, proportion=0,
-                        flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5)
-        
-        #
-        # buttons
-        #
-        btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
-
-        btnsizer.Add(item=self.btn_cancel, proportion=0,
-                     flag=wx.ALL | wx.ALIGN_CENTER,
-                     border=10)
-        
-        btnsizer.Add(item=self.btn_connect, proportion=0,
-                     flag=wx.ALL | wx.ALIGN_CENTER,
-                     border=10)
-        
-        btnsizer.Add(item=self.btn_import, proportion=0,
-                     flag=wx.ALL | wx.ALIGN_CENTER,
-                     border=10)
-        
-        dialogSizer.Add(item=btnsizer, proportion=0,
-                        flag=wx.ALIGN_CENTER)
-        
-        self.panel.SetAutoLayout(True)
-        self.panel.SetSizer(dialogSizer)
-        dialogSizer.Fit(self.panel)
-        self.Layout()
-
-    def OnCancel(self, event):
-        """!Button 'Cancel' pressed -> close the dialog"""
-        self.Close()
-
-    def OnConnect(self, event):
-        """!Button 'Connect' pressed"""
-        server = self.server.GetValue()
-        if not server:
-            self.btn_import.Enable(False)
-            return # not reachable
-
-        layers = {}
-        ret = RunCommand('r.in.wms',
-                         quiet = True,
-                         parent = self,
-                         read = True,
-                         flags = 'l',
-                         mapserver = server)
-        
-        if not ret:
-            self.list.LoadData()
-            self.btn_import.Enable(False)
-            return # no layers found
-        
-        lastLayer = lastStyle = ''
-        for line in ret.splitlines():
-            try:
-                key, value = line.split(':', 1)
-            except ValueError:
-                continue
-            key = key.strip().lower()
-            value = value.strip()
-            
-            if key == 'layer':
-                layers[value] = {}
-                lastLayer = value
-            elif key == 'title':
-                layers[lastLayer][key] = value
-            elif key == 'style':
-                if 'style' not in layers[lastLayer]:
-                    layers[lastLayer]['style'] = {}
-                layers[lastLayer]['style'][value] = ''
-                lastStyle = value
-            elif key == 'style title':
-                layers[lastLayer]['style'][lastStyle] = value
-        
-        # update list of layers
-        self.list.LoadData(layers)
-        
-        if len(layers.keys()) > 0:
-            self.btn_import.Enable(True)
-        else:
-            self.btn_import.Enable(False)
-        
-    def OnServer(self, event):
-        """!Server settings changed"""
-        value = event.GetString()
-        if value:
-            self.btn_connect.Enable(True)
-        else:
-            self.btn_connect.Enable(False)
-        
-    def GetLayers(self):
-        """!Get list of selected layers/styles to be imported"""
-        return self.list.GetSelectedLayers()
-
-    def GetSettings(self):
-        """!Get connection settings"""
-        return { 'server' : self.server.GetValue() }
-    
-class LayersList(TreeListCtrl, listmix.ListCtrlAutoWidthMixin):
-    def __init__(self, parent, pos=wx.DefaultPosition):
-        """!List of layers to be imported (dxf, shp...)"""
-        self.parent = parent
-        
-        TreeListCtrl.__init__(self, parent, wx.ID_ANY,
-                              style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT |
-                              wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE)
-        
-        # setup mixins
-        listmix.ListCtrlAutoWidthMixin.__init__(self)
-        
-        self.AddColumn(_('Layer / Style'))
-        self.AddColumn(_('Title'))
-        self.SetMainColumn(0) # column with the tree
-        self.SetColumnWidth(0, 175)
-        
-        self.root = None
-        
-    def LoadData(self, data = {}):
-        """!Load data into list"""
-        # detete first all items
-        self.DeleteAllItems()
-        self.root = self.AddRoot(_("Layers"))
-
-        layers = data.keys()
-        if not layers:
-            return
-        
-        layers.sort()
-
-        for layer in layers:
-            title = data[layer]['title']
-            lchild = self.AppendItem(self.root, layer)
-            self.SetItemText(lchild, title, 1)
-            if 'style' in data[layer]:
-                styles = data[layer]['style'].keys()
-                if not styles:
-                    continue
-                styles.sort()
-                for style in styles:
-                    title = data[layer]['style'][style]
-                    schild = self.AppendItem(lchild, style)
-                    self.SetItemText(schild, title, 1)
-        
-        self.Expand(self.root)
-        
-    def GetItemCount(self):
-        """!Required for listmix.ListCtrlAutoWidthMixin"""
-        return 0
-
-    def GetCountPerPage(self):
-        """!Required for listmix.ListCtrlAutoWidthMixin"""
-        return 0
-
-    def GetSelectedLayers(self):
-        """!Get selected layers/styles"""
-        layers = dict()
-
-        for item in self.GetSelections():
-            parent = self.GetItemParent(item)
-            if parent == self.root: # -> layer
-                layer = self.GetItemText(item, 0)
-                layers[layer] = list()
-                sitem, cookie = self.GetFirstChild(item)
-                while sitem:
-                    layers[layer].append(self.GetItemText(sitem, 0))
-                    sitem, cookie = self.GetNextChild(item, cookie)
-            else: # -> style
-                layer = self.GetItemText(parent, 0)
-                layers[layer] = list()
-                layers[layer].append(self.GetItemText(item, 0))
-        
-        return layers

+ 397 - 0
gui/wxpython/web_services/cap_interface.py

@@ -0,0 +1,397 @@
+"""!
+@package web_services.cap_interface
+
+@brief Provides common interface for GUI web_services.widgets to capabilities data of web services.
+
+List of classes:
+ - cap_interface::CapabilitiesBase
+ - cap_interface::LayerBase
+ - cap_interface::WMSCapabilities
+ - cap_interface::WMSLayer
+ - cap_interface::WMTSCapabilities
+ - cap_interface::WMTSLayer
+ - cap_interface::OnEarthCapabilities
+ - cap_interface::OnEarthLayer
+
+(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.
+
+@author Stepan Turek <stepan.turek seznam.cz> (Mentor: Martin Landa)
+"""
+
+import os
+import sys
+
+WMSLibPath = os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms")
+sys.path.append(WMSLibPath)
+
+from wms_cap_parsers import WMSCapabilitiesTree, \
+                            WMTSCapabilitiesTree, \
+                            OnEarthCapabilitiesTree
+
+class CapabilitiesBase:
+    def GetLayerByName(self, name):
+        """!Find layer by name
+        """
+        for l in self.layers_by_id:
+            if name == l.GetLayerData('name'):
+                return l
+        return None
+
+    def GetRootLayer(self):
+        """!Get children layers
+        """
+        if self.layers_by_id:
+            return self.layers_by_id[0]
+        else:
+            return None
+
+class LayerBase:
+    def GetId(self):
+        """!Get layer id
+        """
+        return self.id
+
+    def GetChildren(self):
+        """!Get children layers
+        """
+        return self.child_layers
+
+    def GetLayerNode(self):
+        """!Get layer node
+        """
+        return self.layer_node
+
+    def AddChildLayer(self, layer):
+        """!Add child layer
+        """
+        self.child_layers.append(layer)
+
+class WMSCapabilities(CapabilitiesBase, WMSCapabilitiesTree):
+    def __init__(self, cap_file, force_version = None):
+        """!Create common interface for web_services.widgets to WMS capabilities data
+        """
+        # checks all elements needed for creation of GetMap requests
+        # by r.in.wms/d.wms modules, invalid elements are removed
+        WMSCapabilitiesTree.__init__(self, cap_file, force_version)
+
+        self.cap_node = self.getroot().find(self.xml_ns.Ns("Capability"))
+        self.root_layer = self.cap_node.find(self.xml_ns.Ns("Layer"))
+
+        self.layers_by_id = {}
+        self._initializeLayerTree(self.root_layer)
+
+    def _initializeLayerTree(self, parent_layer, id = 0):
+        """!Build tree, which represents layers
+        """
+        if id == 0:
+            parent_layer = WMSLayer(parent_layer, id, self)
+            self.layers_by_id[id] = parent_layer
+            id += 1
+        
+        layer_nodes = parent_layer.GetLayerNode().findall((self.xml_ns.Ns("Layer")))
+        
+        for l in layer_nodes:
+            layer = WMSLayer(l, id, self)
+            parent_layer.AddChildLayer(layer)
+            self.layers_by_id[id] = layer
+            id += 1
+            id = self._initializeLayerTree(layer, id)
+        
+        return id
+
+    def GetFormats(self):
+        """!Get supported formats
+        """       
+        request_node = self.cap_node.find(self.xml_ns.Ns("Request"))
+        get_map_node = request_node.find(self.xml_ns.Ns("GetMap"))
+        format_nodes = get_map_node.findall(self.xml_ns.Ns("Format"))
+ 
+        formats = []
+        for node in format_nodes:
+            formats.append(node.text)
+
+        return formats
+
+class WMSLayer(LayerBase):
+    def __init__(self, layer_node, id, cap):
+        """!Common interface for web_services.widgets to WMS capabilities <Layer> element
+        """
+        self.id = id
+        self.cap = cap
+        self.child_layers = []
+        self.layer_node = layer_node
+        self.xml_ns = self.cap.getxmlnshandler()
+
+    def GetLayerData(self, param):
+        """!Get layer data"""
+        title = self.xml_ns.Ns("Title")
+        name = self.xml_ns.Ns("Name")
+
+        if param == 'title':
+            title_node = self.layer_node.find(title)
+            if title_node is not None:
+                return title_node.text 
+            else:
+                return None
+
+        if param == 'name':
+            name_node = self.layer_node.find(name)
+            if name_node is not None:
+                return name_node.text 
+            else:
+                return None
+
+        if param == 'format':
+            return self.cap.GetFormats()
+
+        if param == 'styles':
+            styles = []
+            style = self.xml_ns.Ns("Style")
+            for style_node in self.layer_node.findall(style):
+                style_name = '' 
+                style_title = ''
+
+                if style_node.find(title) is not None:
+                    style_title = style_node.find(title).text
+                if style_node.find(name) is not None:
+                    style_name = style_node.find(name).text
+
+                styles.append({'title' : style_title, 
+                               'name' : style_name,
+                               'isDefault' : False})
+            return styles
+
+        if param == 'srs':
+            projs_nodes = self.layer_node.findall(self.xml_ns.Ns(self.cap.getprojtag()))
+
+            projs = []
+            if projs_nodes is None:
+                return projs
+            for p in projs_nodes:
+                projs.append(p.text.strip())
+            return projs
+
+    def IsRequestable(self):
+        """!Is it possible to use the layer for WMS GetMap request?
+        """
+        name = self.xml_ns.Ns("Name")
+        name_node = self.layer_node.find(name)
+
+        if name_node is not None:
+            return True
+        else:
+            return False
+
+class WMTSCapabilities(CapabilitiesBase, WMTSCapabilitiesTree):
+    def __init__(self, cap_file):
+        """!Create common interface for web_services.widgets to WMTS capabilities data
+        """
+        # checks all elements needed for creation of GetTile requests
+        # by r.in.wms/d.wms modules, invalid elements are removed
+        WMTSCapabilitiesTree.__init__(self, cap_file)
+
+        contents = self._find(self.getroot(), 'Contents', self.xml_ns.NsWmts)
+        layers = self._findall(contents, 'Layer', self.xml_ns.NsWmts)
+
+        self.layers_by_id = {}
+        
+        id = 0
+        root_layer = WMTSLayer(None, id, self)
+        self.layers_by_id[id] = root_layer
+
+        for layer_node in layers:
+            id += 1
+            self.layers_by_id[id] = WMTSLayer(layer_node, id, self)
+            root_layer.child_layers.append(self.layers_by_id[id])
+    
+class WMTSLayer(LayerBase):
+    def __init__(self, layer_node, id, cap):
+        """!Common interface for web_services.widgets to WMTS capabilities <Layer> element
+        """
+        self.id = id
+        self.cap = cap
+        self.child_layers = []
+        self.layer_node = layer_node
+        self.xml_ns = self.cap.getxmlnshandler()
+        self.projs = self._getProjs()
+
+    def GetLayerData(self, param):
+        """!Get layer data
+        """ 
+        title = self.xml_ns.NsOws("Title")
+        name = self.xml_ns.NsOws("Identifier")
+
+        if self.layer_node is None and param in ['title', 'name']:
+            return None
+        elif self.layer_node is None:
+            return []
+
+        if param == 'title':
+            title_node = self.layer_node.find(title)
+            if title_node is not None:
+                return title_node.text 
+            else:
+                return None
+
+        if param == 'name':
+            name_node = self.layer_node.find(name)
+            if name_node is not None:
+                return name_node.text 
+            else:
+                return None
+
+        if param == 'styles':
+            styles = []
+            for style_node in self.layer_node.findall(self.xml_ns.NsWmts("Style")):
+
+                style_name = '' 
+                style_title = ''
+
+                if style_node.find(title) is not None:
+                    style_title = style_node.find(title).text
+                if style_node.find(name) is not None:
+                    style_name = style_node.find(name).text
+
+                is_def = False
+                if 'isDefault' in style_node.attrib and\
+                    style_node.attrib['isDefault'] == 'true':
+                    is_def = True
+
+                styles.append({'title' : style_title, 
+                               'name' : style_name,
+                               'isDefault' : is_def})
+            
+            return styles
+
+        if param == 'format':
+            formats = []
+            for frmt in self.layer_node.findall(self.xml_ns.NsWmts('Format')):
+                formats.append(frmt.text.strip())
+            return formats
+
+        if param == 'srs':
+            return self.projs
+
+    def _getProjs(self):
+        """!Get layer projections
+        """ 
+        layer_projs = []
+        if self.layer_node is None:
+            return layer_projs
+
+        mat_set_links = self.layer_node.findall(self.xml_ns.NsWmts('TileMatrixSetLink'))
+
+        contents = self.cap.getroot().find(self.xml_ns.NsWmts('Contents'))
+        tileMatrixSets = contents.findall(self.xml_ns.NsWmts('TileMatrixSet'))
+
+        for link in  mat_set_links:
+            mat_set_link_id = link.find(self.xml_ns.NsWmts('TileMatrixSet')).text
+            if not mat_set_link_id:
+                continue
+
+            for mat_set in tileMatrixSets:
+                mat_set_id = mat_set.find(self.xml_ns.NsOws('Identifier')).text 
+                if mat_set_id and mat_set_id != mat_set_link_id:
+                    continue
+                mat_set_srs = mat_set.find(self.xml_ns.NsOws('SupportedCRS')).text.strip()
+                layer_projs.append(mat_set_srs)
+        return layer_projs
+
+    def IsRequestable(self):
+        """!Is it possible to use the layer for WMTS request?
+        """
+        if self.layer_node is None:
+           return False
+        else:
+            return True
+
+class OnEarthCapabilities(CapabilitiesBase, OnEarthCapabilitiesTree):
+    def __init__(self, cap_file):
+        """!Create Common interface for web_services.widgets to NASA OnEarth 
+            tile service data (equivalent to  WMS, WMTS capabilities data)
+        """
+        # checks all elements needed for creation of GetMap requests
+        # by r.in.wms/d.wms modules, invalid elements are removed
+        OnEarthCapabilitiesTree.__init__(self, cap_file)
+
+        self.layers_by_id = {}
+        self._initializeLayerTree(self.getroot())
+        
+    def _initializeLayerTree(self, parent_layer, id = 0):
+        """!Build tree, which represents layers
+        """
+        if id == 0:
+            tiled_patterns = parent_layer.find('TiledPatterns')
+            layer_nodes = tiled_patterns.findall('TiledGroup')
+            layer_nodes += tiled_patterns.findall('TiledGroups')
+            parent_layer = OnEarthLayer(None, None, id, self)
+            self.layers_by_id[id] = parent_layer
+            id += 1
+        else:
+            layer_nodes = parent_layer.layer_node.findall('TiledGroup')
+            layer_nodes += parent_layer.layer_node.findall('TiledGroups')
+
+        for layer_node in layer_nodes:
+            layer = OnEarthLayer(layer_node, parent_layer, id, self)
+            self.layers_by_id[id] = layer
+            id += 1
+            parent_layer.child_layers.append(layer)
+            if layer_node.tag == 'TiledGroups':
+               id = self._initializeLayerTree(layer, id)
+
+        return id
+
+class OnEarthLayer(LayerBase):
+    def __init__(self, layer_node, parent_layer, id, cap):
+        """!Common interface for web_services.widgets to NASA Earth
+            capabilities <TiledGroup>\<TiledGroups> element 
+            (equivalent to  WMS, WMTS <Layer> element)
+        """
+        self.id = id
+        self.cap = cap
+        self.layer_node = layer_node
+        self.child_layers = []
+        self.parent_layer = parent_layer
+
+    def IsRequestable(self):
+        """!Is it possible to use the layer for NASA OnEarth GetMap request?
+        """
+        if self.layer_node is None or \
+           self.layer_node.tag == 'TiledGroups':
+           return False
+        else:
+            return True
+
+    def GetLayerData(self, param):
+        """!Get layer data
+        """
+        if self.layer_node is None and param in ['title', 'name']:
+            return None
+        elif self.layer_node is None:
+            return []
+
+        if param == 'title':
+            title_node = self.layer_node.find("Title")
+            if title_node is not None:
+                return title_node.text 
+            else:
+                return None
+
+        if param == 'name':
+            name_node = self.layer_node.find("Name")
+            if name_node is not None:
+                return name_node.text 
+            else:
+                return None
+
+        if param == 'styles':
+            return []
+
+        if param == 'format':
+            return []
+
+        if param == 'srs':
+            return []

+ 972 - 0
gui/wxpython/web_services/dialogs.py

@@ -0,0 +1,972 @@
+"""!
+@package web_services.dialogs
+
+@brief Dialogs for web services.
+
+List of classes:
+ - dialogs::WSDialogBase
+ - dialogs::AddWSDialog
+ - dialogs::WSPropertiesDialog
+ - dialogs::SaveWMSLayerDialog
+
+(C) 2009-2013 by the GRASS Development Team
+
+This program is free software under the GNU General Public License
+(>=v2). Read the file COPYING that comes with GRASS for details.
+
+@author Martin Landa <landa.martin gmail.com>
+@author Stepan Turek <stepan.turek seznam.cz>
+"""
+
+import wx
+
+import os
+import sys
+import shutil
+
+from copy      import deepcopy
+
+import grass.script as grass
+
+from core             import globalvar
+from core.debug       import Debug
+from core.ws          import RenderWMSMgr
+from core.events      import gUpdateMap
+from core.gcmd        import GMessage, RunCommand, GWarning
+from core.utils       import GetSettingsPath, CmdToTuple, CmdTupleToList
+from core.gconsole    import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
+
+from gui_core.gselect import Select
+from gui_core.widgets import ManageSettingsWidget,\
+                             EVT_SETTINGS_CHANGED, EVT_SETTINGS_SAVING, EVT_SETTINGS_LOADED
+
+from web_services.widgets import WSPanel, EVT_CAP_PARSED
+
+class WSDialogBase(wx.Dialog):
+    """!Base class for web service dialogs. 
+    """
+    def __init__(self, parent, id = wx.ID_ANY,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+
+        wx.Dialog.__init__(self, parent, id, style = style, **kwargs)
+
+        self.parent = parent 
+
+        # contains panel for every web service on server
+        self.ws_panels =  {'WMS_1.1.1'  : {'panel' : None,
+                                           'label' : 'WMS 1.1.1'},
+                           'WMS_1.3.0' : {'panel' : None,
+                                          'label' : 'WMS 1.3.0'},
+                           'WMTS' : {'panel' : None,
+                                     'label' : 'WMTS'},
+                           'OnEarth' : {'panel' : None,
+                                        'label' : 'OnEarth'},
+                          }
+
+        #TODO: should be in file
+        self.default_servers = { 'OSM-WMS-EUROPE' : ['http://129.206.228.72/cached/osm', '', ''],
+                                 'irs.gis-lab.info (OSM)' : ['http://irs.gis-lab.info', '', ''],
+                                 'NASA OnEarth' : ['http://onearth.jpl.nasa.gov/wms.cgi', '', '']
+                               }
+
+        # holds reference to web service panel which is showed
+        self.active_ws_panel = None
+
+        # buttons which are disabled when the dialog is not connected
+        self.run_btns = []
+
+        self._createWidgets()
+        self._doLayout()
+
+    def _createWidgets(self):
+
+        settingsFile = os.path.join(GetSettingsPath(), 'wxWS')
+
+        self.settsManager = ManageSettingsWidget(parent = self, 
+                                                 id = wx.ID_ANY,
+                                                 settingsFile = settingsFile)
+
+        self.settingsBox = wx.StaticBox(parent = self, 
+                                        id = wx.ID_ANY,
+                                        label = _(" Server settings "))
+        
+        self.serverText = wx.StaticText(parent = self, 
+                                        id = wx.ID_ANY, label = _("Server:"))
+        self.server  = wx.TextCtrl(parent = self, id = wx.ID_ANY)
+
+        self.btn_connect = wx.Button(parent = self, 
+                                     id = wx.ID_ANY, label = _("&Connect"))
+        self.btn_connect.SetToolTipString(_("Connect to the server"))
+        self.btn_connect.SetDefault()
+        if not self.server.GetValue():
+            self.btn_connect.Enable(False)
+
+        self.infoCollapseLabelExp = _('Show advanced connection settings')
+        self.infoCollapseLabelCol = _('Hide advanced connection settings')
+
+        self.adv_conn = wx.CollapsiblePane(parent = self,
+                                           label = self.infoCollapseLabelExp,
+                                           style = wx.CP_DEFAULT_STYLE |
+                                                   wx.CP_NO_TLW_RESIZE | wx.EXPAND)
+
+        self.MakeAdvConnPane(pane = self.adv_conn.GetPane())
+        self.adv_conn.Collapse(True)
+        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnAdvConnPaneChanged, self.adv_conn) 
+
+        self.reqDataPanel = wx.Panel(parent = self, id = wx.ID_ANY)
+
+        self.layerNameText = wx.StaticText(parent = self.reqDataPanel, id = wx.ID_ANY, 
+                                           label = _("Output layer name:"))
+        self.layerName = wx.TextCtrl(parent = self.reqDataPanel, id = wx.ID_ANY)
+
+        for ws in self.ws_panels.iterkeys():
+            self.ws_panels[ws]['panel'] =  WSPanel(parent = self.reqDataPanel,
+                                                   web_service = ws,
+                                                   receiver = self)
+
+        # buttons
+        self.btn_close = wx.Button(parent = self, id = wx.ID_CLOSE)
+        self.btn_close.SetToolTipString(_("Close dialog"))
+        
+        # statusbar
+        self.statusbar = wx.StatusBar(parent = self, id = wx.ID_ANY)
+
+        # bindings
+        self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
+        self.Bind(wx.EVT_CLOSE, self.OnClose)
+        self.btn_connect.Bind(wx.EVT_BUTTON, self.OnConnect)
+
+        self.server.Bind(wx.EVT_TEXT, self.OnServer)
+        self.layerName.Bind(wx.EVT_TEXT, self.OnOutputLayerName)
+
+        self.settsManager.Bind(EVT_SETTINGS_CHANGED, self.OnSettingsChanged)
+        self.settsManager.Bind(EVT_SETTINGS_SAVING, self.OnSettingsSaving)
+        self.settsManager.Bind(EVT_SETTINGS_LOADED, self.OnSettingsLoaded)
+
+        self.Bind(EVT_CAP_PARSED, self.OnPanelCapParsed)
+
+    def _doLayout(self):
+
+        dialogSizer = wx.BoxSizer(wx.VERTICAL)
+
+        dialogSizer.Add(item = self.settsManager, proportion = 0,
+                        flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
+        
+        # connectin settings
+        settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.VERTICAL)
+        
+        serverSizer = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
+
+        serverSizer.Add(item = self.serverText,
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        serverSizer.AddGrowableCol(1)
+        serverSizer.Add(item = self.server,
+                      flag = wx.EXPAND | wx.ALL)
+
+        serverSizer.Add(item = self.btn_connect)
+
+        settingsSizer.Add(item = serverSizer, proportion = 0,
+                          flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
+        
+        settingsSizer.Add(item = self.adv_conn,
+                          flag = wx.ALL | wx.EXPAND, border = 5)
+
+        dialogSizer.Add(item = settingsSizer, proportion = 0,
+                        flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
+
+        # layer name, parsed capabilites
+
+        reqDataSizer = wx.BoxSizer(wx.VERTICAL)
+
+        layerNameSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        layerNameSizer.Add(item = self.layerNameText,
+                           flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
+
+        layerNameSizer.Add(item = self.layerName, 
+                           flag = wx.EXPAND, proportion = 1)
+ 
+        reqDataSizer.Add(item = layerNameSizer,
+                         flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
+
+        self.ch_ws_sizer = wx.BoxSizer(wx.VERTICAL)
+
+        reqDataSizer.Add(item = self.ch_ws_sizer, proportion = 0,
+                         flag = wx.TOP | wx.EXPAND, border = 5)
+
+        for ws in self.ws_panels.iterkeys():
+            reqDataSizer.Add(item = self.ws_panels[ws]['panel'], proportion = 1,
+                             flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
+            self.ws_panels[ws]['panel'].Hide()
+
+        dialogSizer.Add(item = self.reqDataPanel, proportion = 1,
+                        flag = wx.EXPAND)
+
+        self.reqDataPanel.SetSizer(reqDataSizer)
+        self.reqDataPanel.Hide()
+
+        # buttons
+        self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+
+        self.btnsizer.Add(item = self.btn_close, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+        
+        dialogSizer.Add(item = self.btnsizer, proportion = 0,
+                        flag = wx.ALIGN_CENTER)
+
+        dialogSizer.Add(item = self.statusbar, proportion = 0)
+
+        self.SetSizer(dialogSizer)
+        self.Layout()
+
+        self.SetMinSize((550, -1))
+        self.SetMaxSize((-1, self.GetBestSize()[1]))
+        self.Fit()
+
+    def MakeAdvConnPane(self, pane):
+        """!Create advanced connection settings pane
+        """
+        self.usernameText = wx.StaticText(parent = pane,
+                                          id = wx.ID_ANY, label = _("Username:"))
+        self.username  = wx.TextCtrl(parent = pane, id = wx.ID_ANY)
+
+        self.passwText = wx.StaticText(parent = pane, 
+                                        id = wx.ID_ANY, label = _("Password:"))
+        self.password  = wx.TextCtrl(parent = pane, id = wx.ID_ANY,
+                                     style = wx.TE_PASSWORD)
+
+        # pane layout
+        adv_conn_sizer = wx.BoxSizer(wx.VERTICAL)
+
+        usernameSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        usernameSizer.Add(item = self.usernameText,
+                          flag = wx.ALIGN_CENTER_VERTICAL, border = 5)
+
+        usernameSizer.Add(item = self.username, proportion = 1, 
+                          flag = wx.EXPAND, border = 5)
+
+        adv_conn_sizer.Add(item = usernameSizer,
+                           flag = wx.ALL | wx.EXPAND, border = 5)
+
+        passwSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        passwSizer.Add(item = self.passwText,
+                       flag = wx.ALIGN_CENTER_VERTICAL, border = 5)
+
+        passwSizer.Add(item = self.password, proportion = 1, 
+                       flag = wx.EXPAND, border = 5)
+
+        adv_conn_sizer.Add(item = passwSizer,
+                           flag = wx.ALL | wx.EXPAND, border = 5)
+        
+        pane.SetSizer(adv_conn_sizer)
+        adv_conn_sizer.Fit(pane)
+
+        pane.SetSizer(adv_conn_sizer)
+        adv_conn_sizer.Fit(pane)
+
+    def OnSettingsSaving(self, event):
+        """!Check if required data are filled before setting save is performed.
+        """
+        server = self.server.GetValue().strip()
+        if not server:
+            GMessage(parent = self,
+                     message = _("No data source defined, settings are not saved."))
+            return
+
+        self.settsManager.SetDataToSave((server,
+                                         self.username.GetValue(),
+                                         self.password.GetValue()))
+        event.Skip()
+
+    def OnSettingsChanged(self, event):
+        """!Update widgets according to chosen settings"""
+        data = event.data
+
+        # data list: [server, username, password]
+        if len < 3:
+            return
+
+        self.server.SetValue(data[0])
+
+        self.username.SetValue(data[1])
+        self.password.SetValue(data[2])
+
+        if data[1] or data[2]:
+            self.adv_conn.Expand()
+        else:
+            self.adv_conn.Collapse(True)
+
+    def OnSettingsLoaded(self, event):
+        """!If settings are empty set default servers
+        """
+        if not event.settings:
+            self.settsManager.SetSettings(self.default_servers)
+
+    def OnClose(self, event):
+        """!Close the dialog
+        """
+        """!Close dialog"""
+        if not self.IsModal():
+            self.Destroy()
+        event.Skip()
+
+    def _getCapFiles(self):
+        ws_cap_files = {}
+        for v in self.ws_panels.itervalues():
+            ws_cap_files[v['panel'].GetWebService()] = v['panel'].GetCapFile()
+
+        return ws_cap_files
+
+    def OnServer(self, event):
+        """!Server settings edited
+        """
+        value = event.GetString()
+        if value:
+            self.btn_connect.Enable(True)
+        else:
+            self.btn_connect.Enable(False)
+        
+    def OnOutputLayerName(self, event):
+        """!Update layer name to web service panel
+        """
+        lname = event.GetString()
+
+        for v in self.ws_panels.itervalues():
+            v['panel'].SetOutputLayerName(lname.strip())
+
+    def OnConnect(self, event):
+        """!Connect to the server
+        """
+        server = self.server.GetValue().strip()
+
+        self.ch_ws_sizer.Clear(deleteWindows = True)
+
+        if self.active_ws_panel is not None:
+            self.reqDataPanel.Hide()
+            for btn in self.run_btns:
+                btn.Enable(False)
+            self.active_ws_panel = None
+
+            self.Layout()
+            self.Fit()
+
+        self.statusbar.SetStatusText(_("Connectig to <%s>..." % self.server.GetValue().strip()))
+
+        # number of panels already connected
+        self.finished_panels_num = 0
+        for ws in self.ws_panels.iterkeys():
+            self.ws_panels[ws]['panel'].ConnectToServer(url = server,
+                                                        username = self.username.GetValue(),
+                                                        password = self.password.GetValue())
+            self.ws_panels[ws]['panel'].Hide()
+
+    def OnPanelCapParsed(self, event):
+        """!Called when panel has downloaded and parsed capabilities file.
+        """
+        # how many web service panels are finished
+        self.finished_panels_num +=  1
+
+        # if all are finished, show panels, which succeeded in connection
+        if self.finished_panels_num == len(self.ws_panels):
+            self.UpdateDialogAfterConnection()
+            self.Layout()
+            self.Fit()
+
+    def UpdateDialogAfterConnection(self):
+        """!Update dialog after all web service panels downloaded and parsed capabilities data.
+        """
+        avail_ws = {}
+        for ws, data in self.ws_panels.iteritems():
+
+            if data['panel'].IsConnected():
+                avail_ws[ws] = data
+
+        self.web_service_sel = []
+        self.rb_choices = []
+
+        # at least one web service found on server
+        if len(avail_ws) > 0:
+            self.reqDataPanel.Show()
+            self.rb_order = ['WMS_1.1.1', 'WMS_1.3.0', 'WMTS', 'OnEarth']
+
+            for ws in self.rb_order:
+
+                if ws in avail_ws:
+                    self.web_service_sel.append(ws)
+                    self.rb_choices.append(avail_ws[ws]['label'])
+
+            self.choose_ws_rb = wx.RadioBox(parent = self.reqDataPanel, id = wx.ID_ANY, 
+                                            label = _("Available web services"), 
+                                            pos = wx.DefaultPosition, choices = self.rb_choices, 
+                                            majorDimension = 1, style = wx.RA_SPECIFY_ROWS)
+        
+            self.Bind(wx.EVT_RADIOBOX, self.OnChooseWs, self.choose_ws_rb)
+            self.ch_ws_sizer.Add(item = self.choose_ws_rb,
+                                 flag = wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
+            self._showWsPanel(self.web_service_sel[self.choose_ws_rb.GetSelection()])
+            self.statusbar.SetStatusText(_("Connected to <%s>" % self.server.GetValue().strip()))
+            for btn in self.run_btns:
+                btn.Enable(True)
+        # no web service found on server
+        else:
+            self.statusbar.SetStatusText(_("Unable to connect to <%s>" % self.server.GetValue().strip()))
+            for btn in self.run_btns:
+                btn.Enable(False)
+            self.reqDataPanel.Hide()
+            self.active_ws_panel = None
+
+    def OnChooseWs(self, event):
+        """!Show panel corresponding to selected web service.
+        """
+        choosen_r = event.GetInt() 
+        self._showWsPanel(self.web_service_sel[choosen_r])
+
+    def _showWsPanel(self, ws):
+        """!Helper function
+        """
+        if self.active_ws_panel is not None:
+            self.active_ws_panel.Hide()
+
+        self.active_ws_panel = self.ws_panels[ws]['panel']
+        self.active_ws_panel.Show()
+        self.SetMaxSize((-1, -1))
+        self.Layout()
+        self.Fit()
+
+    def OnAdvConnPaneChanged(self, event):
+        """!Collapse search module box
+        """
+        if self.adv_conn.IsExpanded():
+            self.adv_conn.SetLabel(self.infoCollapseLabelCol)
+        else:
+            self.adv_conn.SetLabel(self.infoCollapseLabelExp)
+
+        self.Layout()
+        self.SetMaxSize((-1, self.GetBestSize()[1]))
+        self.SendSizeEvent()
+        self.Fit()
+
+class AddWSDialog(WSDialogBase):
+    """!Show web service layer."""
+    def __init__(self, parent, gmframe, id = wx.ID_ANY,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+
+        WSDialogBase.__init__(self, parent, id = wx.ID_ANY,
+                              style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs)
+
+        self.SetTitle(_("Add web service layer"))
+
+        self.gmframe = gmframe
+
+    def _createWidgets(self):
+
+        WSDialogBase._createWidgets(self)
+
+        self.btn_add = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Add layer"))
+        self.btn_add.SetToolTipString(_("Import selected layers"))        
+        self.btn_add.Enable(False)
+
+        self.run_btns.append(self.btn_add)
+
+    def _doLayout(self):
+
+        WSDialogBase._doLayout(self)
+
+        self.btnsizer.Add(item = self.btn_add, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+
+        # bindings
+        self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddLayer)
+
+    def OnAddLayer(self, event):
+        """!Add web service layer.
+        """
+        # add layer
+        if self.active_ws_panel is None:
+            return 
+
+        lcmd = self.active_ws_panel.CreateCmd()
+        if not lcmd:
+            return None
+
+        ltree = self.gmframe.GetLayerTree()
+
+        active_ws = self.active_ws_panel.GetWebService()
+        if 'WMS' not in active_ws:
+            cap_file =  self.active_ws_panel.GetCapFile()
+            cmd_cap_file = grass.tempfile()
+            shutil.copyfile(cap_file, cmd_cap_file)
+            lcmd.append('capfile=' + cmd_cap_file)
+
+        layer = ltree.AddLayer(ltype = 'wms', lname = self.layerName.GetValue(), 
+                               lchecked = True, lcmd = lcmd)
+
+
+        ws_cap_files = self._getCapFiles()
+        # create properties dialog
+        cmd_list = ltree.GetLayerInfo(layer,'cmd')
+        cmd = CmdToTuple(cmd_list)
+
+        prop_win = WSPropertiesDialog(parent = self.gmframe,
+                                      id = wx.ID_ANY,
+                                      layer = layer,
+                                      ltree = ltree,
+                                      ws_cap_files = ws_cap_files,
+                                      cmd = cmd)
+
+        prop_win.Hide()
+        ltree.GetOptData(dcmd = None, layer = layer, 
+                         params = None, propwin = prop_win)
+
+class WSPropertiesDialog(WSDialogBase):
+    """!Show web service property."""
+    def __init__(self, parent, layer, ltree, ws_cap_files, cmd, id = wx.ID_ANY,
+                 style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
+        """
+        @param layer - layer tree item
+        @param ltree - layer tree reference
+        @param ws_cap_files - dict web service('WMS_1.1.1', 'WMS_1.3.0', 'WMTS', 'OnEarth') : cap file path
+                            - cap files, which will be parsed
+        @param cmd - cmd to which dialog widgets will be initialized if it is possible 
+                    (cmp parameters exists in parsed web service cap_file)
+        """
+
+        WSDialogBase.__init__(self, parent, id = wx.ID_ANY,
+                               style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs)
+
+        self.SetTitle(_("Web service layer properties"))
+
+        self.ltree = ltree
+        self.layer = layer
+
+        # after web service panels are connected, set dialog widgets
+        # according to cmd in this variable (if it is not None) 
+        self.cmd_to_set = None
+
+        # store data needed for reverting
+        self.revert_ws_cap_files = {}
+        self.revert_cmd = cmd
+
+        ws_cap = self._getWSfromCmd(cmd)
+        for ws in self.ws_panels.iterkeys():
+            # cap file used in cmd will be deleted, thnaks to the dialogs destructor
+            if ws == ws_cap and cmd[1].has_key('capfile'):
+                self.revert_ws_cap_files[ws] = cmd[1]['capfile']
+                del ws_cap_files[ws]
+            else:
+                self.revert_ws_cap_files[ws] = grass.tempfile()
+
+        self._setRevertCapFiles(ws_cap_files)
+
+        self.LoadCapFiles(ws_cap_files = self.revert_ws_cap_files, cmd = cmd)
+
+    def __del__(self):
+        for f in self.revert_ws_cap_files.itervalues():
+            grass.try_remove(f)
+
+    def _setRevertCapFiles(self, ws_cap_files):
+
+        for ws, f in ws_cap_files.iteritems():
+            if os.path.isfile(ws_cap_files[ws]):
+                shutil.copyfile(f, self.revert_ws_cap_files[ws])
+            else:
+                # delete file content
+                f_o = open(f, 'w')
+                f_o.close()
+
+    def _createWidgets(self):
+
+        WSDialogBase._createWidgets(self)
+
+        self.btn_apply = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Apply"))
+        self.btn_apply.SetToolTipString(_("Apply changes"))        
+        self.btn_apply.Enable(False)
+        self.run_btns.append(self.btn_apply)
+
+        self.btn_save = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Save"))
+        self.btn_save.SetToolTipString(_("Revert changes"))
+        self.btn_save.Enable(False)
+        self.run_btns.append(self.btn_save)
+
+    def _doLayout(self):
+
+        WSDialogBase._doLayout(self)
+
+        self.btnsizer.Add(item = self.btn_apply, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+
+        self.btnsizer.Add(item = self.btn_save, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+
+        # bindings
+        self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
+        self.btn_save.Bind(wx.EVT_BUTTON, self.OnSave)
+
+    def LoadCapFiles(self, ws_cap_files, cmd):
+        """!Parse cap files and update dialog.
+
+        @params for description see constructor
+        """
+        self.ch_ws_sizer.Clear(deleteWindows = True)
+
+        self.cmd_to_set = cmd
+
+        self.finished_panels_num = 0
+
+        conn = self._getServerConnFromCmd(cmd)
+
+        self.server.SetValue(conn['url'])
+        self.password.SetValue(conn['password'])
+        self.username.SetValue(conn['username'])
+
+        self.layerName.SetValue(cmd[1]['map'])
+
+        for ws, data in self.ws_panels.iteritems():
+            cap_file = None
+
+            if ws_cap_files.has_key(ws):
+                cap_file = ws_cap_files[ws]
+
+            data['panel'].ParseCapFile(url = conn['url'], 
+                                       username = conn['password'], 
+                                       password = conn['username'], 
+                                       cap_file = cap_file)
+
+    def _getServerConnFromCmd(self, cmd):
+        """!Get url/server/passwod from cmd tuple 
+        """
+        conn = { 'url' : '', 'username' : '', 'password' : ''}
+        
+        for k in conn.iterkeys():
+            if cmd[1].has_key(k):
+                conn[k] = cmd[1][k]
+        return conn
+
+    def _apply(self):
+        """!Apply chosen values from widgets to web service layer."""
+        lcmd = self.active_ws_panel.CreateCmd()
+        if not lcmd:
+            return
+
+        active_ws = self.active_ws_panel.GetWebService()
+        if 'WMS' not in active_ws:
+            lcmd.append('capfile=' + self.revert_ws_cap_files[active_ws])
+
+        self.ltree.GetOptData(dcmd = lcmd, 
+                              layer = self.layer, 
+                              params = None,
+                              propwin = self)
+
+        #TODO use just list or tuple
+        cmd = CmdToTuple(lcmd)
+        self.revert_cmd = cmd
+        self._setRevertCapFiles(self._getCapFiles())
+
+        display = self.ltree.GetMapDisplay().GetMapWindow()
+        event = gUpdateMap()
+        wx.PostEvent(display, event)
+
+    def OnApply(self, event):   
+        self._apply()
+
+    def OnSave(self, event):
+        self._apply()
+        self._close()
+
+    def OnClose(self, event):
+        """!Close dialog"""
+        self._close()
+
+    def _close(self):
+        """!Hide dialog"""
+        self.Hide()
+        self.LoadCapFiles(cmd = self.revert_cmd,
+                          ws_cap_files = self.revert_ws_cap_files)
+
+    def OnPanelCapParsed(self, event):
+        """!Called when panel has downloaded and parsed capabilities file.
+        """
+        WSDialogBase.OnPanelCapParsed(self, event)
+
+        if self.finished_panels_num == len(self.ws_panels):
+            if self.cmd_to_set:
+                self._updateWsPanelWidgetsByCmd(self.cmd_to_set)
+                self.cmd_to_set = None
+
+    def _updateWsPanelWidgetsByCmd(self, cmd):
+        """!Set values of  widgets according to parameters in cmd.
+        """
+
+        ws = self._getWSfromCmd(cmd)
+        if self.ws_panels[ws]['panel'].IsConnected():
+            self.ws_panels[ws]['panel'].UpdateWidgetsByCmd(cmd)
+            self.choose_ws_rb.SetStringSelection(self.ws_panels[ws]['label'])
+            self._showWsPanel(ws)
+
+    def _getWSfromCmd(self, cmd):
+        driver = cmd[1]['driver']
+        ws = driver.split('_')[0]
+        
+        if ws == 'WMS':
+            ws += '_' + cmd[1]['wms_version']
+        return ws
+
+class SaveWMSLayerDialog(wx.Dialog):
+    """!Dialog for saving web service layer into GRASS vector/raster layer.
+
+    @todo Implement saving data in region of map display.
+    """
+    def __init__(self, parent, layer, ltree):
+        
+        wx.Dialog.__init__(self, parent = parent, title = ("Save web service layer"), id = wx.ID_ANY)
+
+        self.layer = layer
+        self.ltree = ltree
+
+        self.cmd = self.layer.GetCmd()
+
+        self.thread = CmdThread(self)
+        self.cmdStdErr = GStderr(self)
+
+        self._createWidgets()
+
+    def _createWidgets(self):
+
+        self.labels = {}
+        self.params = {}
+
+        self.labels['output'] = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("Name for output raster layer:"))
+
+        self.params['output'] = Select(parent = self, type = 'rast', mapsets = [grass.gisenv()['MAPSET']],
+                                       size = globalvar.DIALOG_GSELECT_SIZE)
+
+        self.regionStBoxLabel = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                             label = _("Region"))
+
+        self.region_types_order = ['comp', 'named']
+        self.region_types =  {}
+        #self.region_types['map_display'] = wx.RadioButton(parent = self, id = wx.ID_ANY, label = 'Map display', style = wx.RB_GROUP )
+        self.region_types['comp'] = wx.RadioButton(parent = self, id = wx.ID_ANY, label = 'Computational region')
+        self.region_types['named'] = wx.RadioButton(parent = self, id = wx.ID_ANY, label = 'Named region')
+
+        self.overwrite  = wx.CheckBox(parent = self, id = wx.ID_ANY,
+                                      label = _("Overwrite existing layer"))
+
+        self.named_reg_panel = wx.Panel(parent = self, id = wx.ID_ANY)
+        self.labels['region'] = wx.StaticText(parent = self.named_reg_panel, id = wx.ID_ANY, 
+                                             label = _("Choose named region:"))
+
+        self.params['region'] = Select(parent = self.named_reg_panel, type = 'region',
+                                       size = globalvar.DIALOG_GSELECT_SIZE)
+
+        # buttons
+        self.btn_close = wx.Button(parent = self, id = wx.ID_CLOSE)
+        self.btn_close.SetToolTipString(_("Close dialog"))
+        
+        self.btn_save = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Save layer"))
+        self.btn_save.SetToolTipString(_("Add web service layer"))     
+
+        # statusbar
+        self.statusbar = wx.StatusBar(parent = self, id = wx.ID_ANY)
+
+        self._layout()
+
+    def _layout(self):
+
+        border = wx.BoxSizer(wx.VERTICAL) 
+        dialogSizer = wx.BoxSizer(wx.VERTICAL)
+
+        regionSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+        dialogSizer.Add(item = self._addSelectSizer(title = self.labels['output'], 
+                                                    sel = self.params['output']))
+
+        dialogSizer.Add(item = self.overwrite)
+
+        regionSizer = wx.StaticBoxSizer(self.regionStBoxLabel, wx.VERTICAL)
+
+        regionTypeSizer = wx.BoxSizer(wx.HORIZONTAL)
+        for r_type in self.region_types_order:
+            regionTypeSizer.Add(item = self.region_types[r_type])
+
+        regionSizer.Add(item = regionTypeSizer)
+
+        self.named_reg_panel.SetSizer(self._addSelectSizer(title = self.labels['region'],
+                                                            sel = self.params['region']))
+        regionSizer.Add(item = self.named_reg_panel)
+        self.named_reg_panel.Hide()
+
+        dialogSizer.Add(item = regionSizer, flag = wx.EXPAND)
+
+        # buttons
+        self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
+
+        self.btnsizer.Add(item = self.btn_close, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+        
+        self.btnsizer.Add(item = self.btn_save, proportion = 0,
+                          flag = wx.ALL | wx.ALIGN_CENTER,
+                          border = 10)
+
+        dialogSizer.Add(item = self.btnsizer, proportion = 0,
+                        flag = wx.ALIGN_CENTER)
+
+        border.Add(item = dialogSizer, proportion = 0,
+                   flag = wx.ALL, border = 5)
+
+        border.Add(item = self.statusbar, proportion = 0)
+
+        self.SetSizer(border)
+        self.Layout()
+        self.Fit()
+
+        # bindings
+        self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
+        self.btn_save.Bind(wx.EVT_BUTTON, self.OnSave)
+
+        self.Bind(EVT_CMD_DONE,   self.OnCmdDone)
+        self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
+
+        for r_type in self.region_types_order:
+            self.Bind(wx.EVT_RADIOBUTTON, self.OnRegionType, self.region_types[r_type])
+
+    def _addSelectSizer(self, title, sel): 
+        """!Helper layout function.
+        """
+        selSizer = wx.BoxSizer(orient = wx.VERTICAL)
+
+        selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
+        selTitleSizer.Add(item = title, proportion = 1,
+                          flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
+
+        selSizer.Add(item = selTitleSizer, proportion = 0,
+                     flag = wx.EXPAND)
+
+        selSizer.Add(item = sel, proportion = 1,
+                     flag = wx.EXPAND | wx.ALL| wx.ALIGN_CENTER_VERTICAL,
+                     border = 5)
+
+        return selSizer
+
+    def OnClose(self, event):
+        """!Close dialog
+        """
+        if not self.IsModal():
+            self.Destroy()
+        event.Skip()
+
+    def OnRegionType(self, event):
+        
+        selected = event.GetEventObject()
+        if selected == self.region_types['named']:
+            self.named_reg_panel.Show()
+        else:
+            self.named_reg_panel.Hide()
+
+        self.Layout()
+        self.Fit()
+
+    def OnSave(self, event):
+        """!Import WMS raster data into GRASS as raster layer.
+        """
+        self.thread.abort(abortall = True)
+        currmapset = grass.gisenv()['MAPSET']
+        
+        self.output = self.params['output'].GetValue().strip()
+        l_spl = self.output.strip().split("@")
+
+        # check output layer
+        msg = None
+        if not self.output:
+            msg = _('Missing output raster.')
+
+        elif len(l_spl) > 1 and \
+             l_spl[1] != currmapset:
+                msg = _('Output map can be added only to current mapset.')
+
+        elif not self.overwrite.IsChecked() and\
+            grass.find_file(self.output, 'cell', '.')['fullname']:
+            msg = _('Output map <%s> already exists' % self.output)
+
+        if msg:
+            GWarning(parent = self,
+                     message = msg)
+            return
+
+        self.output = l_spl[0]
+
+
+        # check region
+        region = self.params['region'].GetValue().strip()
+        reg_spl = region.strip().split("@")
+
+        reg_mapset = '.'
+        if len(reg_spl) > 1:
+            reg_mapset = reg_spl[1]
+
+        if self.region_types['comp'].GetValue() == 1: 
+            pass
+        elif grass.find_file(reg_spl[0], 'region', reg_mapset)['fullname']:
+            msg = _('Region <%s> does not exists.' % self.params['region'].GetValue())
+            GWarning(parent = self,
+                     message = msg)
+            return
+
+        # create r.in.wms command
+        cmd = ('r.in.wms', deepcopy(self.cmd[1]))
+
+        if cmd[1].has_key('map'):
+            del cmd[1]['map']
+
+        cmd[1]['output'] = self.output
+
+        if self.overwrite.IsChecked():
+            cmd[1]['overwrite'] = True
+
+        if self.region_types['named'].GetValue() == 1:
+            cmd[1]['region'] = region
+
+        cmdList = CmdTupleToList(cmd)
+        self.currentPid = self.thread.GetId()
+
+        self.thread.RunCmd(cmdList, stderr = self.cmdStdErr)
+
+        self.statusbar.SetStatusText(_("Downloading data..."))
+
+    def OnCmdDone(self, event):
+        """!When data are fetched.
+        """
+        if event.pid != self.currentPid:
+            return
+
+        self._addLayer()
+        self.statusbar.SetStatusText("")
+
+    def _addLayer(self):
+        """!Add layer into layer tree.
+        """
+        if self.ltree.FindItemByData(key = 'name', value = self.output) is None: 
+            cmd = ['d.rast', 'map=' + self.output]
+            self.ltree.AddLayer(ltype = 'raster',
+                                lname = self.output,
+                                lcmd = cmd,
+                                lchecked = True)
+
+    def OnCmdOutput(self, event):
+        """!Handle cmd output according to debug level.
+        """
+        if Debug.GetLevel() == 0:
+            if event.type == 'error':
+                msg = _('Unable to fetch data.\n')
+                msg += event.text
+                GWarning(parent = self,
+                         message = msg)
+        else:
+            Debug.msg(1, event.text)

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1016 - 0
gui/wxpython/web_services/widgets.py


+ 0 - 6
gui/wxpython/xml/menudata.xml

@@ -184,12 +184,6 @@
               <handler>OnMenuCmd</handler>
               <handler>OnMenuCmd</handler>
               <command>r.in.lidar</command>
               <command>r.in.lidar</command>
             </menuitem>
             </menuitem>
-            <menuitem>
-              <label>WMS import</label>
-              <help>Download and import data from WMS servers.</help>
-              <keywords>raster,import,wms</keywords><handler>OnImportWMS</handler>
-              <command>r.in.wms</command>
-            </menuitem>
             <separator />
             <separator />
             <menuitem>
             <menuitem>
               <label>Unpack raster map</label>
               <label>Unpack raster map</label>