[GRASS-SVN] r57738 - in grass/trunk/gui/wxpython: core gui_core mapdisp mapwin nviz

svn_grass at osgeo.org svn_grass at osgeo.org
Wed Sep 18 09:44:27 PDT 2013


Author: annakrat
Date: 2013-09-18 09:44:27 -0700 (Wed, 18 Sep 2013)
New Revision: 57738

Modified:
   grass/trunk/gui/wxpython/core/utils.py
   grass/trunk/gui/wxpython/gui_core/forms.py
   grass/trunk/gui/wxpython/mapdisp/frame.py
   grass/trunk/gui/wxpython/mapdisp/toolbars.py
   grass/trunk/gui/wxpython/mapwin/base.py
   grass/trunk/gui/wxpython/mapwin/buffered.py
   grass/trunk/gui/wxpython/mapwin/decorations.py
   grass/trunk/gui/wxpython/nviz/mapwindow.py
   grass/trunk/gui/wxpython/nviz/wxnviz.py
Log:
wxGUI: improve decorations handling (see #2080)

Modified: grass/trunk/gui/wxpython/core/utils.py
===================================================================
--- grass/trunk/gui/wxpython/core/utils.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/core/utils.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -1069,6 +1069,44 @@
         mainfn()
 
 
+def PilImageToWxImage(pilImage, copyAlpha = True):
+    """!Convert PIL image to wx.Image
+    
+    Based on http://wiki.wxpython.org/WorkingWithImages
+    """
+    import wx
+    hasAlpha = pilImage.mode[-1] == 'A'
+    if copyAlpha and hasAlpha :  # Make sure there is an alpha layer copy.
+        wxImage = wx.EmptyImage(*pilImage.size)
+        pilImageCopyRGBA = pilImage.copy()
+        pilImageCopyRGB = pilImageCopyRGBA.convert('RGB')    # RGBA --> RGB
+        pilImageRgbData = pilImageCopyRGB.tostring()
+        wxImage.SetData(pilImageRgbData)
+        wxImage.SetAlphaData(pilImageCopyRGBA.tostring()[3::4])  # Create layer and insert alpha values.
+
+    else :    # The resulting image will not have alpha.
+        wxImage = wx.EmptyImage(*pilImage.size)
+        pilImageCopy = pilImage.copy()
+        pilImageCopyRGB = pilImageCopy.convert('RGB')    # Discard any alpha from the PIL image.
+        pilImageRgbData = pilImageCopyRGB.tostring()
+        wxImage.SetData(pilImageRgbData)
+
+    return wxImage
+
+
+def autoCropImageFromFile(filename):
+    """!Loads image from file and crops it automatically.
+    
+    @param filename path to file
+    @return wx.Image instance
+    """
+    import Image
+    pilImage = Image.open(filename)
+    imageBox = pilImage.getbbox()
+    cropped = pilImage.crop(imageBox)
+    return PilImageToWxImage(cropped, copyAlpha=True)
+
+
 def isInRegion(regionA, regionB):
     """!Tests if 'regionA' is inside of 'regionB'.
 

Modified: grass/trunk/gui/wxpython/gui_core/forms.py
===================================================================
--- grass/trunk/gui/wxpython/gui_core/forms.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/gui_core/forms.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -748,7 +748,7 @@
                                           'MapWindow'):
             # display decorations and 
             # pressing OK or cancel after setting layer properties
-            if self.task.name in ['d.barscale','d.legend','d.histogram'] \
+            if self.task.name in ['d.barscale','d.legend', 'd.northarrow', 'd.histogram'] \
                 or len(self.parent.GetLayerInfo(self.layer, key = 'cmd')) >=  1:
                 self.Hide()
             # canceled layer with nothing set
@@ -1063,12 +1063,12 @@
                                 cb = ColorTablesComboBox(parent=which_panel, value=p.get('default',''),
                                                          size=globalvar.DIALOG_COMBOBOX_SIZE,
                                                          choices=valuelist)
-                            elif self.task.name == 'd.barscale' and p['name'] in ('style', 'north_arrow'):
-                                if p['name'] == 'style':
+                            elif p['name'] == 'style' and self.task.name in ('d.barscale', 'd.northarrow'):
+                                if self.task.name == 'd.barscale':
                                     cb = BarscalesComboBox(parent=which_panel, value=p.get('default',''),
                                                            size=globalvar.DIALOG_COMBOBOX_SIZE,
                                                            choices=valuelist)
-                                elif p['name'] == 'north_arrow':
+                                elif self.task.name == 'd.northarrow':
                                     cb = NArrowsComboBox(parent=which_panel, value=p.get('default',''),
                                                          size=globalvar.DIALOG_COMBOBOX_SIZE,
                                                          choices=valuelist)

Modified: grass/trunk/gui/wxpython/mapdisp/frame.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/frame.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/mapdisp/frame.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -50,14 +50,14 @@
 from mapwin.base import MapWindowProperties
 from gui_core.query     import QueryDialog, PrepareQueryResults
 from mapwin.buffered import BufferedMapWindow
-from mapwin.decorations import DecorationDialog, TextLayerDialog, \
-    LegendController, BarscaleController, \
-    DECOR_DIALOG_LEGEND, DECOR_DIALOG_BARSCALE
+from mapwin.decorations import TextLayerDialog, \
+    LegendController, BarscaleController, ArrowController
 from modules.histogram  import HistogramFrame
 from wxplot.histogram   import HistogramPlotFrame
 from wxplot.profile     import ProfileFrame
 from wxplot.scatter     import ScatterFrame
 from mapwin.analysis import ProfileController, MeasureDistanceController
+from gui_core.forms import GUI
 
 from mapdisp import statusbar as sb
 
@@ -160,10 +160,12 @@
 
         # init decoration objects
         self.decorations = {}
-        self.legend = LegendController(self.Map)
-        self.barscale = BarscaleController(self.Map)
+        self.legend = LegendController(self.Map, self._giface)
+        self.barscale = BarscaleController(self.Map, self._giface)
+        self.arrow = ArrowController(self.Map, self._giface)
         self.decorations[self.legend.id] = self.legend
         self.decorations[self.barscale.id] = self.barscale
+        self.decorations[self.arrow.id] = self.arrow
 
         self.mapWindowProperties.autoRenderChanged.connect(
             lambda value:
@@ -178,6 +180,10 @@
                                              overlays=self.decorations)
         self.MapWindow2D.mapQueried.connect(self.Query)
         self.MapWindow2D.overlayActivated.connect(self._activateOverlay)
+        self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
+        self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
+        for overlay in (self.legend, self.barscale, self.arrow):
+            overlay.overlayChanged.connect(lambda: self.MapWindow2D.UpdateMap(render=False, renderVector=False))
         self._setUpMapWindow(self.MapWindow2D)
 
         self.MapWindow2D.mouseHandlerUnregistered.connect(self.ResetPointer)
@@ -226,13 +232,9 @@
         self.dialogs = {}
         self.dialogs['attributes'] = None
         self.dialogs['category'] = None
-        self.dialogs['barscale'] = None
-        self.dialogs['legend'] = None
         self.dialogs['vnet'] = None
         self.dialogs['query'] = None
 
-        self.decorationDialog = None # decoration/overlays
-        
         self.measureDistController = None
 
     def GetMapWindow(self):
@@ -360,6 +362,8 @@
             self.MapWindow3D.ResetViewHistory()            
             self.MapWindow3D.UpdateView(None)
             self.MapWindow3D.overlayActivated.connect(self._activateOverlay)
+            self.MapWindow3D.overlayHidden.connect(self._hideOverlay)
+            self.legend.overlayChanged.connect(lambda: self.MapWindow3D.UpdateOverlays())
         else:
             self.MapWindow = self.MapWindow3D
             os.environ['GRASS_REGION'] = self.Map.SetRegion(windres = True, windres3 = True)
@@ -908,34 +912,50 @@
         win.Update()
 
     def _activateOverlay(self, overlayId):
-        """!Launch decoratio dialog according to overlay id.
+        """!Launch decoration dialog according to overlay id.
 
         @param overlayId id of overlay        
         """
         if overlayId > 100:
             self.OnAddText(None)
         elif overlayId == 0:
-            self.AddBarscale()
+            self.AddLegend(showDialog=True)
         elif overlayId == 1:
-            self.AddLegend()
+            self.AddBarscale(showDialog=True)
+        elif overlayId == 2:
+            self.AddArrow(showDialog=True)
 
-    def AddBarscale(self, cmd = None, showDialog = True):
-        """!Handler for scale/arrow map decoration menu selection.
+    def _hideOverlay(self, overlayId):
+        """!Hide overlay.
+
+        @param overlayId id of overlay        
         """
+        self.decorations[overlayId].Hide()
+
+    def AddBarscale(self, cmd=None, showDialog=None):
+        """!Handler for scale bar map decoration menu selection."""
+        if self.IsPaneShown('3d'):
+            self.MapWindow3D.SetDrawScalebar((70, 70))
+            return
+
+        if self.barscale.IsShown() and showDialog is None:
+            self.barscale.Hide()
+            return
+
         if cmd:
             self.barscale.cmd = cmd
 
         if not showDialog:
             self.barscale.Show()
-            self.MapWindow.UpdateMap()
             return
 
         # Decoration overlay control dialog
-        if self.dialogs['barscale']:
-            if self.dialogs['barscale'].IsShown():
-                self.dialogs['barscale'].SetFocus()
+        if self.barscale.dialog:
+            if self.barscale.dialog.IsShown():
+                self.barscale.dialog.SetFocus()
+                self.barscale.dialog.Raise()
             else:
-                self.dialogs['barscale'].Show()
+                self.barscale.dialog.Show()
         else:
             # If location is latlon, only display north arrow (scale won't work)
             #        proj = self.Map.projinfo['proj']
@@ -945,23 +965,17 @@
             #            barcmd = 'd.barscale'
 
             # decoration overlay control dialog
-            self.dialogs['barscale'] = \
-                DecorationDialog(parent=self, title=_('Scale and North arrow'),
-                                 giface=self._giface,
-                                 overlayController=self.barscale,
-                                 ddstyle=DECOR_DIALOG_BARSCALE,
-                                 size=(350, 200),
-                                 style=wx.DEFAULT_DIALOG_STYLE | wx.CENTRE)
+            GUI(parent=self, giface=self._giface, show=True,
+                modal=False).ParseCommand(self.barscale.cmd,
+                                          completed=(self.barscale.GetOptData, None, None))
 
-            self.dialogs['barscale'].CentreOnParent()
-            ### dialog cannot be show as modal - in the result d.barscale is not selectable
-            ### self.dialogs['barscale'].ShowModal()
-            self.dialogs['barscale'].Show()
         self.MapWindow.mouse['use'] = 'pointer'
 
-    def AddLegend(self, cmd = None, showDialog = True):
-        """!Handler for legend map decoration menu selection.
-        """
+    def AddLegend(self, cmd=None, showDialog=None):
+        """!Handler for legend map decoration menu selection."""
+        if self.legend.IsShown() and showDialog is None:
+            self.legend.Hide()
+            return
         if cmd:
             self.legend.cmd = cmd
         else:
@@ -971,32 +985,56 @@
                     self.legend.cmd.append('map=%s' % layer.maplayer.name)
                     break
 
-        if not showDialog:
+        if not showDialog and self.legend.CmdIsValid():
             self.legend.Show()
-            self.MapWindow.UpdateMap()
             return
 
         # Decoration overlay control dialog
-        if self.dialogs['legend']:
-            if self.dialogs['legend'].IsShown():
-                self.dialogs['legend'].SetFocus()
+        if self.legend.dialog:
+            if self.legend.dialog.IsShown():
+                self.legend.dialog.SetFocus()
+                self.legend.dialog.Raise()
             else:
-                self.dialogs['legend'].Show()
+                self.legend.dialog.Show()
         else:
-            self.dialogs['legend'] = \
-                DecorationDialog(parent=self, title=_("Legend"),
-                                 overlayController=self.legend,
-                                 giface=self._giface, 
-                                 ddstyle=DECOR_DIALOG_LEGEND,
-                                 size=(350, 200),
-                                 style=wx.DEFAULT_DIALOG_STYLE | wx.CENTRE)
+            GUI(parent=self, giface=self._giface, show=True,
+                modal=False).ParseCommand(self.legend.cmd,
+                                          completed=(self.legend.GetOptData, None, None))
 
-            self.dialogs['legend'].CentreOnParent() 
-            ### dialog cannot be show as modal - in the result d.legend is not selectable
-            ### self.dialogs['legend'].ShowModal()
-            self.dialogs['legend'].Show()
         self.MapWindow.mouse['use'] = 'pointer'
-        
+
+    def AddArrow(self, cmd=None, showDialog=None):
+        """!Handler for north arrow menu selection."""
+        if self.IsPaneShown('3d'):
+            # here was opening of appearance page of nviz notebook
+            # but now moved to MapWindow3D where are other problematic nviz calls
+            self.MapWindow3D.SetDrawArrow((70, 70))
+            return
+
+        if self.arrow.IsShown() and showDialog is None:
+            self.arrow.Hide()
+            return
+        if cmd:
+            self.arrow.cmd = cmd
+
+        if not showDialog:
+            self.arrow.Show()
+            return
+
+        # Decoration overlay control dialog
+        if self.arrow.dialog:
+            if self.arrow.dialog.IsShown():
+                self.arrow.dialog.SetFocus()
+                self.arrow.dialog.Raise()
+            else:
+                self.arrow.dialog.Show()
+        else:
+            GUI(parent=self, giface=self._giface, show=True,
+                modal=False).ParseCommand(self.arrow.cmd,
+                                          completed=(self.arrow.GetOptData, None, None))
+
+        self.MapWindow.mouse['use'] = 'pointer'
+
     def OnAddText(self, event):
         """!Handler for text decoration menu selection.
         """
@@ -1047,13 +1085,6 @@
                 self.MapWindow2D.UpdateMap(render = False, renderVector = False)
             
         self.MapWindow.mouse['use'] = 'pointer'
-    
-    def OnAddArrow(self, event):
-        """!Handler for north arrow menu selection.
-        """
-        # here was opening of appearance page of nviz notebook
-        # but now moved to MapWindow3D where are other problematic nviz calls
-        self.MapWindow3D.SetDrawArrow((70, 70))
         
     def GetOptData(self, dcmd, type, params, propwin):
         """!Callback method for decoration overlay command generated by

Modified: grass/trunk/gui/wxpython/mapdisp/toolbars.py
===================================================================
--- grass/trunk/gui/wxpython/mapdisp/toolbars.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/mapdisp/toolbars.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -29,11 +29,11 @@
                             label = _('Query raster/vector map(s)'),
                             desc = _('Query selected raster/vector map(s)')),
     'addBarscale': MetaIcon(img = 'scalebar-add',
-                            label = _('Add scalebar and north arrow')),
+                            label = _('Show/hide scale bar')),
     'addLegend'  : MetaIcon(img = 'legend-add',
-                            label = _('Add legend')),
+                            label = _('Show/hide legend')),
     'addNorthArrow': MetaIcon(img = 'north-arrow-add',
-                              label = _('North Arrow')),
+                              label = _('Show/hide north arrow')),
     'analyze'    : MetaIcon(img = 'layer-raster-analyze',
                             label = _('Analyze map'),
                             desc = _('Measuring, profiling, histogramming, ...')),
