class MapPanelBase(wx.Panel): """Base class for map display window Derived class must use (create and initialize) \c statusbarManager or override GetProperty(), SetProperty() and HasProperty() methods. Several methods has to be overridden or \c NotImplementedError("MethodName") will be raised. If derived class enables and disables auto-rendering, it should override IsAutoRendered method. It is expected that derived class will call _setUpMapWindow(). Derived class can has one or more map windows (and map renders) but implementation of MapPanelBase expects that one window and one map will be current. Current instances of map window and map renderer should be returned by methods GetWindow() and GetMap() respectively. AUI manager is stored in \c self._mgr. """ def __init__( self, parent=None, id=wx.ID_ANY, title="", auimgr=None, name="", **kwargs, ): """ .. warning:: Use \a auimgr parameter only if you know what you are doing. :param parent: gui parent :param id: wx id :param title: window title :param toolbars: array of activated toolbars, e.g. ['map', 'digit'] :param auimgr: AUI manager (if \c None, wx.aui.AuiManager is used) :param name: panel name :param kwargs: arguments passed to \c wx.Panel """ self.parent = parent wx.Panel.__init__(self, parent, id, name=name, **kwargs) # toolbars self.toolbars = {} self.iconsize = (16, 16) # properties are shared in other objects, so defining here self.mapWindowProperties = MapWindowProperties() self.mapWindowProperties.setValuesFromUserSettings() # update statusbar when user-defined projection changed self.mapWindowProperties.useDefinedProjectionChanged.connect( self.StatusbarUpdate) # # Fancy gui # if auimgr is None: from wx.aui import AuiManager self._mgr = AuiManager(self) else: self._mgr = auimgr # handles switching between tools in different toolbars self._toolSwitcher = ToolSwitcher() self._toolSwitcher.toggleToolChanged.connect(self._onToggleTool) # set accelerator table self.shortcuts_table = [ (self.OnCloseWindow, wx.ACCEL_CTRL, ord("W")), (self.OnRender, wx.ACCEL_CTRL, ord("R")), (self.OnRender, wx.ACCEL_NORMAL, wx.WXK_F5), ] self._initShortcuts() def _initShortcuts(self): """init shortcuts to acceleration table""" accelTable = [] for handler, entry, kdb in self.shortcuts_table: wxId = NewId() self.Bind(wx.EVT_MENU, handler, id=wxId) accelTable.append((entry, kdb, wxId)) self.SetAcceleratorTable(wx.AcceleratorTable(accelTable)) def _initMap(self, Map): """Initialize map display, set dimensions and map region""" if not grass.find_program("g.region", "--help"): sys.exit( _("GRASS module '%s' not found. Unable to start map " "display window.") % "g.region") Debug.msg(2, "MapPanel._initMap():") Map.ChangeMapSize(self.GetClientSize()) Map.region = Map.GetRegion() # g.region -upgc # self.Map.SetRegion() # adjust region to match display window def _resize(self): Debug.msg(1, "MapPanel_resize():") wm, hw = self.MapWindow.GetClientSize() wf, hf = self.GetSize() dw = wf - wm dh = hf - hw self.SetSize((wf + dw, hf + dh)) def _onToggleTool(self, id): if self._toolSwitcher.IsToolInGroup(id, "mouseUse"): self.GetWindow().UnregisterAllHandlers() def OnSize(self, event): """Adjust statusbar on changing size""" # reposition checkbox in statusbar self.StatusbarReposition() # update statusbar self.StatusbarUpdate() def OnCloseWindow(self, event): self.Destroy() def GetToolSwitcher(self): return self._toolSwitcher def SetProperty(self, name, value): """Sets property""" if hasattr(self.mapWindowProperties, name): setattr(self.mapWindowProperties, name, value) else: self.statusbarManager.SetProperty(name, value) def GetProperty(self, name): """Returns property""" if hasattr(self.mapWindowProperties, name): return getattr(self.mapWindowProperties, name) else: return self.statusbarManager.GetProperty(name) def HasProperty(self, name): """Checks whether object has property""" return self.statusbarManager.HasProperty(name) def GetPPM(self): """Get pixel per meter .. todo:: now computed every time, is it necessary? .. todo:: enable user to specify ppm (and store it in UserSettings) """ # TODO: need to be fixed... # screen X region problem # user should specify ppm dc = wx.ScreenDC() dpSizePx = wx.DisplaySize() # display size in pixels dpSizeMM = wx.DisplaySizeMM() # display size in mm (system) dpSizeIn = (dpSizeMM[0] / 25.4, dpSizeMM[1] / 25.4) # inches sysPpi = dc.GetPPI() comPpi = (dpSizePx[0] / dpSizeIn[0], dpSizePx[1] / dpSizeIn[1]) ppi = comPpi # pixel per inch ppm = ((ppi[0] / 2.54) * 100, (ppi[1] / 2.54) * 100) # pixel per meter Debug.msg( 4, "MapPanelBase.GetPPM(): size: px=%d,%d mm=%f,%f " "in=%f,%f ppi: sys=%d,%d com=%d,%d; ppm=%f,%f" % ( dpSizePx[0], dpSizePx[1], dpSizeMM[0], dpSizeMM[1], dpSizeIn[0], dpSizeIn[1], sysPpi[0], sysPpi[1], comPpi[0], comPpi[1], ppm[0], ppm[1], ), ) return ppm def SetMapScale(self, value, map=None): """Set current map scale :param value: scale value (n if scale is 1:n) :param map: Map instance (if none self.Map is used) """ if not map: map = self.Map region = self.Map.region dEW = value * (region["cols"] / self.GetPPM()[0]) dNS = value * (region["rows"] / self.GetPPM()[1]) region["n"] = region["center_northing"] + dNS / 2.0 region["s"] = region["center_northing"] - dNS / 2.0 region["w"] = region["center_easting"] - dEW / 2.0 region["e"] = region["center_easting"] + dEW / 2.0 # add to zoom history self.GetWindow().ZoomHistory(region["n"], region["s"], region["e"], region["w"]) def GetMapScale(self, map=None): """Get current map scale :param map: Map instance (if none self.Map is used) """ if not map: map = self.GetMap() region = map.region ppm = self.GetPPM() heightCm = region["rows"] / ppm[1] * 100 widthCm = region["cols"] / ppm[0] * 100 Debug.msg( 4, "MapPanel.GetMapScale(): width_cm=%f, height_cm=%f" % (widthCm, heightCm)) xscale = (region["e"] - region["w"]) / (region["cols"] / ppm[0]) yscale = (region["n"] - region["s"]) / (region["rows"] / ppm[1]) scale = (xscale + yscale) / 2.0 Debug.msg( 3, "MapPanel.GetMapScale(): xscale=%f, yscale=%f -> scale=%f" % (xscale, yscale, scale), ) return scale def GetProgressBar(self): """Returns progress bar Progress bar can be used by other classes. """ return self.statusbarManager.GetProgressBar() def GetMap(self): """Returns current map (renderer) instance""" raise NotImplementedError("GetMap") def GetWindow(self): """Returns current map window""" raise NotImplementedError("GetWindow") def GetWindows(self): """Returns list of map windows""" raise NotImplementedError("GetWindows") def GetMapToolbar(self): """Returns toolbar with zooming tools""" raise NotImplementedError("GetMapToolbar") def GetToolbar(self, name): """Returns toolbar if exists and is active, else None.""" if name in self.toolbars and self.toolbars[name].IsShown(): return self.toolbars[name] return None def StatusbarUpdate(self): """Update statusbar content""" if self.statusbarManager: Debug.msg(5, "MapPanelBase.StatusbarUpdate()") self.statusbarManager.Update() def IsAutoRendered(self): """Check if auto-rendering is enabled""" # TODO: this is now not the right place to access this attribute # TODO: add mapWindowProperties to init parameters # and pass the right object in the init of derived class? # or do not use this method at all, let mapwindow decide return self.mapWindowProperties.autoRender def CoordinatesChanged(self): """Shows current coordinates on statusbar.""" # assuming that the first mode is coordinates # probably shold not be here but good solution is not available now if self.statusbarManager: if self.statusbarManager.GetMode() == 0: self.statusbarManager.ShowItem("coordinates") def CreateStatusbar(self, statusbarItems): """Create statusbar (default items).""" # create statusbar and its manager statusbar = wx.StatusBar(self, id=wx.ID_ANY) statusbar.SetMinHeight(24) statusbar.SetFieldsCount(3) statusbar.SetStatusWidths([-6, -2, -1]) self.statusbarManager = sb.SbManager(mapframe=self, statusbar=statusbar) # fill statusbar manager self.statusbarManager.AddStatusbarItemsByClass(statusbarItems, mapframe=self, statusbar=statusbar) self.statusbarManager.AddStatusbarItem( sb.SbRender(self, statusbar=statusbar, position=2)) self.statusbarManager.Update() return statusbar def AddStatusbarPane(self): """Add statusbar as a pane""" self._mgr.AddPane( self.statusbar, wx.aui.AuiPaneInfo().Bottom().MinSize( 30, 30).Fixed().Name("statusbar").CloseButton( False).DestroyOnClose(True).ToolbarPane().Dockable( False).PaneBorder(False).Gripper(False), ) def SetStatusText(self, *args): """Overide wx.StatusBar method""" self.statusbar.SetStatusText(*args) def ShowStatusbar(self, show): """Show/hide statusbar and associated pane""" self._mgr.GetPane("statusbar").Show(show) self._mgr.Update() def IsStatusbarShown(self): """Check if statusbar is shown""" return self._mgr.GetPane("statusbar").IsShown() def StatusbarReposition(self): """Reposition items in statusbar""" if self.statusbarManager: self.statusbarManager.Reposition() def StatusbarEnableLongHelp(self, enable=True): """Enable/disable toolbars long help""" for toolbar in six.itervalues(self.toolbars): if toolbar: toolbar.EnableLongHelp(enable) def ShowAllToolbars(self, show=True): if not show: # hide action = self.RemoveToolbar else: action = self.AddToolbar for toolbar in self.GetToolbarNames(): action(toolbar) def AreAllToolbarsShown(self): return self.GetMapToolbar().IsShown() def GetToolbarNames(self): """Return toolbar names""" return list(self.toolbars.keys()) def AddToolbar(self): """Add defined toolbar to the window""" raise NotImplementedError("AddToolbar") def RemoveToolbar(self, name, destroy=False): """Removes defined toolbar from the window :param name toolbar to remove :param destroy True to destroy otherwise toolbar is only hidden """ self._mgr.DetachPane(self.toolbars[name]) if destroy: self._toolSwitcher.RemoveToolbarFromGroup("mouseUse", self.toolbars[name]) self.toolbars[name].Destroy() self.toolbars.pop(name) else: self.toolbars[name].Hide() self._mgr.Update() def IsPaneShown(self, name): """Check if pane (toolbar, mapWindow ...) of given name is currently shown""" if self._mgr.GetPane(name).IsOk(): return self._mgr.GetPane(name).IsShown() return False def OnRender(self, event): """Re-render map composition (each map layer)""" raise NotImplementedError("OnRender") def OnDraw(self, event): """Re-display current map composition""" self.MapWindow.UpdateMap(render=False) def OnErase(self, event): """Erase the canvas""" self.MapWindow.EraseMap() def OnZoomIn(self, event): """Zoom in the map.""" self.MapWindow.SetModeZoomIn() def OnZoomOut(self, event): """Zoom out the map.""" self.MapWindow.SetModeZoomOut() def _setUpMapWindow(self, mapWindow): """Binds map windows' zoom history signals to map toolbar.""" # enable or disable zoom history tool if self.GetMapToolbar(): mapWindow.zoomHistoryAvailable.connect( lambda: self.GetMapToolbar().Enable("zoomBack", enable=True)) mapWindow.zoomHistoryUnavailable.connect( lambda: self.GetMapToolbar().Enable("zoomBack", enable=False)) mapWindow.mouseMoving.connect(self.CoordinatesChanged) def OnPointer(self, event): """Sets mouse mode to pointer.""" self.MapWindow.SetModePointer() def OnPan(self, event): """Panning, set mouse to drag""" self.MapWindow.SetModePan() def OnZoomBack(self, event): """Zoom last (previously stored position)""" self.MapWindow.ZoomBack() def OnZoomToMap(self, event): """ Set display extents to match selected raster (including NULLs) or vector map. """ self.MapWindow.ZoomToMap(layers=self.Map.GetListOfLayers()) def OnZoomToWind(self, event): """Set display geometry to match computational region settings (set with g.region) """ self.MapWindow.ZoomToWind() def OnZoomToDefault(self, event): """Set display geometry to match default region settings""" self.MapWindow.ZoomToDefault() def OnMapDisplayProperties(self, event): """Show Map Display Properties dialog""" from mapdisp.properties import MapDisplayPropertiesDialog dlg = MapDisplayPropertiesDialog(parent=self, mapframe=self, properties=self.mapWindowProperties) dlg.CenterOnParent() dlg.Show()
class MapFrameBase(wx.Frame): """Base class for map display window Derived class must use (create and initialize) \c statusbarManager or override GetProperty(), SetProperty() and HasProperty() methods. Several methods has to be overriden or \c NotImplementedError("MethodName") will be raised. If derived class enables and disables auto-rendering, it should override IsAutoRendered method. It is expected that derived class will call _setUpMapWindow(). Derived class can has one or more map windows (and map renderes) but implementation of MapFrameBase expects that one window and one map will be current. Current instances of map window and map renderer should be returned by methods GetWindow() and GetMap() respectively. AUI manager is stored in \c self._mgr. """ def __init__(self, parent=None, id=wx.ID_ANY, title='', style=wx.DEFAULT_FRAME_STYLE, auimgr=None, name='', **kwargs): """ .. warning:: Use \a auimgr parameter only if you know what you are doing. :param parent: gui parent :param id: wx id :param title: window title :param style: \c wx.Frame style :param toolbars: array of activated toolbars, e.g. ['map', 'digit'] :param auimgr: AUI manager (if \c None, wx.aui.AuiManager is used) :param name: frame name :param kwargs: arguments passed to \c wx.Frame """ self.parent = parent wx.Frame.__init__( self, parent, id, title, style=style, name=name, **kwargs) # # set the size & system icon # self.SetClientSize(self.GetSize()) self.iconsize = (16, 16) self.SetIcon( wx.Icon( os.path.join( globalvar.ICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO)) # toolbars self.toolbars = {} # # Fancy gui # if auimgr is None: from wx.aui import AuiManager self._mgr = AuiManager(self) else: self._mgr = auimgr # handles switching between tools in different toolbars self._toolSwitcher = ToolSwitcher() self._toolSwitcher.toggleToolChanged.connect(self._onToggleTool) self._initShortcuts() def _initShortcuts(self): # set accelerator table (fullscreen, close window) shortcuts_table = ( (self.OnFullScreen, wx.ACCEL_NORMAL, wx.WXK_F11), (self.OnCloseWindow, wx.ACCEL_CTRL, ord('W')), (self.OnRender, wx.ACCEL_CTRL, ord('R')), (self.OnRender, wx.ACCEL_NORMAL, wx.WXK_F5), ) accelTable = [] for handler, entry, kdb in shortcuts_table: wxId = wx.NewId() self.Bind(wx.EVT_MENU, handler, id=wxId) accelTable.append((entry, kdb, wxId)) self.SetAcceleratorTable(wx.AcceleratorTable(accelTable)) def _initMap(self, Map): """Initialize map display, set dimensions and map region """ if not grass.find_program('g.region', '--help'): sys.exit(_("GRASS module '%s' not found. Unable to start map " "display window.") % 'g.region') Debug.msg(2, "MapFrame._initMap():") Map.ChangeMapSize(self.GetClientSize()) Map.region = Map.GetRegion() # g.region -upgc # self.Map.SetRegion() # adjust region to match display window def _resize(self): Debug.msg(1, "MapFrame._resize():") wm, hw = self.MapWindow.GetClientSize() wf, hf = self.GetSize() dw = wf - wm dh = hf - hw self.SetSize((wf + dw, hf + dh)) def _onToggleTool(self, id): if self._toolSwitcher.IsToolInGroup(id, 'mouseUse'): self.GetWindow().UnregisterAllHandlers() def OnSize(self, event): """Adjust statusbar on changing size""" # reposition checkbox in statusbar self.StatusbarReposition() # update statusbar self.StatusbarUpdate() def OnFullScreen(self, event): """!Switch fullscreen mode, hides also toolbars""" for toolbar in self.toolbars.keys(): self._mgr.GetPane(self.toolbars[toolbar]).Show(self.IsFullScreen()) self._mgr.Update() self.ShowFullScreen(not self.IsFullScreen()) event.Skip() def OnCloseWindow(self, event): self.Destroy() def GetToolSwitcher(self): return self._toolSwitcher def SetProperty(self, name, value): """Sets property""" self.statusbarManager.SetProperty(name, value) def GetProperty(self, name): """Returns property""" return self.statusbarManager.GetProperty(name) def HasProperty(self, name): """Checks whether object has property""" return self.statusbarManager.HasProperty(name) def GetPPM(self): """Get pixel per meter .. todo:: now computed every time, is it necessary? .. todo:: enable user to specify ppm (and store it in UserSettings) """ # TODO: need to be fixed... # screen X region problem # user should specify ppm dc = wx.ScreenDC() dpSizePx = wx.DisplaySize() # display size in pixels dpSizeMM = wx.DisplaySizeMM() # display size in mm (system) dpSizeIn = (dpSizeMM[0] / 25.4, dpSizeMM[1] / 25.4) # inches sysPpi = dc.GetPPI() comPpi = (dpSizePx[0] / dpSizeIn[0], dpSizePx[1] / dpSizeIn[1]) ppi = comPpi # pixel per inch ppm = ((ppi[0] / 2.54) * 100, # pixel per meter (ppi[1] / 2.54) * 100) Debug.msg(4, "MapFrameBase.GetPPM(): size: px=%d,%d mm=%f,%f " "in=%f,%f ppi: sys=%d,%d com=%d,%d; ppm=%f,%f" % (dpSizePx[0], dpSizePx[1], dpSizeMM[0], dpSizeMM[1], dpSizeIn[0], dpSizeIn[1], sysPpi[0], sysPpi[1], comPpi[0], comPpi[1], ppm[0], ppm[1])) return ppm def SetMapScale(self, value, map=None): """Set current map scale :param value: scale value (n if scale is 1:n) :param map: Map instance (if none self.Map is used) """ if not map: map = self.Map region = self.Map.region dEW = value * (region['cols'] / self.GetPPM()[0]) dNS = value * (region['rows'] / self.GetPPM()[1]) region['n'] = region['center_northing'] + dNS / 2. region['s'] = region['center_northing'] - dNS / 2. region['w'] = region['center_easting'] - dEW / 2. region['e'] = region['center_easting'] + dEW / 2. # add to zoom history self.GetWindow().ZoomHistory(region['n'], region['s'], region['e'], region['w']) def GetMapScale(self, map=None): """Get current map scale :param map: Map instance (if none self.Map is used) """ if not map: map = self.GetMap() region = map.region ppm = self.GetPPM() heightCm = region['rows'] / ppm[1] * 100 widthCm = region['cols'] / ppm[0] * 100 Debug.msg(4, "MapFrame.GetMapScale(): width_cm=%f, height_cm=%f" % (widthCm, heightCm)) xscale = (region['e'] - region['w']) / (region['cols'] / ppm[0]) yscale = (region['n'] - region['s']) / (region['rows'] / ppm[1]) scale = (xscale + yscale) / 2. Debug.msg( 3, "MapFrame.GetMapScale(): xscale=%f, yscale=%f -> scale=%f" % (xscale, yscale, scale)) return scale def GetProgressBar(self): """Returns progress bar Progress bar can be used by other classes. """ return self.statusbarManager.GetProgressBar() def GetMap(self): """Returns current map (renderer) instance""" raise NotImplementedError("GetMap") def GetWindow(self): """Returns current map window""" raise NotImplementedError("GetWindow") def GetWindows(self): """Returns list of map windows""" raise NotImplementedError("GetWindows") def GetMapToolbar(self): """Returns toolbar with zooming tools""" raise NotImplementedError("GetMapToolbar") def GetToolbar(self, name): """Returns toolbar if exists and is active, else None. """ if name in self.toolbars and self.toolbars[name].IsShown(): return self.toolbars[name] return None def StatusbarUpdate(self): """Update statusbar content""" if self.statusbarManager: Debug.msg(5, "MapFrameBase.StatusbarUpdate()") self.statusbarManager.Update() def IsAutoRendered(self): """Check if auto-rendering is enabled""" # TODO: this is now not the right place to access this attribute # TODO: add mapWindowProperties to init parameters # and pass the right object in the init of derived class? # or do not use this method at all, let mapwindow decide return self.mapWindowProperties.autoRender def CoordinatesChanged(self): """Shows current coordinates on statusbar. """ # assuming that the first mode is coordinates # probably shold not be here but good solution is not available now if self.statusbarManager: if self.statusbarManager.GetMode() == 0: self.statusbarManager.ShowItem('coordinates') def StatusbarReposition(self): """Reposition items in statusbar""" if self.statusbarManager: self.statusbarManager.Reposition() def StatusbarEnableLongHelp(self, enable=True): """Enable/disable toolbars long help""" for toolbar in self.toolbars.itervalues(): toolbar.EnableLongHelp(enable) def IsStandalone(self): """Check if map frame is standalone""" raise NotImplementedError("IsStandalone") def OnRender(self, event): """Re-render map composition (each map layer) """ raise NotImplementedError("OnRender") def OnDraw(self, event): """Re-display current map composition """ self.MapWindow.UpdateMap(render=False) def OnErase(self, event): """Erase the canvas """ self.MapWindow.EraseMap() def OnZoomIn(self, event): """Zoom in the map.""" self.MapWindow.SetModeZoomIn() def OnZoomOut(self, event): """Zoom out the map.""" self.MapWindow.SetModeZoomOut() def _setUpMapWindow(self, mapWindow): """Binds map windows' zoom history signals to map toolbar.""" # enable or disable zoom history tool if self.GetMapToolbar(): mapWindow.zoomHistoryAvailable.connect( lambda: self.GetMapToolbar().Enable('zoomBack', enable=True)) mapWindow.zoomHistoryUnavailable.connect( lambda: self.GetMapToolbar().Enable('zoomBack', enable=False)) mapWindow.mouseMoving.connect(self.CoordinatesChanged) def OnPointer(self, event): """Sets mouse mode to pointer.""" self.MapWindow.SetModePointer() def OnPan(self, event): """Panning, set mouse to drag """ self.MapWindow.SetModePan() def OnZoomBack(self, event): """Zoom last (previously stored position) """ self.MapWindow.ZoomBack() def OnZoomToMap(self, event): """ Set display extents to match selected raster (including NULLs) or vector map. """ self.MapWindow.ZoomToMap(layers=self.Map.GetListOfLayers()) def OnZoomToWind(self, event): """Set display geometry to match computational region settings (set with g.region) """ self.MapWindow.ZoomToWind() def OnZoomToDefault(self, event): """Set display geometry to match default region settings """ self.MapWindow.ZoomToDefault()