1"""
2@package mapdisp.frame
3
4@brief Map display with toolbar for various display management
5functions, and additional toolbars (vector digitizer, 3d view).
6
7Can be used either from Layer Manager or as d.mon backend.
8
9Classes:
10 - mapdisp::MapFrame
11
12(C) 2006-2016 by the GRASS Development Team
13
14This program is free software under the GNU General Public License
15(>=v2). Read the file COPYING that comes with GRASS for details.
16
17@author Michael Barton
18@author Jachym Cepicky
19@author Martin Landa <landa.martin gmail.com>
20@author Vaclav Petras <wenzeslaus gmail.com> (SingleMapFrame, handlers support)
21@author Anna Kratochvilova <kratochanna gmail.com> (SingleMapFrame)
22@author Stepan Turek <stepan.turek seznam.cz> (handlers support)
23"""
24
25import os
26import sys
27import copy
28
29from core import globalvar
30import wx
31import wx.aui
32
33from mapdisp.toolbars import MapToolbar, NvizIcons
34from mapdisp.gprint import PrintOptions
35from core.gcmd import GError, GMessage, RunCommand
36from core.utils import ListOfCatsToRange, GetLayerNameFromCmd
37from gui_core.dialogs import GetImageHandlers, ImageSizeDialog
38from core.debug import Debug
39from core.settings import UserSettings
40from gui_core.mapdisp import SingleMapFrame
41from mapwin.base import MapWindowProperties
42from gui_core.query import QueryDialog, PrepareQueryResults
43from mapwin.buffered import BufferedMapWindow
44from mapwin.decorations import LegendController, BarscaleController, \
45    ArrowController, DtextController, LegendVectController
46from mapwin.analysis import ProfileController, MeasureDistanceController, \
47    MeasureAreaController
48from gui_core.forms import GUI
49from core.giface import Notification
50from gui_core.vselect import VectorSelectBase, VectorSelectHighlighter
51from gui_core.wrap import Menu
52from mapdisp import statusbar as sb
53
54import grass.script as grass
55
56from grass.pydispatch.signal import Signal
57
58
59class MapFrame(SingleMapFrame):
60    """Main frame for map display window. Drawing takes place in
61    child double buffered drawing window.
62    """
63
64    def __init__(self, parent, giface, title=_("GRASS GIS - Map display"),
65                 toolbars=["map"], statusbar=True,
66                 tree=None, notebook=None, lmgr=None,
67                 page=None, Map=None, auimgr=None, name='MapWindow', **kwargs):
68        """Main map display window with toolbars, statusbar and
69        2D map window, 3D map window and digitizer.
70
71        :param toolbars: array of activated toolbars, e.g. ['map', 'digit']
72        :param statusbar: True to add statusbar
73        :param tree: reference to layer tree
74        :param notebook: control book ID in Layer Manager
75        :param lmgr: Layer Manager
76        :param page: notebook page with layer tree
77        :param map: instance of render.Map
78        :param auimgr: AUI manager
79        :param name: frame name
80        :param kwargs: wx.Frame attributes
81        """
82        SingleMapFrame.__init__(self, parent=parent, title=title,
83                                Map=Map, auimgr=auimgr, name=name, **kwargs)
84
85        self._giface = giface
86        # Layer Manager object
87        # need by GLWindow (a lot), VDigitWindow (a little bit)
88        self._layerManager = lmgr
89        # Layer Manager layer tree object
90        # used for VDigit toolbar and window and GLWindow
91        self.tree = tree
92        # Notebook page holding the layer tree
93        # used only in OnCloseWindow
94        self.page = page
95        # Layer Manager layer tree notebook
96        # used only in OnCloseWindow
97        self.layerbook = notebook
98
99        # Emitted when starting (switching to) 3D mode.
100        # Parameter firstTime specifies if 3D was already actived.
101        self.starting3dMode = Signal("MapFrame.starting3dMode")
102
103        # Emitted when ending (switching from) 3D mode.
104        self.ending3dMode = Signal("MapFrame.ending3dMode")
105
106        # Emitted when closing display by closing its window.
107        self.closingDisplay = Signal("MapFrame.closingDisplay")
108
109        # Emitted when closing display by closing its window.
110        self.closingVNETDialog = Signal("MapFrame.closingVNETDialog")
111
112        # properties are shared in other objects, so defining here
113        self.mapWindowProperties = MapWindowProperties()
114        self.mapWindowProperties.setValuesFromUserSettings()
115
116        #
117        # Add toolbars
118        #
119        for toolb in toolbars:
120            self.AddToolbar(toolb)
121
122        #
123        # Add statusbar
124        #
125        self.statusbarManager = None
126        if statusbar:
127            self.CreateStatusbar()
128
129        # init decoration objects
130        self.decorations = {}
131        self._decorationWindows = {}
132
133        self.mapWindowProperties.autoRenderChanged.connect(
134            lambda value:
135            self.OnRender(None) if value else None)
136
137        #
138        # Init map display (buffered DC & set default cursor)
139        #
140        self.MapWindow2D = BufferedMapWindow(
141            self, giface=self._giface, Map=self.Map,
142            properties=self.mapWindowProperties, overlays=self.decorations)
143        self.MapWindow2D.mapQueried.connect(self.Query)
144        self.MapWindow2D.overlayActivated.connect(self._activateOverlay)
145        self.MapWindow2D.overlayRemoved.connect(self.RemoveOverlay)
146        self._setUpMapWindow(self.MapWindow2D)
147
148        self.MapWindow2D.mouseHandlerUnregistered.connect(self.ResetPointer)
149
150        self.MapWindow2D.InitZoomHistory()
151        self.MapWindow2D.zoomChanged.connect(self.StatusbarUpdate)
152
153        self._giface.updateMap.connect(self.MapWindow2D.UpdateMap)
154        # default is 2D display mode
155        self.MapWindow = self.MapWindow2D
156        self.MapWindow.SetNamedCursor('default')
157        # used by vector digitizer
158        self.MapWindowVDigit = None
159        # used by Nviz (3D display mode)
160        self.MapWindow3D = None
161
162        if 'map' in self.toolbars:
163            self.toolbars['map'].SelectDefault()
164
165        #
166        # Bind various events
167        #
168        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
169        self.Bind(wx.EVT_SIZE, self.OnSize)
170
171        #
172        # Update fancy gui style
173        #
174        self._mgr.AddPane(self.MapWindow, wx.aui.AuiPaneInfo().CentrePane().
175                          Dockable(False).BestSize((-1, -1)).Name('2d').
176                          CloseButton(False).DestroyOnClose(True).
177                          Layer(0))
178        self._mgr.Update()
179
180        #
181        # Init print module and classes
182        #
183        self.printopt = PrintOptions(self, self.MapWindow)
184
185        #
186        # Re-use dialogs
187        #
188        self.dialogs = {}
189        self.dialogs['attributes'] = None
190        self.dialogs['category'] = None
191        self.dialogs['vnet'] = None
192        self.dialogs['query'] = None
193        self.dialogs['vselect'] = None
194
195        # initialize layers to query (d.what.vect/rast)
196        self._vectQueryLayers = []
197        self._rastQueryLayers = []
198        # initialize highlighter for vector features
199        self._highlighter_layer = None
200
201        self.measureController = None
202
203        self._resize()
204
205    def CreateStatusbar(self):
206        if self.statusbarManager:
207            return
208
209        # items for choice
210        self.statusbarItems = [sb.SbCoordinates,
211                               sb.SbRegionExtent,
212                               sb.SbCompRegionExtent,
213                               sb.SbShowRegion,
214                               sb.SbAlignExtent,
215                               sb.SbResolution,
216                               sb.SbDisplayGeometry,
217                               sb.SbMapScale,
218                               sb.SbGoTo,
219                               sb.SbProjection]
220
221        self.statusbarItemsHiddenInNviz = (sb.SbAlignExtent,
222                                           sb.SbDisplayGeometry,
223                                           sb.SbShowRegion,
224                                           sb.SbResolution,
225                                           sb.SbMapScale)
226
227        # create statusbar and its manager
228        statusbar = self.CreateStatusBar(number=4, style=0)
229        statusbar.SetMinHeight(24)
230        statusbar.SetStatusWidths([-5, -2, -1, -1])
231        self.statusbarManager = sb.SbManager(
232            mapframe=self, statusbar=statusbar)
233
234        # fill statusbar manager
235        self.statusbarManager.AddStatusbarItemsByClass(
236            self.statusbarItems, mapframe=self, statusbar=statusbar)
237        self.statusbarManager.AddStatusbarItem(
238            sb.SbMask(self, statusbar=statusbar, position=2))
239        sbRender = sb.SbRender(self, statusbar=statusbar, position=3)
240        self.statusbarManager.AddStatusbarItem(sbRender)
241
242        self.statusbarManager.Update()
243
244        #
245        self.Map.GetRenderMgr().updateProgress.connect(self.statusbarManager.SetProgress)
246        self.Map.GetRenderMgr().renderingFailed.connect(lambda cmd, error: self._giface.WriteError(
247            _("Failed to run command '%(command)s'. Details:\n%(error)s") % dict(command=' '.join(cmd), error=error)))
248
249    def GetMapWindow(self):
250        return self.MapWindow
251
252    def SetTitleWithName(self, name):
253        """Set map display title its name
254
255        This function should be used when there are multiple map
256        displays.
257
258        Sets also other dynamically determined parts of the title
259        specific for GRASS GIS map display,
260        while the standard (inherited) ``SetTitle()`` function sets the
261        raw title and doesn't add or modify anything.
262        """
263        gisenv = grass.gisenv()
264        title = _("GRASS GIS Map Display: %(name)s - %(loc)s/%(mapset)s") % {
265            'name': name,
266            'loc': gisenv["LOCATION_NAME"],
267            'mapset': gisenv["MAPSET"]}
268
269        self.SetTitle(title)
270
271    def _addToolbarVDigit(self):
272        """Add vector digitizer toolbar
273        """
274        from vdigit.main import haveVDigit, VDigit
275        from vdigit.toolbars import VDigitToolbar
276
277        if not haveVDigit:
278            from vdigit import errorMsg
279
280            self.toolbars['map'].combo.SetValue(_("2D view"))
281
282            GError(_("Unable to start wxGUI vector digitizer.\n"
283                     "Details: %s") % errorMsg, parent=self)
284            return
285
286        if not self.MapWindowVDigit:
287            from vdigit.mapwindow import VDigitWindow
288            self.MapWindowVDigit = VDigitWindow(
289                parent=self, giface=self._giface,
290                properties=self.mapWindowProperties, Map=self.Map,
291                tree=self.tree, lmgr=self._layerManager,
292                overlays=self.decorations)
293            self._setUpMapWindow(self.MapWindowVDigit)
294            self.MapWindowVDigit.digitizingInfo.connect(
295                lambda text:
296                self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(text))
297            self.MapWindowVDigit.digitizingInfoUnavailable.connect(
298                lambda: self.statusbarManager.statusbarItems['coordinates'].SetAdditionalInfo(None))
299            self.MapWindowVDigit.Show()
300            self._mgr.AddPane(
301                self.MapWindowVDigit, wx.aui.AuiPaneInfo().CentrePane(). Dockable(False).BestSize(
302                    (-1, -1)).Name('vdigit'). CloseButton(False).DestroyOnClose(True). Layer(0))
303
304        self._switchMapWindow(self.MapWindowVDigit)
305
306        if self._mgr.GetPane('2d').IsShown():
307            self._mgr.GetPane('2d').Hide()
308        elif self._mgr.GetPane('3d').IsShown():
309            self._mgr.GetPane('3d').Hide()
310        self._mgr.GetPane('vdigit').Show()
311        if 'vdigit' not in self.toolbars:
312            self.toolbars['vdigit'] = VDigitToolbar(
313                parent=self, toolSwitcher=self._toolSwitcher,
314                MapWindow=self.MapWindow, digitClass=VDigit,
315                giface=self._giface)
316            self.toolbars['vdigit'].quitDigitizer.connect(self.QuitVDigit)
317            self.Map.layerAdded.connect(self._updateVDigitLayers)
318        self.MapWindowVDigit.SetToolbar(self.toolbars['vdigit'])
319
320        self._mgr.AddPane(self.toolbars['vdigit'],
321                          wx.aui.AuiPaneInfo().
322                          Name("vdigittoolbar").Caption(_("Vector Digitizer Toolbar")).
323                          ToolbarPane().Top().Row(1).
324                          LeftDockable(False).RightDockable(False).
325                          BottomDockable(False).TopDockable(True).
326                          CloseButton(False).Layer(2).
327                          BestSize((self.toolbars['vdigit'].GetBestSize())))
328        # change mouse to draw digitized line
329        self.MapWindow.mouse['box'] = "point"
330        self.MapWindow.zoomtype = 0
331        self.MapWindow.pen = wx.Pen(colour='red', width=2, style=wx.SOLID)
332        self.MapWindow.polypen = wx.Pen(
333            colour='green', width=2, style=wx.SOLID)
334
335    def _updateVDigitLayers(self, layer):
336        """Update vdigit layers"""
337        if 'vdigit' in self.toolbars:
338            self.toolbars['vdigit'].UpdateListOfLayers(updateTool=True)
339
340    def AddNviz(self):
341        """Add 3D view mode window
342        """
343        from nviz.main import haveNviz, GLWindow, errorMsg
344
345        # check for GLCanvas and OpenGL
346        if not haveNviz:
347            self.toolbars['map'].combo.SetValue(_("2D view"))
348            GError(
349                parent=self, message=_(
350                    "Unable to switch to 3D display mode.\nThe Nviz python extension "
351                    "was not found or loaded properly.\n"
352                    "Switching back to 2D display mode.\n\nDetails: %s" %
353                    errorMsg))
354            return
355
356        # here was disabling 3D for other displays, now done on starting3dMode
357
358        self.toolbars['map'].Enable2D(False)
359        # add rotate tool to map toolbar
360        self.toolbars['map'].InsertTool(
361            (('rotate',
362              NvizIcons['rotate'],
363              self.OnRotate,
364              wx.ITEM_CHECK,
365              7),
366             ))  # 7 is position
367        self._toolSwitcher.AddToolToGroup(
368            group='mouseUse', toolbar=self.toolbars['map'],
369            tool=self.toolbars['map'].rotate)
370        self.toolbars['map'].InsertTool((('flyThrough', NvizIcons['flyThrough'],
371                                          self.OnFlyThrough, wx.ITEM_CHECK, 8),))
372        self._toolSwitcher.AddToolToGroup(
373            group='mouseUse', toolbar=self.toolbars['map'],
374            tool=self.toolbars['map'].flyThrough)
375        # update status bar
376
377        self.statusbarManager.HideStatusbarChoiceItemsByClass(
378            self.statusbarItemsHiddenInNviz)
379        self.statusbarManager.SetMode(0)
380
381        # erase map window
382        self.MapWindow.EraseMap()
383
384        self._giface.WriteCmdLog(
385            _("Starting 3D view mode..."),
386            notification=Notification.HIGHLIGHT)
387        self.SetStatusText(_("Please wait, loading data..."), 0)
388
389        # create GL window
390        if not self.MapWindow3D:
391            self.MapWindow3D = GLWindow(
392                self,
393                giface=self._giface,
394                id=wx.ID_ANY,
395                frame=self,
396                Map=self.Map,
397                tree=self.tree,
398                lmgr=self._layerManager)
399            self._setUpMapWindow(self.MapWindow3D)
400            self.MapWindow3D.mapQueried.connect(self.Query)
401            self._switchMapWindow(self.MapWindow3D)
402            self.MapWindow.SetNamedCursor('default')
403
404            # here was AddNvizTools in lmgr
405            self.starting3dMode.emit(firstTime=True)
406
407            # switch from MapWindow to MapWindowGL
408            self._mgr.GetPane('2d').Hide()
409            self._mgr.AddPane(
410                self.MapWindow3D, wx.aui.AuiPaneInfo().CentrePane(). Dockable(False).BestSize(
411                    (-1, -1)).Name('3d'). CloseButton(False).DestroyOnClose(True). Layer(0))
412
413            self.MapWindow3D.Show()
414            self.MapWindow3D.ResetViewHistory()
415            self.MapWindow3D.UpdateView(None)
416            self.MapWindow3D.overlayActivated.connect(self._activateOverlay)
417            self.MapWindow3D.overlayRemoved.connect(self.RemoveOverlay)
418        else:
419            self._switchMapWindow(self.MapWindow3D)
420            os.environ['GRASS_REGION'] = self.Map.SetRegion(
421                windres=True, windres3=True)
422            self.MapWindow3D.GetDisplay().Init()
423            self.MapWindow3D.LoadDataLayers()
424            del os.environ['GRASS_REGION']
425
426            # switch from MapWindow to MapWindowGL
427            self._mgr.GetPane('2d').Hide()
428            self._mgr.GetPane('3d').Show()
429
430            # here was AddNvizTools in lmgr and updating of pages
431            self.starting3dMode.emit(firstTime=False)
432
433            self.MapWindow3D.ResetViewHistory()
434
435        # connect signals for updating overlays
436        for overlay in self.decorations.values():
437            overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
438        self.Map.GetRenderMgr().renderDone.connect(self.MapWindow3D._onUpdateOverlays)
439
440        self._giface.updateMap.disconnect(self.MapWindow2D.UpdateMap)
441        self._giface.updateMap.connect(self.MapWindow3D.UpdateMap)
442        self.MapWindow3D.overlays = self.MapWindow2D.overlays
443        # update overlays needs to be called after because getClientSize
444        # is called during update and it must give reasonable values
445        wx.CallAfter(self.MapWindow3D.UpdateOverlays)
446
447        self.SetStatusText("", 0)
448        self._mgr.Update()
449
450    def Disable3dMode(self):
451        """Disables 3D mode (NVIZ) in user interface."""
452        # TODO: this is broken since item is removed but switch is drived by
453        # index
454        if '3D' in self.toolbars['map'].combo.GetString(1):
455            self.toolbars['map'].combo.Delete(1)
456
457    def RemoveNviz(self):
458        """Restore 2D view"""
459        try:
460            self.toolbars['map'].RemoveTool(self.toolbars['map'].rotate)
461            self.toolbars['map'].RemoveTool(self.toolbars['map'].flyThrough)
462        except AttributeError:
463            pass
464
465        # update status bar
466        self.statusbarManager.ShowStatusbarChoiceItemsByClass(
467            self.statusbarItemsHiddenInNviz)
468        self.statusbarManager.SetMode(UserSettings.Get(group='display',
469                                                       key='statusbarMode',
470                                                       subkey='selection'))
471        self.SetStatusText(_("Please wait, unloading data..."), 0)
472        # unloading messages from library cause highlight anyway
473        self._giface.WriteCmdLog(_("Switching back to 2D view mode..."),
474                                 notification=Notification.NO_NOTIFICATION)
475        if self.MapWindow3D:
476            self.MapWindow3D.OnClose(event=None)
477        # switch from MapWindowGL to MapWindow
478        self._mgr.GetPane('2d').Show()
479        self._mgr.GetPane('3d').Hide()
480
481        self._switchMapWindow(self.MapWindow2D)
482        # here was RemoveNvizTools form lmgr
483        self.ending3dMode.emit()
484        try:
485            self.MapWindow2D.overlays = self.MapWindow3D.overlays
486        except AttributeError:
487            pass
488        # TODO: here we end because self.MapWindow3D is None for a while
489        self._giface.updateMap.disconnect(self.MapWindow3D.UpdateMap)
490        self._giface.updateMap.connect(self.MapWindow2D.UpdateMap)
491        # disconnect overlays
492        for overlay in self.decorations.values():
493            overlay.overlayChanged.disconnect(self.MapWindow3D.UpdateOverlays)
494        self.Map.GetRenderMgr().renderDone.disconnect(self.MapWindow3D._onUpdateOverlays)
495        self.MapWindow3D.ClearTextures()
496
497        self.MapWindow.UpdateMap()
498        self._mgr.Update()
499        self.GetMapToolbar().SelectDefault()
500
501    def AddToolbar(self, name, fixed=False):
502        """Add defined toolbar to the window
503
504        Currently recognized toolbars are:
505         - 'map'     - basic map toolbar
506         - 'vdigit'  - vector digitizer
507
508        :param name: toolbar to add
509        :param fixed: fixed toolbar
510        """
511        # default toolbar
512        if name == "map":
513            if 'map' not in self.toolbars:
514                self.toolbars['map'] = MapToolbar(
515                    self, toolSwitcher=self._toolSwitcher)
516
517            self._mgr.AddPane(self.toolbars['map'],
518                              wx.aui.AuiPaneInfo().
519                              Name("maptoolbar").Caption(_("Map Toolbar")).
520                              ToolbarPane().Top().Name('mapToolbar').
521                              LeftDockable(False).RightDockable(False).
522                              BottomDockable(False).TopDockable(True).
523                              CloseButton(False).Layer(2).
524                              BestSize((self.toolbars['map'].GetBestSize())))
525
526        # vector digitizer
527        elif name == "vdigit":
528            self.toolbars['map'].combo.SetValue(_("Vector digitizer"))
529            self._addToolbarVDigit()
530
531        if fixed:
532            self.toolbars['map'].combo.Disable()
533
534        self._mgr.Update()
535
536    def RemoveToolbar(self, name, destroy=False):
537        """Removes defined toolbar from the window
538
539        :param name toolbar to remove
540        :param destroy True to destroy otherwise toolbar is only hidden
541        """
542        self._mgr.DetachPane(self.toolbars[name])
543        if destroy:
544            self._toolSwitcher.RemoveToolbarFromGroup(
545                'mouseUse', self.toolbars[name])
546            self.toolbars[name].Destroy()
547            self.toolbars.pop(name)
548        else:
549            self.toolbars[name].Hide()
550
551        if name == 'vdigit':
552            self._mgr.GetPane('vdigit').Hide()
553            self._mgr.GetPane('2d').Show()
554            self._switchMapWindow(self.MapWindow2D)
555
556        self.toolbars['map'].Enable2D(True)
557
558        self._mgr.Update()
559
560    def IsPaneShown(self, name):
561        """Check if pane (toolbar, mapWindow ...) of given name is currently shown"""
562        if self._mgr.GetPane(name).IsOk():
563            return self._mgr.GetPane(name).IsShown()
564        return False
565
566    def RemoveQueryLayer(self):
567        """Removes temporary map layers (queries)"""
568        qlayer = self.GetMap().GetListOfLayers(name=globalvar.QUERYLAYER)
569        for layer in qlayer:
570            self.GetMap().DeleteLayer(layer)
571
572    def OnRender(self, event):
573        """Re-render map composition (each map layer)
574        """
575        self.RemoveQueryLayer()
576
577        # deselect features in vdigit
578        if self.GetToolbar('vdigit'):
579            if self.MapWindow.digit:
580                self.MapWindow.digit.GetDisplay().SetSelected([])
581            self.MapWindow.UpdateMap(render=True, renderVector=True)
582        else:
583            self.MapWindow.UpdateMap(render=True)
584
585        # reset dialog with selected features
586        if self.dialogs['vselect']:
587            self.dialogs['vselect'].Reset()
588
589        # update statusbar
590        self.StatusbarUpdate()
591
592    def OnPointer(self, event):
593        """Pointer button clicked
594        """
595        self.MapWindow.SetModePointer()
596
597        if self.GetToolbar('vdigit'):
598            self.toolbars['vdigit'].action['id'] = -1
599            self.toolbars['vdigit'].action['desc'] = ''
600
601    def OnSelect(self, event):
602        """Vector feature selection button clicked
603        """
604        layerList = self._giface.GetLayerList()
605        layerSelected = layerList.GetSelectedLayer()
606        if not self.dialogs['vselect']:
607            if layerSelected is None:
608                GMessage(_("No map layer selected. Operation canceled."))
609                return
610
611            self.dialogs['vselect'] = VectorSelectBase(
612                self.parent, self._giface)
613            self.dialogs['vselect'].CreateDialog(createButton=True)
614            self.dialogs['vselect'].onCloseDialog.connect(
615                self._onCloseVectorSelectDialog)
616
617    def _onCloseVectorSelectDialog(self):
618        self.dialogs['vselect'] = None
619
620    def OnRotate(self, event):
621        """Rotate 3D view
622        """
623        self.MapWindow.mouse['use'] = "rotate"
624
625        # change the cursor
626        self.MapWindow.SetNamedCursor('hand')
627
628    def OnFlyThrough(self, event):
629        """Fly-through mode
630        """
631        self.MapWindow.mouse['use'] = "fly"
632
633        # change the cursor
634        self.MapWindow.SetNamedCursor('hand')
635        self.MapWindow.SetFocus()
636
637    def SaveToFile(self, event):
638        """Save map to image
639        """
640        filetype, ltype = self._prepareSaveToFile()
641        if not ltype:
642            return
643
644        # get size
645        dlg = ImageSizeDialog(self)
646        dlg.CentreOnParent()
647        if dlg.ShowModal() != wx.ID_OK:
648            dlg.Destroy()
649            return
650        width, height = dlg.GetValues()
651        dlg.Destroy()
652
653        # get filename
654        dlg = wx.FileDialog(parent=self,
655                            message=_("Choose a file name to save the image "
656                                      "(no need to add extension)"),
657                            wildcard=filetype,
658                            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
659
660        if dlg.ShowModal() == wx.ID_OK:
661            path = dlg.GetPath()
662            if not path:
663                dlg.Destroy()
664                return
665
666            base, ext = os.path.splitext(path)
667            fileType = ltype[dlg.GetFilterIndex()]['type']
668            extType = ltype[dlg.GetFilterIndex()]['ext']
669            if ext != extType:
670                path = base + '.' + extType
671
672            self.MapWindow.SaveToFile(path, fileType,
673                                      width, height)
674
675        dlg.Destroy()
676
677    def DOutFile(self, command, callback=None):
678        """Saves map to image by running d.out.file from gui or d.mon.
679        Command is expected to be validated by parser.
680        """
681        filetype, ltype = self._prepareSaveToFile()
682        if not ltype:
683            return
684        width, height = self.MapWindow.GetClientSize()
685        for param in command[1:]:
686            try:
687                p, val = param.split('=')
688            except ValueError:
689                # --overwrite
690                continue
691            if p == 'format':  # must be there
692                if self.IsPaneShown('3d'):
693                    extType = 'ppm'
694                else:
695                    extType = val
696            if p == 'output':  # must be there
697                name = val
698            elif p == 'size':
699                width, height = val.split(',')
700
701        base, ext = os.path.splitext(name)
702        if not ext:
703            name = base + '.' + extType
704        elif ext[1:] != extType:
705            extType = ext[1:]
706
707        if self.IsPaneShown('3d'):
708            bitmapType = 'ppm'
709        else:
710            bitmapType = wx.BITMAP_TYPE_PNG  # default type
711        for each in ltype:
712            if each['ext'] == extType:
713                bitmapType = each['type']
714                break
715        self.MapWindow.SaveToFile(name, bitmapType, int(width), int(height), callback)
716
717    def DOutFileOptData(self, dcmd, layer, params, propwin):
718        """Dummy function which is called when d.out.file is called
719        and returns parsed and validated command which is then passed
720        to DOutFile method."""
721        if not dcmd:
722            return
723
724        self.DOutFile(dcmd)
725
726    def DToRast(self, command):
727        """Saves currently loaded composition of layers as a raster map.
728        """
729        def _DToRastDone():
730            # import back as red, green, blue rasters
731            returncode, messages = RunCommand(
732                'r.in.gdal', flags='o', input=pngFile, output=tmpName, quiet=True,
733                overwrite=overwrite, getErrorMsg=True)
734            if not returncode == 0:
735                self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
736                return
737            # set region for composite
738            grass.use_temp_region()
739            returncode, messages = RunCommand('g.region', raster=tmpName + '.red',
740                                              quiet=True, getErrorMsg=True)
741            if not returncode == 0:
742                grass.del_temp_region()
743                self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
744                return
745            # composite
746            returncode, messages = RunCommand(
747                'r.composite', red=tmpName + '.red', green=tmpName + '.green',
748                blue=tmpName + '.blue', output=outputRaster, quiet=True,
749                overwrite=overwrite, getErrorMsg=True)
750            grass.del_temp_region()
751            RunCommand(
752                'g.remove',
753                type='raster',
754                flags='f',
755                quiet=True,
756                name=[tmpName + '.red', tmpName + '.green', tmpName + '.blue'])
757            if not returncode == 0:
758                self._giface.WriteError(_('Failed to run d.to.rast:\n') + messages)
759                grass.try_remove(pngFile)
760                return
761
762            # alignExtent changes only region variable
763            oldRegion = self.GetMap().GetCurrentRegion().copy()
764            self.GetMap().AlignExtentFromDisplay()
765            region = self.GetMap().GetCurrentRegion().copy()
766            self.GetMap().region.update(oldRegion)
767            RunCommand('r.region', map=outputRaster, n=region['n'], s=region['s'],
768                       e=region['e'], w=region['w'], quiet=True)
769            grass.try_remove(pngFile)
770
771
772        if self.IsPaneShown('3d'):
773            self._giface.WriteError(
774                _('d.to.rast can be used only in 2D mode.'))
775            return
776        outputRaster = None
777        overwrite = False
778        for param in command[1:]:
779            try:
780                p, val = param.split('=')
781                if p == 'output':
782                    outputRaster = val
783            except ValueError:
784                if param.startswith('--overwrite'):
785                    overwrite = True
786
787        if not outputRaster:
788            return
789        # output file as PNG
790        tmpName = 'd_to_rast_tmp'
791        pngFile = grass.tempfile(create=False) + '.png'
792        dOutFileCmd = ['d.out.file', 'output=' + pngFile, 'format=png']
793        self.DOutFile(dOutFileCmd, callback=_DToRastDone)
794
795
796
797    def DToRastOptData(self, dcmd, layer, params, propwin):
798        """Dummy function which is called when d.to.rast is called
799        and returns parsed and validated command which is then passed
800        to DToRast method."""
801        if not dcmd:
802            return
803
804        self.DToRast(dcmd)
805
806    def _prepareSaveToFile(self):
807        """Get wildcards and format extensions."""
808        if self.IsPaneShown('3d'):
809            filetype = "TIF file (*.tif)|*.tif|PPM file (*.ppm)|*.ppm"
810            ltype = [{'ext': 'tif', 'type': 'tif'},
811                     {'ext': 'ppm', 'type': 'ppm'}]
812        else:
813            img = self.MapWindow.img
814            if not img:
815                GMessage(
816                    parent=self,
817                    message=_(
818                        "Nothing to render (empty map). Operation canceled."))
819                return None, None
820            filetype, ltype = GetImageHandlers(img)
821        return filetype, ltype
822
823    def PrintMenu(self, event):
824        """
825        Print options and output menu for map display
826        """
827        printmenu = Menu()
828        # Add items to the menu
829        setup = wx.MenuItem(printmenu, wx.ID_ANY, _('Page setup'))
830        printmenu.AppendItem(setup)
831        self.Bind(wx.EVT_MENU, self.printopt.OnPageSetup, setup)
832
833        preview = wx.MenuItem(printmenu, wx.ID_ANY, _('Print preview'))
834        printmenu.AppendItem(preview)
835        self.Bind(wx.EVT_MENU, self.printopt.OnPrintPreview, preview)
836
837        doprint = wx.MenuItem(printmenu, wx.ID_ANY, _('Print display'))
838        printmenu.AppendItem(doprint)
839        self.Bind(wx.EVT_MENU, self.printopt.OnDoPrint, doprint)
840
841        # Popup the menu.  If an item is selected then its handler
842        # will be called before PopupMenu returns.
843        self.PopupMenu(printmenu)
844        printmenu.Destroy()
845
846    def CleanUp(self):
847        """Clean up before closing map display.
848        End digitizer/nviz."""
849        Debug.msg(2, "MapFrame.CleanUp()")
850        self.Map.Clean()
851        # close edited map and 3D tools properly
852        if self.GetToolbar('vdigit'):
853            maplayer = self.toolbars['vdigit'].GetLayer()
854            if maplayer:
855                self.toolbars['vdigit'].OnExit()
856        if self.IsPaneShown('3d'):
857            self.RemoveNviz()
858        if hasattr(self, 'rdigit') and self.rdigit:
859            self.rdigit.CleanUp()
860        if self.dialogs['vnet']:
861            self.closingVNETDialog.emit()
862        self._mgr.UnInit()
863
864    def OnCloseWindow(self, event, askIfSaveWorkspace=True):
865        """Window closed.
866        Also close associated layer tree page
867        """
868        Debug.msg(2, "MapFrame.OnCloseWindow()")
869        if self._layerManager:
870            pgnum = self.layerbook.GetPageIndex(self.page)
871            name = self.layerbook.GetPageText(pgnum)
872            caption = _("Close Map Display {}").format(name)
873            if not askIfSaveWorkspace or \
874               (askIfSaveWorkspace and self._layerManager.CanClosePage(caption)):
875                self.CleanUp()
876                if pgnum > -1:
877                    self.closingDisplay.emit(page_index=pgnum)
878                    # Destroy is called when notebook page is deleted
879        else:
880            self.CleanUp()
881            self.Destroy()
882
883    def Query(self, x, y):
884        """Query selected layers.
885
886        :param x,y: coordinates
887        """
888        if self._vectQueryLayers or self._rastQueryLayers:
889            rast = self._rastQueryLayers
890            vect = self._vectQueryLayers
891        else:
892            layers = self._giface.GetLayerList().GetSelectedLayers(checkedOnly=False)
893            rast = []
894            vect = []
895            for layer in layers:
896                if layer.type == 'command':
897                    continue
898                name, found = GetLayerNameFromCmd(layer.cmd)
899                if not found:
900                    continue
901                ltype = layer.maplayer.GetType()
902                if ltype == 'raster':
903                    rast.append(name)
904                elif ltype in ('rgb', 'his'):
905                    for iname in name.split('\n'):
906                        rast.append(iname)
907                elif ltype in ('vector', 'thememap', 'themechart'):
908                    vect.append(name)
909            if vect:
910                # check for vector maps open to be edited
911                digitToolbar = self.GetToolbar('vdigit')
912                if digitToolbar:
913                    lmap = digitToolbar.GetLayer().GetName()
914                    for name in vect:
915                        if lmap == name:
916                            self._giface.WriteWarning(
917                                _("Vector map <%s> " "opened for editing - skipped.") % lmap)
918                            vect.remove(name)
919
920        if not (rast + vect):
921            GMessage(
922                parent=self,
923                message=_(
924                    'No raster or vector map layer selected for querying.'))
925            return
926
927        # set query snap distance for v.what at map unit equivalent of 10
928        # pixels
929        qdist = 10.0 * (
930            (self.Map.region['e'] - self.Map.region['w']) / self.Map.width)
931
932        # TODO: replace returning None by exception or so
933        try:
934            east, north = self.MapWindow.Pixel2Cell((x, y))
935        except TypeError:
936            return
937
938        if not self.IsPaneShown('3d'):
939            self.QueryMap(east, north, qdist, rast, vect)
940        else:
941            if rast:
942                self.MapWindow.QuerySurface(x, y)
943            if vect:
944                self.QueryMap(east, north, qdist, rast=[], vect=vect)
945
946    def SetQueryLayersAndActivate(self, ltype, maps):
947        """Activate query mode and set layers to query.
948        This method is used for querying in d.mon using d.what.rast/vect"""
949        self.toolbars['map'].SelectTool(self.toolbars['map'].query)
950        if ltype == 'vector':
951            self._vectQueryLayers = maps
952        elif ltype == 'raster':
953            self._rastQueryLayers = maps
954
955    def QueryMap(self, east, north, qdist, rast, vect):
956        """Query raster or vector map layers by r/v.what
957
958        :param east,north: coordinates
959        :param qdist: query distance
960        :param rast: raster map names
961        :param vect: vector map names
962        """
963        Debug.msg(1, "QueryMap(): raster=%s vector=%s" % (','.join(rast),
964                                                          ','.join(vect)))
965        if self._highlighter_layer is None:
966            self._highlighter_layer = VectorSelectHighlighter(
967                mapdisp=self._giface.GetMapDisplay(), giface=self._giface)
968
969        # use display region settings instead of computation region settings
970        self.tmpreg = os.getenv("GRASS_REGION")
971        os.environ["GRASS_REGION"] = self.Map.SetRegion(windres=False)
972
973        rastQuery = []
974        vectQuery = []
975        if rast:
976            rastQuery = grass.raster_what(map=rast, coord=(east, north),
977                                          localized=True)
978        if vect:
979            encoding = UserSettings.Get(
980                group='atm', key='encoding', subkey='value')
981            try:
982                vectQuery = grass.vector_what(
983                    map=vect, coord=(east, north),
984                    distance=qdist, encoding=encoding, multiple=True)
985            except grass.ScriptError:
986                GError(
987                    parent=self, message=_(
988                        "Failed to query vector map(s) <{maps}>. "
989                        "Check database settings and topology.").format(
990                        maps=','.join(vect)))
991        self._QueryMapDone()
992
993        self._highlighter_layer.Clear()
994        if vectQuery and 'Category' in vectQuery[0]:
995            self._queryHighlight(vectQuery)
996
997        result = rastQuery + vectQuery
998        result = PrepareQueryResults(coordinates=(east, north), result=result)
999        if self.dialogs['query']:
1000            self.dialogs['query'].Raise()
1001            self.dialogs['query'].SetData(result)
1002        else:
1003            self.dialogs['query'] = QueryDialog(parent=self, data=result)
1004            self.dialogs['query'].Bind(wx.EVT_CLOSE, self._oncloseQueryDialog)
1005            self.dialogs['query'].redirectOutput.connect(
1006                self._onRedirectQueryOutput)
1007            self.dialogs['query'].Show()
1008
1009    def _oncloseQueryDialog(self, event):
1010        self.dialogs['query'] = None
1011        self._vectQueryLayers = []
1012        self._rastQueryLayers = []
1013        self._highlighter_layer.Clear()
1014        self._highlighter_layer = None
1015        event.Skip()
1016
1017    def _onRedirectQueryOutput(self, output, style='log'):
1018        """Writes query output into console"""
1019        if style == 'log':
1020            self._giface.WriteLog(
1021                output, notification=Notification.MAKE_VISIBLE)
1022        elif style == 'cmd':
1023            self._giface.WriteCmdLog(output)
1024
1025    def _queryHighlight(self, vectQuery):
1026        """Highlight category from query."""
1027        if len(vectQuery) > 0:
1028            self._highlighter_layer.SetLayer(vectQuery[0]['Layer'])
1029            self._highlighter_layer.SetMap(
1030                vectQuery[0]['Map'] + '@' + vectQuery[0]['Mapset']
1031            )
1032            tmp = list()
1033            for i in vectQuery:
1034                tmp.append(i['Category'])
1035
1036            self._highlighter_layer.SetCats(tmp)
1037            self._highlighter_layer.DrawSelected()
1038
1039    def _QueryMapDone(self):
1040        """Restore settings after querying (restore GRASS_REGION)
1041        """
1042        if hasattr(self, "tmpreg"):
1043            if self.tmpreg:
1044                os.environ["GRASS_REGION"] = self.tmpreg
1045            elif 'GRASS_REGION' in os.environ:
1046                del os.environ["GRASS_REGION"]
1047        elif 'GRASS_REGION' in os.environ:
1048            del os.environ["GRASS_REGION"]
1049
1050        if hasattr(self, "tmpreg"):
1051            del self.tmpreg
1052
1053    def OnQuery(self, event):
1054        """Query tools menu"""
1055        self.MapWindow.mouse['use'] = "query"
1056        self.MapWindow.mouse['box'] = "point"
1057        self.MapWindow.zoomtype = 0
1058
1059        # change the cursor
1060        self.MapWindow.SetNamedCursor('cross')
1061
1062    def AddTmpVectorMapLayer(self, name, cats, useId=False, addLayer=True):
1063        """Add temporal vector map layer to map composition
1064
1065        :param name: name of map layer
1066        :param useId: use feature id instead of category
1067        """
1068        # color settings from ATM
1069        color = UserSettings.Get(group='atm', key='highlight', subkey='color')
1070        colorStr = str(color[0]) + ":" + \
1071            str(color[1]) + ":" + \
1072            str(color[2])
1073
1074        # icon used in vector display and its size
1075        icon = ''
1076        size = 0
1077        # here we know that there is one selected layer and it is vector
1078        layerSelected = self._giface.GetLayerList().GetSelectedLayer()
1079        if not layerSelected:
1080            return None
1081
1082        vparam = layerSelected.cmd
1083        for p in vparam:
1084            if '=' in p:
1085                parg, pval = p.split('=', 1)
1086                if parg == 'icon':
1087                    icon = pval
1088                elif parg == 'size':
1089                    size = float(pval)
1090
1091        pattern = [
1092            "d.vect",
1093            "map=%s" % name,
1094            "color=%s" % colorStr,
1095            "fill_color=%s" % colorStr,
1096            "width=%d" % UserSettings.Get(
1097                group='atm',
1098                key='highlight',
1099                subkey='width')
1100        ]
1101        if icon != '':
1102            pattern.append('icon=%s' % icon)
1103        if size > 0:
1104            pattern.append('size=%i' % size)
1105
1106        if useId:
1107            cmd = pattern
1108            cmd.append('-i')
1109            cmd.append('cats=%s' % str(cats))
1110        else:
1111            cmd = []
1112            for layer in cats.keys():
1113                cmd.append(copy.copy(pattern))
1114                lcats = cats[layer]
1115                cmd[-1].append("layer=%d" % layer)
1116                cmd[-1].append("cats=%s" % ListOfCatsToRange(lcats))
1117
1118        if addLayer:
1119            args = {}
1120            if useId:
1121                args['ltype'] = 'vector'
1122            else:
1123                args['ltype'] = 'command'
1124
1125            return self.Map.AddLayer(name=globalvar.QUERYLAYER, command=cmd,
1126                                     active=True, hidden=True, opacity=1.0,
1127                                     render=True, **args)
1128        else:
1129            return cmd
1130
1131    def OnMeasureDistance(self, event):
1132        self._onMeasure(MeasureDistanceController)
1133
1134    def OnMeasureArea(self, event):
1135        self._onMeasure(MeasureAreaController)
1136
1137    def _onMeasure(self, controller):
1138        """Starts measurement mode.
1139
1140        :param controller: measurement class (MeasureDistanceController, MeasureAreaController)
1141        """
1142        self.measureController = controller(
1143            self._giface, mapWindow=self.GetMapWindow())
1144        # assure that the mode is ended and lines are cleared whenever other
1145        # tool is selected
1146        self._toolSwitcher.toggleToolChanged.connect(
1147            lambda: self.measureController.Stop())
1148        self.measureController.Start()
1149
1150    def OnProfile(self, event):
1151        """Launch profile tool
1152        """
1153        rasters = []
1154        layers = self._giface.GetLayerList().GetSelectedLayers()
1155        for layer in layers:
1156            if layer.type == 'raster':
1157                rasters.append(layer.maplayer.name)
1158        self.Profile(rasters=rasters)
1159
1160    def Profile(self, rasters=None):
1161        """Launch profile tool"""
1162        from wxplot.profile import ProfileFrame
1163
1164        self.profileController = ProfileController(
1165            self._giface, mapWindow=self.GetMapWindow())
1166        win = ProfileFrame(parent=self, giface=self._giface, rasterList=rasters,
1167                           units=self.Map.projinfo['units'],
1168                           controller=self.profileController)
1169        win.Show()
1170        # Open raster select dialog to make sure that a raster (and
1171        # the desired raster) is selected to be profiled
1172        win.OnSelectRaster(None)
1173
1174    def OnHistogramPyPlot(self, event):
1175        """Init PyPlot histogram display canvas and tools
1176        """
1177        raster = []
1178
1179        for layer in self._giface.GetLayerList().GetSelectedLayers():
1180            if layer.maplayer.GetType() == 'raster':
1181                raster.append(layer.maplayer.GetName())
1182
1183        from wxplot.histogram import HistogramPlotFrame
1184        win = HistogramPlotFrame(parent=self, giface=self._giface,
1185                                 rasterList=raster)
1186        win.CentreOnParent()
1187        win.Show()
1188
1189    def OnScatterplot(self, event):
1190        """Init PyPlot scatterplot display canvas and tools
1191        """
1192        raster = []
1193
1194        for layer in self._giface.GetLayerList().GetSelectedLayers():
1195            if layer.maplayer.GetType() == 'raster':
1196                raster.append(layer.maplayer.GetName())
1197
1198        from wxplot.scatter import ScatterFrame
1199        win = ScatterFrame(parent=self, giface=self._giface, rasterList=raster)
1200
1201        win.CentreOnParent()
1202        win.Show()
1203        # Open raster select dialog to make sure that at least 2 rasters (and the desired rasters)
1204        # are selected to be plotted
1205        win.OnSelectRaster(None)
1206
1207    def OnHistogram(self, event):
1208        """Init histogram display canvas and tools
1209        """
1210        from modules.histogram import HistogramFrame
1211        win = HistogramFrame(self, giface=self._giface)
1212
1213        win.CentreOnParent()
1214        win.Show()
1215        win.Refresh()
1216        win.Update()
1217
1218    def _activateOverlay(self, overlayId):
1219        """Launch decoration dialog according to overlay id.
1220
1221        :param overlayId: id of overlay
1222        """
1223        if overlayId in self.decorations:
1224            dlg = self.decorations[overlayId].dialog
1225            if dlg.IsShown():
1226                dlg.SetFocus()
1227                dlg.Raise()
1228            else:
1229                dlg.Show()
1230
1231    def RemoveOverlay(self, overlayId):
1232        """Hide overlay.
1233
1234        :param overlayId: id of overlay
1235        """
1236        del self._decorationWindows[self.decorations[overlayId].dialog]
1237        self.decorations[overlayId].Remove()
1238        del self.decorations[overlayId]
1239
1240    def AddBarscale(self, cmd=None):
1241        """Handler for scale bar map decoration menu selection."""
1242        if self.IsPaneShown('3d'):
1243            self.MapWindow3D.SetDrawScalebar((70, 70))
1244            return
1245
1246        if cmd:
1247            show = False
1248        else:
1249            show = True
1250            cmd = ['d.barscale']
1251
1252        # Decoration overlay control dialog
1253        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
1254            cmd, completed=(self.GetOptData, None, None))
1255
1256        self.MapWindow.mouse['use'] = 'pointer'
1257
1258    def AddLegendRast(self, cmd=None):
1259        """Handler for legend raster map decoration menu selection."""
1260
1261        if cmd:
1262            show = False
1263        else:
1264            show = True
1265            cmd = ['d.legend']
1266            layers = self._giface.GetLayerList().GetSelectedLayers()
1267            for layer in layers:
1268                if layer.type == 'raster':
1269                    cmd.append('raster={rast}'.format(rast=layer.maplayer.name))
1270                    break
1271
1272        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
1273            cmd, completed=(self.GetOptData, None, None))
1274
1275        self.MapWindow.mouse['use'] = 'pointer'
1276
1277    def AddLegendVect(self, cmd=None, showDialog=None):
1278        """Handler for legend vector map decoration menu selection."""
1279
1280        if cmd:
1281            show = False
1282        else:
1283            show = True
1284            cmd = ['d.legend.vect']
1285
1286        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
1287            cmd, completed=(self.GetOptData, None, None))
1288
1289        self.MapWindow.mouse['use'] = 'pointer'
1290
1291    def AddArrow(self, cmd=None):
1292        """Handler for north arrow menu selection."""
1293        if self.IsPaneShown('3d'):
1294            # here was opening of appearance page of nviz notebook
1295            # but now moved to MapWindow3D where are other problematic nviz
1296            # calls
1297            self.MapWindow3D.SetDrawArrow((70, 70))
1298            return
1299
1300        if cmd:
1301            show = False
1302        else:
1303            show = True
1304            cmd = ['d.northarrow']
1305
1306        # Decoration overlay control dialog
1307        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
1308            cmd, completed=(self.GetOptData, None, None))
1309
1310        self.MapWindow.mouse['use'] = 'pointer'
1311
1312    def AddDtext(self, cmd=None):
1313        """Handler for d.text menu selection."""
1314        if cmd:
1315            show = False
1316        else:
1317            show = True
1318            cmd = ['d.text']
1319
1320        # Decoration overlay control dialog
1321        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
1322            cmd, completed=(self.GetOptData, None, None))
1323
1324        self.MapWindow.mouse['use'] = 'pointer'
1325
1326    def GetOptData(self, dcmd, layer, params, propwin):
1327        """Called after options are set through module dialog.
1328
1329        :param dcmd: resulting command
1330        :param layer: not used
1331        :param params: module parameters (not used)
1332        :param propwin: dialog window
1333        """
1334
1335        if not dcmd:
1336            return
1337        if propwin in self._decorationWindows:
1338            overlay = self._decorationWindows[propwin]
1339        else:
1340            cmd = dcmd[0]
1341            if cmd == 'd.northarrow':
1342                overlay = ArrowController(self.Map, self._giface)
1343            elif cmd == 'd.barscale':
1344                overlay = BarscaleController(self.Map, self._giface)
1345            elif cmd == 'd.legend':
1346                overlay = LegendController(self.Map, self._giface)
1347            elif cmd == 'd.legend.vect':
1348                overlay = LegendVectController(self.Map, self._giface)
1349            elif cmd == 'd.text':
1350                overlay = DtextController(self.Map, self._giface)
1351
1352            self.decorations[overlay.id] = overlay
1353            overlay.overlayChanged.connect(lambda: self.MapWindow2D.UpdateMap(
1354                                           render=False, renderVector=False))
1355            if self.IsPaneShown('3d'):
1356                overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
1357
1358            overlay.dialog = propwin
1359            self._decorationWindows[propwin] = overlay
1360
1361        overlay.cmd = dcmd
1362        overlay.Show()
1363
1364    def OnZoomToMap(self, event):
1365        """Set display extents to match selected raster (including
1366        NULLs) or vector map.
1367        """
1368        Debug.msg(3, "MapFrame.OnZoomToMap()")
1369        layers = None
1370        if self.IsStandalone():
1371            layers = self.MapWindow.GetMap().GetListOfLayers(active=False)
1372
1373        self.MapWindow.ZoomToMap(layers=layers)
1374
1375    def OnZoomToRaster(self, event):
1376        """Set display extents to match selected raster map (ignore NULLs)
1377        """
1378        self.MapWindow.ZoomToMap(ignoreNulls=True)
1379
1380    def OnZoomToSaved(self, event):
1381        """Set display geometry to match extents in
1382        saved region file
1383        """
1384        self.MapWindow.SetRegion(zoomOnly=True)
1385
1386    def OnSetDisplayToWind(self, event):
1387        """Set computational region (WIND file) to match display
1388        extents
1389        """
1390        self.MapWindow.DisplayToWind()
1391
1392    def OnSetWindToRegion(self, event):
1393        """Set computational region (WIND file) from named region
1394        file
1395        """
1396        self.MapWindow.SetRegion(zoomOnly=False)
1397
1398    def OnSetExtentToWind(self, event):
1399        """Set compulational region extent interactively"""
1400        self.MapWindow.SetModeDrawRegion()
1401
1402    def OnSaveDisplayRegion(self, event):
1403        """Save display extents to named region file.
1404        """
1405        self.MapWindow.SaveRegion(display=True)
1406
1407    def OnSaveWindRegion(self, event):
1408        """Save computational region to named region file.
1409        """
1410        self.MapWindow.SaveRegion(display=False)
1411
1412    def OnZoomMenu(self, event):
1413        """Popup Zoom menu
1414        """
1415        zoommenu = Menu()
1416
1417        for label, handler in (
1418            (_('Zoom to default region'),
1419             self.OnZoomToDefault),
1420            (_('Zoom to saved region'),
1421             self.OnZoomToSaved),
1422            (None, None),
1423            (_('Set computational region extent from display'),
1424             self.OnSetDisplayToWind),
1425            (_('Set computational region extent interactively'),
1426             self.OnSetExtentToWind),
1427            (_('Set computational region from named region'),
1428             self.OnSetWindToRegion),
1429            (None, None),
1430            (_('Save display geometry to named region'),
1431             self.OnSaveDisplayRegion),
1432            (_('Save computational region to named region'),
1433             self.OnSaveWindRegion)):
1434            if label:
1435                mid = wx.MenuItem(zoommenu, wx.ID_ANY, label)
1436                zoommenu.AppendItem(mid)
1437                self.Bind(wx.EVT_MENU, handler, mid)
1438            else:
1439                zoommenu.AppendSeparator()
1440
1441        # Popup the menu. If an item is selected then its handler will
1442        # be called before PopupMenu returns.
1443        self.PopupMenu(zoommenu)
1444        zoommenu.Destroy()
1445
1446    def SetProperties(self, render=False, mode=0, showCompExtent=False,
1447                      constrainRes=False, projection=False, alignExtent=True):
1448        """Set properies of map display window"""
1449        self.mapWindowProperties.autoRender = render
1450        if self.statusbarManager:
1451            self.statusbarManager.SetMode(mode)
1452            self.StatusbarUpdate()
1453            self.SetProperty('projection', projection)
1454        self.mapWindowProperties.showRegion = showCompExtent
1455        self.mapWindowProperties.alignExtent = alignExtent
1456        self.mapWindowProperties.resolution = constrainRes
1457
1458    def IsStandalone(self):
1459        """Check if Map display is standalone
1460
1461        .. deprecated:: 7.0
1462        """
1463        # TODO: once it is removed from 2 places in vdigit it can be deleted
1464        # here and also in base class and other classes in the tree (hopefully)
1465        # and one place here still uses IsStandalone
1466        Debug.msg(1, "MapFrame.IsStandalone(): Method IsStandalone is"
1467                  "depreciated, use some general approach instead such as"
1468                  " Signals or giface")
1469        if self._layerManager:
1470            return False
1471
1472        return True
1473
1474    def GetLayerManager(self):
1475        """Get reference to Layer Manager
1476
1477        :return: window reference
1478        :return: None (if standalone)
1479
1480        .. deprecated:: 7.0
1481        """
1482        Debug.msg(1, "MapFrame.GetLayerManager(): Method GetLayerManager is"
1483                  "depreciated, use some general approach instead such as"
1484                  " Signals or giface")
1485        return self._layerManager
1486
1487    def GetMapToolbar(self):
1488        """Returns toolbar with zooming tools"""
1489        return self.toolbars['map'] if 'map' in self.toolbars else None
1490
1491    def GetToolbarNames(self):
1492        """Return toolbar names"""
1493        return self.toolbars.keys()
1494
1495    def GetDialog(self, name):
1496        """Get selected dialog if exist"""
1497        return self.dialogs.get(name, None)
1498
1499    def OnVNet(self, event):
1500        """Dialog for v.net* modules
1501        """
1502        if self.dialogs['vnet']:
1503            self.dialogs['vnet'].Raise()
1504            return
1505
1506        from vnet.dialogs import VNETDialog
1507        self.dialogs['vnet'] = VNETDialog(parent=self, giface=self._giface)
1508        self.closingVNETDialog.connect(self.dialogs['vnet'].OnCloseDialog)
1509        self.dialogs['vnet'].CenterOnScreen()
1510        self.dialogs['vnet'].Show()
1511
1512    def ResetPointer(self):
1513        """Sets pointer mode.
1514
1515        Sets pointer and toggles it (e.g. after unregistration of mouse
1516        handler).
1517        """
1518        self.GetMapToolbar().SelectDefault()
1519
1520    def _switchMapWindow(self, map_win):
1521        """Notifies activated and disactivated map_wins."""
1522        self.MapWindow.DisactivateWin()
1523        map_win.ActivateWin()
1524
1525        self.MapWindow = map_win
1526
1527    def AddRDigit(self):
1528        """Adds raster digitizer: creates toolbar and digitizer controller,
1529        binds events and signals."""
1530        from rdigit.controller import RDigitController, EVT_UPDATE_PROGRESS
1531        from rdigit.toolbars import RDigitToolbar
1532
1533        self.rdigit = RDigitController(self._giface,
1534                                       mapWindow=self.GetMapWindow())
1535        self.toolbars['rdigit'] = RDigitToolbar(
1536            parent=self, giface=self._giface, controller=self.rdigit,
1537            toolSwitcher=self._toolSwitcher)
1538        # connect signals
1539        self.rdigit.newRasterCreated.connect(
1540            self.toolbars['rdigit'].NewRasterAdded)
1541        self.rdigit.newRasterCreated.connect(
1542            lambda name: self._giface.mapCreated.emit(
1543                name=name, ltype='raster'))
1544        self.rdigit.newFeatureCreated.connect(
1545            self.toolbars['rdigit'].UpdateCellValues)
1546        self.rdigit.uploadMapCategories.connect(
1547            self.toolbars['rdigit'].UpdateCellValues)
1548        self.rdigit.showNotification.connect(
1549            lambda text: self.SetStatusText(text, 0))
1550        self.rdigit.quitDigitizer.connect(self.QuitRDigit)
1551        self.rdigit.Bind(
1552            EVT_UPDATE_PROGRESS,
1553            lambda evt: self.statusbarManager.SetProgress(
1554                evt.range,
1555                evt.value,
1556                evt.text))
1557        rasters = self.GetMap().GetListOfLayers(
1558            ltype='raster', mapset=grass.gisenv()['MAPSET'])
1559        self.toolbars['rdigit'].UpdateRasterLayers(rasters)
1560        self.toolbars['rdigit'].SelectDefault()
1561
1562        self.GetMap().layerAdded.connect(self._updateRDigitLayers)
1563        self.GetMap().layerRemoved.connect(self._updateRDigitLayers)
1564        self.GetMap().layerChanged.connect(self._updateRDigitLayers)
1565        self._mgr.AddPane(self.toolbars['rdigit'],
1566                          wx.aui.AuiPaneInfo().
1567                          Name("rdigit toolbar").Caption(_("Raster Digitizer Toolbar")).
1568                          ToolbarPane().Top().Row(1).
1569                          LeftDockable(False).RightDockable(False).
1570                          BottomDockable(False).TopDockable(True).Floatable().
1571                          CloseButton(False).Layer(2).DestroyOnClose().
1572                          BestSize((self.toolbars['rdigit'].GetBestSize())))
1573        self._mgr.Update()
1574
1575        self.rdigit.Start()
1576
1577    def _updateRDigitLayers(self, layer):
1578        mapset = grass.gisenv()['MAPSET']
1579        self.toolbars['rdigit'].UpdateRasterLayers(
1580            rasters=self.GetMap().GetListOfLayers(
1581                ltype='raster', mapset=mapset))
1582
1583    def QuitRDigit(self):
1584        """Calls digitizer cleanup, removes digitizer object and disconnects
1585        signals from Map."""
1586        self.rdigit.CleanUp()
1587        # disconnect updating layers
1588        self.GetMap().layerAdded.disconnect(self._updateRDigitLayers)
1589        self.GetMap().layerRemoved.disconnect(self._updateRDigitLayers)
1590        self.GetMap().layerChanged.disconnect(self._updateRDigitLayers)
1591        self._toolSwitcher.toggleToolChanged.disconnect(self.toolbars['rdigit'].CheckSelectedTool)
1592
1593        self.RemoveToolbar('rdigit', destroy=True)
1594        self.rdigit = None
1595
1596    def QuitVDigit(self):
1597        """Quit VDigit"""
1598        if not self.IsStandalone():
1599            # disable the toolbar
1600            self.RemoveToolbar("vdigit", destroy=True)
1601        else:
1602            self.Close()
1603