@@ -244,14 +244,11 @@
     def OnDecoration(self, event):
         """!Decorations overlay menu
         """
-        if self.parent.IsPaneShown('3d'):
-            self._onMenu(((MapIcons["addNorthArrow"], self.parent.OnAddArrow),
-                          (MapIcons["addLegend"],     lambda evt: self.parent.AddLegend()),
-                          (MapIcons["addText"],       self.parent.OnAddText)))
-        else:
-            self._onMenu(((MapIcons["addBarscale"], lambda evt: self.parent.AddBarscale()),
-                          (MapIcons["addLegend"],   lambda evt: self.parent.AddLegend()),
-                          (MapIcons["addText"],     self.parent.OnAddText)))
+        self._onMenu(((MapIcons["addLegend"], lambda evt: self.parent.AddLegend()),
+                      (MapIcons["addBarscale"], lambda evt: self.parent.AddBarscale()),
+                      (MapIcons["addNorthArrow"], lambda evt: self.parent.AddArrow()),
+                      (MapIcons["addText"], self.parent.OnAddText)))
+
         
     def ExitToolbars(self):
         if self.parent.GetToolbar('vdigit'):

Modified: grass/trunk/gui/wxpython/mapwin/base.py
===================================================================
--- grass/trunk/gui/wxpython/mapwin/base.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/mapwin/base.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -115,6 +115,8 @@
         self.mouseHandlerUnregistered = Signal('MapWindow.mouseHandlerUnregistered')
         # emitted after double click in pointer mode on legend, text, scalebar
         self.overlayActivated = Signal('MapWindow.overlayActivated')
+        # emitted when overlay should be hidden
+        self.overlayHidden = Signal('MapWindow.overlayHidden')
 
         # mouse attributes -- position on the screen, begin and end of
         # dragging, and type of drawing

Modified: grass/trunk/gui/wxpython/mapwin/buffered.py
===================================================================
--- grass/trunk/gui/wxpython/mapwin/buffered.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/mapwin/buffered.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -134,6 +134,7 @@
         self.img     = None   # wx.Image object (self.mapfile)
         # decoration overlays
         self.overlays = overlays
+        self._overlayNames = {0:  _("legend"), 1: _("scale bar"), 2: _("north arrow")}
         # images and their PseudoDC ID's for painting and dragging
         self.imagedict = {}   
         self.select = {}      # selecting/unselecting decorations for dragging
@@ -192,14 +193,30 @@
             event.Skip()
             return
 
+        # generate popup-menu
+        menu = wx.Menu()
+
         if not hasattr(self, "popupCopyCoordinates"):
             self.popupCopyCoordinates = wx.NewId()
             self.Bind(wx.EVT_MENU, self.OnCopyCoordinates, id = self.popupCopyCoordinates)
-
-        # generate popup-menu
-        menu = wx.Menu()
         menu.Append(self.popupCopyCoordinates, _("Copy coordinates to clipboard"))
 
+        pos = self.ScreenToClient(event.GetPosition())
+        idlist = self.pdc.FindObjects(pos[0], pos[1], self.hitradius)
+        if idlist and idlist[0] in (0, 1, 2):  # legend, scale bar, north arrow
+            self._hide = wx.NewId()
+            self.Bind(wx.EVT_MENU,
+                      lambda evt: self.overlayHidden.emit(overlayId=idlist[0]),
+                      id=self._hide)
+            menu.Append(self._hide,
+                        _("Hide {overlay}").format(overlay=self._overlayNames[idlist[0]]))
+
+            if idlist[0] == 0:
+                self._resizeLegend = wx.NewId()
+                self.Bind(wx.EVT_MENU,
+                      lambda evt: self.overlays[idlist[0]].StartResizing(),
+                      id=self._resizeLegend)
+                menu.Append(self._resizeLegend, _("Resize legend"))
         self.PopupMenu(menu)
         menu.Destroy()
 
@@ -581,7 +598,7 @@
         for overlay in self.Map.GetListOfLayers(ltype = "overlay", active = True):
             if overlay.mapfile is not None \
                and os.path.isfile(overlay.mapfile) and os.path.getsize(overlay.mapfile):
-                img = wx.Image(overlay.mapfile, wx.BITMAP_TYPE_ANY)
+                img = utils.autoCropImageFromFile(overlay.mapfile)
 
                 for key in self.imagedict.keys():
                     if self.imagedict[key]['id'] == overlay.id:

Modified: grass/trunk/gui/wxpython/mapwin/decorations.py
===================================================================
--- grass/trunk/gui/wxpython/mapwin/decorations.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/mapwin/decorations.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -6,8 +6,8 @@
 Classes:
  - decorations::OverlayController
  - decorations::BarscaleController
+ - decorations::ArrowController
  - decorations::LegendController
- - decorations::DecorationDialog
  - decorations::TextLayerDialog
 
 (C) 2006-2013 by the GRASS Development Team
@@ -19,27 +19,31 @@
 """
 
 import wx
-from core.utils import GetLayerNameFromCmd, _
-from gui_core.forms import GUI
+from core.utils import _
 
-from grass.pydispatch.errors import DispatcherKeyError
+from grass.pydispatch.signal import Signal
 
 
 class OverlayController(object):
 
     """!Base class for decorations (barscale, legend) controller."""
 
-    def __init__(self, renderer):
+    def __init__(self, renderer, giface):
+        self._giface = giface
         self._renderer = renderer
         self._overlay = None
-        self._coords = [0, 0]
+        self._coords = None
         self._pdcType = 'image'
         self._propwin = None
         self._defaultAt = ''
         self._cmd = None   # to be set by user
         self._name = None  # to be defined by subclass
         self._id = None    # to be defined by subclass
+        self._dialog = None
 
+        # signals that overlay or its visibility changed
+        self.overlayChanged = Signal('OverlayController::overlayChanged')
+
     def SetCmd(self, cmd):
         hasAt = False
         for i in cmd:
@@ -59,6 +63,9 @@
         self._coords = list(coords)
 
     def GetCoords(self):
+        if self._coords is None:  # initial position
+            x, y = self.GetPlacement((self._renderer.width, self._renderer.height))
+            self._coords = [x, y]
         return self._coords
 
     coords = property(fset=SetCoords, fget=GetCoords)
@@ -91,6 +98,14 @@
 
     layer = property(fget=GetLayer)
 
+    def GetDialog(self):
+        return self._dialog
+
+    def SetDialog(self, win):
+        self._dialog = win
+
+    dialog = property(fget=GetDialog, fset=SetDialog)
+
     def IsShown(self):
         if self._overlay and self._overlay.IsActive():
             return True
@@ -106,10 +121,29 @@
         else:
             self.Hide()
 
+        self.overlayChanged.emit()
+
     def Hide(self):
         if self._overlay:
             self._overlay.SetActive(False)
+        self.overlayChanged.emit()
 
+    def GetOptData(self, dcmd, layer, params, propwin):
+        """!Called after options are set through module dialog.
+
+        @param dcmd resulting command
+        @param layer not used
+        @param params module parameters (not used)
+        @param propwin dialog window
+        """
+        if not dcmd:
+            return
+
+        self._cmd = dcmd
+        self._dialog = propwin
+
+        self.Show()
+
     def _add(self):
         self._overlay = self._renderer.AddOverlay(id=self._id, ltype=self._name,
                                                   command=self.cmd, active=False,
@@ -120,27 +154,74 @@
         self._renderer.ChangeOverlay(id=self._id, command=self._cmd,
                                      render=False)
 
+    def CmdIsValid(self):
+        """!If command is valid"""
+        return True
 
+    def GetPlacement(self, screensize):
+        """!Get coordinates where to place overlay in a reasonable way
+
+        @param screensize sreen size
+        """
+        for param in self._cmd:
+            if not param.startswith('at'):
+                continue
+            x, y = [float(number) for number in param.split('=')[1].split(',')]
+            x = int((x / 100.) * screensize[0])
+            y = int((1 - y / 100.) * screensize[1])
+
+            return x, y
+
+
 class BarscaleController(OverlayController):
 
-    def __init__(self, renderer):
-        OverlayController.__init__(self, renderer)
-        self._id = 0
+    def __init__(self, renderer, giface):
+        OverlayController.__init__(self, renderer, giface)
+        self._id = 1
         self._name = 'barscale'
-        self._defaultAt = 'at=0,95'
+        # different from default because the reference point is not in the middle
+        self._defaultAt = 'at=0,98'
         self._cmd = ['d.barscale', self._defaultAt]
 
 
+class ArrowController(OverlayController):
+
+    def __init__(self, renderer, giface):
+        OverlayController.__init__(self, renderer, giface)
+        self._id = 2
+        self._name = 'arrow'
+        # different from default because the reference point is not in the middle
+        self._defaultAt = 'at=85.0,25.0'
+        self._cmd = ['d.northarrow', self._defaultAt]
+
+
 class LegendController(OverlayController):
 
-    def __init__(self, renderer):
-        OverlayController.__init__(self, renderer)
-        self._id = 1
+    def __init__(self, renderer, giface):
+        OverlayController.__init__(self, renderer, giface)
+        self._id = 0
         self._name = 'legend'
         # TODO: synchronize with d.legend?
         self._defaultAt = 'at=5,50,2,5'
         self._cmd = ['d.legend', self._defaultAt]
 
+    def GetPlacement(self, screensize):
+        for param in self._cmd:
+            if not param.startswith('at'):
+                continue
+            b, t, l, r = [float(number) for number in param.split('=')[1].split(',')]  # pylint: disable-msg=W0612
+            x = int((l / 100.) * screensize[0])
+            y = int((1 - t / 100.) * screensize[1])
+
+            return x, y
+
+    def CmdIsValid(self):
+        for param in self._cmd:
+            param = param.split('=')
+            if param[0] == 'map' and len(param) == 2:
+                return True
+        return False
+
     def ResizeLegend(self, begin, end, screenSize):
         """!Resize legend according to given bbox coordinates."""
         w = abs(begin[0] - end[0])
@@ -165,213 +246,29 @@
                 self._cmd[i] = atStr
                 break
 
-        self._coords = [0, 0]
+        self._coords = None
         self.Show()
 
-
-DECOR_DIALOG_LEGEND = 0
-DECOR_DIALOG_BARSCALE = 1
-
-
-class DecorationDialog(wx.Dialog):
-
-    """!Controls setting options and displaying/hiding map overlay
-    decorations
-    """
-
-    def __init__(self, parent, title, giface, overlayController,
-                 ddstyle, **kwargs):
-
-        wx.Dialog.__init__(self, parent, wx.ID_ANY, title, **kwargs)
-
-        self.parent = parent  # MapFrame
-        self._overlay = overlayController
-        self._ddstyle = ddstyle
-        self._giface = giface
-
-        sizer = wx.BoxSizer(wx.VERTICAL)
-
-        box = wx.BoxSizer(wx.HORIZONTAL)
-        self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY)
-        self.chkbox.SetValue(True)
-
-        if self._ddstyle == DECOR_DIALOG_LEGEND:
-            self.chkbox.SetLabel("Show legend")
-        else:
-            self.chkbox.SetLabel("Show scale and North arrow")
-
-        box.Add(item=self.chkbox, proportion=0,
-                flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
-        sizer.Add(item=box, proportion=0,
-                  flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-
-        box = wx.BoxSizer(wx.HORIZONTAL)
-        optnbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set options"))
-        box.Add(item=optnbtn, proportion=0, flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
-        sizer.Add(item=box, proportion=0,
-                  flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-        if self._ddstyle == DECOR_DIALOG_LEGEND:
-            box = wx.BoxSizer(wx.HORIZONTAL)
-            self.resizeBtn = wx.ToggleButton(
-                parent=self, id=wx.ID_ANY, label=_("Set size and position"))
-            self.resizeBtn.SetToolTipString(_("Click and drag on the map display to set legend "
-                                              "size and position and then press OK"))
-            self.resizeBtn.Disable()
-            toolSwitcher = self._giface.GetMapDisplay().GetToolSwitcher()
-            toolSwitcher.AddCustomToolToGroup(group='mouseUse', btnId=self.resizeBtn.GetId(),
-                                              toggleHandler=self.resizeBtn.SetValue)
-            toolSwitcher.toggleToolChanged.connect(self._toolChanged)
-            self.resizeBtn.Bind(wx.EVT_TOGGLEBUTTON, lambda evt: toolSwitcher.ToolChanged(evt.GetId()))
-
-            box.Add(item=self.resizeBtn, proportion=0,
-                    flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
-            sizer.Add(item=box, proportion=0,
-                      flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-
-        box = wx.BoxSizer(wx.HORIZONTAL)
-        if self._ddstyle == DECOR_DIALOG_LEGEND:
-            labelText = _("Drag legend object with mouse in pointer mode to position.\n"
-                          "Double-click to change options.\n"
-                          "Define raster map name for legend in properties dialog.")
-        else:
-            labelText = _("Drag scale object with mouse in pointer mode to position.\n"
-                          "Double-click to change options.")
-
-        label = wx.StaticText(parent=self, id=wx.ID_ANY,
-                              label=labelText)
-
-        box.Add(item=label, proportion=0,
-                flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
-        sizer.Add(item=box, proportion=0,
-                  flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-
-        line = wx.StaticLine(
-            parent=self, id=wx.ID_ANY, size=(20, -1), style = wx.LI_HORIZONTAL)
-        sizer.Add(item=line, proportion=0,
-                  flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-
-        # buttons
-        btnsizer = wx.StdDialogButtonSizer()
-
-        self.btnOK = wx.Button(parent=self, id=wx.ID_OK)
-        self.btnOK.SetDefault()
-        self.btnOK.Enable(self._ddstyle != DECOR_DIALOG_LEGEND)
-        btnsizer.AddButton(self.btnOK)
-
-        btnCancel = wx.Button(parent=self, id=wx.ID_CANCEL)
-        btnsizer.AddButton(btnCancel)
-        btnsizer.Realize()
-
-        sizer.Add(item=btnsizer, proportion=0,
-                  flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
-
-        #
-        # bindings
-        #
-        optnbtn.Bind(wx.EVT_BUTTON, self.OnOptions)
-        btnCancel.Bind(wx.EVT_BUTTON, lambda evt: self.CloseDialog())
-        self.btnOK.Bind(wx.EVT_BUTTON, self.OnOK)
-
-        self.SetSizer(sizer)
-        sizer.Fit(self)
-
-        mapName, found = GetLayerNameFromCmd(self._overlay.cmd)
-        if found:
-            # enable 'OK' and 'Resize' button
-            self.btnOK.Enable()
-
-            # set title
-            self.SetTitle(_('Legend of raster map <%s>') %
-                          mapName)
-
-    def OnOptions(self, event):
-        """!Sets option for decoration map overlays
-        """
-        if self._overlay.propwin is None:
-            # build properties dialog
-            GUI(parent=self.parent).ParseCommand(cmd=self._overlay.cmd,
-                                                 completed=(self.GetOptData, self._overlay.name, ''))
-
-        else:
-            if self._overlay.propwin.IsShown():
-                self._overlay.propwin.SetFocus()
-            else:
-                self._overlay.propwin.Show()
-
-    def _toolChanged(self, id):
+    def StartResizing(self):
         """!Tool in toolbar or button itself were pressed"""
-        if id == self.resizeBtn.GetId():
-            if self.resizeBtn.GetValue():
-                # prepare for resizing
-                window = self._giface.GetMapWindow()
-                window.SetNamedCursor('cross')
-                window.mouse['use'] = None
-                window.mouse['box'] = 'box'
-                window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH)
-                window.mouseLeftUp.connect(self._resizeLegend)
-            else:
-                # stop resizing mode
-                self.DisconnectResizing()
-                self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
-        else:
-            # any other tool was pressed -> stop resizing mode
-            self.DisconnectResizing()
+        # prepare for resizing
+        window = self._giface.GetMapWindow()
+        window.SetNamedCursor('cross')
+        window.mouse['use'] = None
+        window.mouse['box'] = 'box'
+        window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH)
+        window.mouseLeftUp.connect(self._finishResizing)
 
-    def DisconnectResizing(self):
-        if not self.parent.IsPaneShown('3d'):
-            try:            
-                self._giface.GetMapWindow().mouseLeftUp.disconnect(self._resizeLegend)
-            except DispatcherKeyError:
-                pass
-
-    def _resizeLegend(self, x, y):
-        """!Update legend after drawing new legend size (moved from BufferedWindow)"""
-        self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
-        self.DisconnectResizing()
-        # resize legend
+    def _finishResizing(self):
         window = self._giface.GetMapWindow()
+        window.mouseLeftUp.disconnect(self._finishResizing)
         screenSize = window.GetClientSizeTuple()
-        self._overlay.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
+        self.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
+        self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
         # redraw
-        self._giface.updateMap.emit()
+        self.overlayChanged.emit()
 
-    def CloseDialog(self):
-        """!Hide dialog"""
-        if self._ddstyle == DECOR_DIALOG_LEGEND and self.resizeBtn.GetValue():
-            self.DisconnectResizing()
 
-        self.Hide()
-
-    def OnOK(self, event):
-        """!Button 'OK' pressed"""
-        # enable or disable overlay
-        self._overlay.Show(self.chkbox.IsChecked())
-
-        # update map
-        if self.parent.IsPaneShown('3d'):
-            self.parent.MapWindow.UpdateOverlays()
-
-        self._giface.updateMap.emit()
-
-        # hide dialog
-        self.CloseDialog()
-
-    def GetOptData(self, dcmd, layer, params, propwin):
-        """!Process decoration layer data"""
-        if dcmd:
-            self._overlay.cmd = dcmd
-        self._overlay.propwin = propwin
-        if params:
-            self.btnOK.Enable()
-            if self._ddstyle == DECOR_DIALOG_LEGEND and not self.parent.IsPaneShown('3d'):
-                self.resizeBtn.Enable()
-
-    def Show(self, show=True):
-        if show and self._ddstyle == DECOR_DIALOG_LEGEND:
-            self.resizeBtn.Enable(not self.parent.IsPaneShown('3d'))
-        wx.Dialog.Show(self, show)
-
-
 class TextLayerDialog(wx.Dialog):
 
     """
@@ -440,7 +337,7 @@
                 flag=wx.ALIGN_CENTER_VERTICAL,
                 pos=(2, 0))
         self.rotation = wx.SpinCtrl(parent=self, id=wx.ID_ANY, value="", pos=(30, 50),
-                                    size = (75, -1), style = wx.SP_ARROW_KEYS)
+                                    size=(75, -1), style=wx.SP_ARROW_KEYS)
         self.rotation.SetRange(-360, 360)
         self.rotation.SetValue(int(self.currRot))
         box.Add(item=self.rotation,
@@ -467,7 +364,7 @@
                        flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER | wx.ALL, border=5)
 
         line = wx.StaticLine(parent=self, id=wx.ID_ANY,
-                             size=(20, -1), style = wx.LI_HORIZONTAL)
+                             size=(20, -1), style=wx.LI_HORIZONTAL)
         self.sizer.Add(item=line, proportion=0,
                        flag=wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border=5)
 

Modified: grass/trunk/gui/wxpython/nviz/mapwindow.py
===================================================================
--- grass/trunk/gui/wxpython/nviz/mapwindow.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/nviz/mapwindow.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -443,8 +443,8 @@
     def GetLegendRect(self):
         """!Estimates legend size for dragging"""
         size = None
-        if 1 in self.overlays:
-            for param in self.overlays[1].cmd[1:]:
+        if 0 in self.overlays:
+            for param in self.overlays[0].cmd[1:]:
                 if param.startswith("at="):
                     size = map(int, param.split("=")[-1].split(','))
                     break
@@ -494,7 +494,7 @@
             Updates self.imagelist"""
         self.Map.ChangeMapSize(self.GetClientSize())
         self.Map.RenderOverlays(force = True)
-        
+
         # delete textures
         for texture in self.imagelist:
             # inactive overlays, remove text labels
@@ -509,7 +509,7 @@
                     
         # update images (only legend so far)
         for oid, overlay in self.overlays.iteritems():
-            if not overlay.IsShown() or oid == 0: # 0 for barscale
+            if not overlay.IsShown() or oid in (1, 2): # 0 for barscale
                 continue
             if oid not in [t.GetId() for t in self.imagelist]: # new
                 self.CreateTexture(overlay = overlay.layer)
@@ -519,10 +519,7 @@
                         if not t.Corresponds(overlay):
                             self.imagelist.remove(t)
                             t = self.CreateTexture(overlay = overlay.layer)
-                        # always set coordinates, needed for synchr. 2D and 3D modes
-                        t.SetCoords(overlay.coords)
 
-                    
         # update text labels
         for textId in self.textdict.keys():
             if textId not in [t.GetId() for t in self.imagelist]:# new
@@ -536,6 +533,7 @@
                             t = self.CreateTexture(textId = textId)
                         # always set coordinates, needed for synchr. 2D and 3D modes
                         t.SetCoords(self.textdict[textId]['coords'])
+        self.Refresh()
             
     def CreateTexture(self, overlay = None, textId = None):
         """!Create texture from overlay image or from textdict"""
@@ -543,7 +541,7 @@
             texture = wxnviz.ImageTexture(filepath = overlay.mapfile, overlayId = overlay.id,
                                           coords = list(self.overlays[overlay.id].coords),
                                           cmd = overlay.GetCmd())
-            if overlay.id == 1: # legend
+            if overlay.id == 0: # legend
                 texture.SetBounds(self.GetLegendRect())
         else: # text
             coords, bbox, relCoords = self.TextBounds(self.textdict[textId])
@@ -741,8 +739,7 @@
         if self.mouse['use'] == 'pointer':
             # get decoration or text id
             self.dragid = self.FindObjects(self.mouse['tmp'][0], self.mouse['tmp'][1],
-                                          self.hitradius)   
-                
+                                           self.hitradius)
         if self.mouse['use'] == 'fly':
             if not self.timerFly.IsRunning():
                 self.timerFly.Start(self.fly['interval'])
@@ -753,7 +750,7 @@
     def OnDragging(self, event):
                 
         if self.mouse['use'] == 'pointer':
-            if self.dragid > 0:
+            if self.dragid >= 0:
                 self.DragItem(self.dragid, event.GetPositionTuple())
             
         if self.mouse['use'] == 'rotate':    
@@ -844,7 +841,7 @@
             
 
         elif self.mouse['use'] == 'pointer':
-            if self.dragid > 0:
+            if self.dragid >= 0:
                 dx = self.mouse['end'][0] - self.mouse['begin'][0]
                 dy = self.mouse['end'][1] - self.mouse['begin'][1]
                 if self.dragid < 99:
@@ -964,7 +961,8 @@
     def DragItem(self, id, coords):
         """!Drag an overlay decoration item
         """
-        if not id: return
+        if id is None:
+            return
         Debug.msg (5, "GLWindow.DragItem(): id=%d" % id)
         x, y = self.mouse['tmp']
         dx = coords[0] - x
@@ -1268,6 +1266,7 @@
             self.decoration['scalebar'][-1]['position']['x'] = pos[0]
             self.decoration['scalebar'][-1]['position']['y'] = pos[1]
             self.Refresh(False)
+        self.lmgr.nviz.SetPage('decoration')
         
     def IsLoaded(self, item):
         """!Check if layer (item) is already loaded

Modified: grass/trunk/gui/wxpython/nviz/wxnviz.py
===================================================================
--- grass/trunk/gui/wxpython/nviz/wxnviz.py	2013-09-18 15:58:49 UTC (rev 57737)
+++ grass/trunk/gui/wxpython/nviz/wxnviz.py	2013-09-18 16:44:27 UTC (rev 57738)
@@ -1998,7 +1998,7 @@
         self.width = self.image.GetWidth()
         self.height = self.image.GetHeight()
         self.id = overlayId
-        self.coords = list(coords)
+        self.coords = [0, 0]
         self.bounds = wx.Rect()
         self.active = True
         



More information about the grass-commit mailing list