# -*- coding: utf-8 -*- """ @package gui_core.simplelmgr @brief GUI class for simple layer management. Classes: - simplelmgr::SimpleLayerManager - simplelmgr::SimpleLmgrToolbar (C) 2013 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. @author Anna Petrasova (kratochanna gmail.com) """ import os import wx import wx.aui from grass.pydispatch.signal import Signal # needed just for testing if __name__ == '__main__': from grass.script.setup import set_gui_path set_gui_path() from gui_core.toolbars import BaseToolbar, BaseIcons from icons.icon import MetaIcon from gui_core.forms import GUI from gui_core.dialogs import SetOpacityDialog from gui_core.wrap import CheckListBox, Menu from core.utils import GetLayerNameFromCmd from core.gcmd import GError from core.layerlist import LayerList from core.utils import _ SIMPLE_LMGR_RASTER = 1 SIMPLE_LMGR_VECTOR = 2 SIMPLE_LMGR_RASTER3D = 4 SIMPLE_LMGR_RGB = 8 SIMPLE_LMGR_TB_TOP = 16 SIMPLE_LMGR_TB_BOTTOM = 32 SIMPLE_LMGR_TB_LEFT = 64 SIMPLE_LMGR_TB_RIGHT = 128 class SimpleLayerManager(wx.Panel): """Simple layer manager class provides similar functionality to Layertree, but it's just list, not tree.""" def __init__( self, parent, layerList, lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT, toolbarCls=None, modal=False): wx.Panel.__init__(self, parent=parent, name='SimpleLayerManager') self._style = lmgrStyle self._layerList = layerList self._checkList = CheckListBox(self, style=wx.LB_EXTENDED) if not toolbarCls: toolbarCls = SimpleLmgrToolbar self._toolbar = toolbarCls(self, lmgrStyle=self._style) self._auimgr = wx.aui.AuiManager(self) self._modal = modal # d.* dialogs are recreated each time, attempt to hide it resulted # in completely mysterious memory corruption and crash when opening # any dialog with stock labels (wx.ID_OK and so on) # needed in order not to change selection when moving layers self._blockSelectionChanged = False self._checkList.Bind( wx.EVT_LISTBOX, lambda evt: self._selectionChanged()) self._checkList.Bind( wx.EVT_LISTBOX_DCLICK, self.OnLayerChangeProperties) self._checkList.Bind(wx.EVT_CHECKLISTBOX, self.OnLayerChecked) self._checkList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) # signal emitted when somethin in layer list changes self.opacityChanged = Signal('SimpleLayerManager.opacityChanged') self.cmdChanged = Signal('SimpleLayerManager.cmdChanged') self.layerAdded = Signal('SimpleLayerManager.layerAdded') self.layerRemoved = Signal('SimpleLayerManager.layerRemoved') self.layerActivated = Signal('SimpleLayerManager.layerActivated') self.layerMovedUp = Signal('SimpleLayerManager.layerMovedUp') self.layerMovedDown = Signal('SimpleLayerManager.layerMovedDown') # emitted by any change (e.g. for rerendering) self.anyChange = Signal('SimpleLayerManager.layerChange') self._layout() self.SetMinSize((200, -1)) self._update() def _layout(self): self._auimgr.AddPane(self._checkList, wx.aui.AuiPaneInfo(). Name("checklist"). CenterPane(). CloseButton(False). BestSize((self._checkList.GetBestSize()))) paneInfo = wx.aui.AuiPaneInfo(). \ Name("toolbar").Caption(_("Toolbar")).ToolbarPane(). \ CloseButton(False).Layer(1).Gripper(False). \ BestSize((self._toolbar.GetBestSize())) if self._style & SIMPLE_LMGR_TB_LEFT: paneInfo.Left() elif self._style & SIMPLE_LMGR_TB_RIGHT: paneInfo.Right() elif self._style & SIMPLE_LMGR_TB_TOP: paneInfo.Top() else: paneInfo.Bottom() self._auimgr.AddPane(self._toolbar, paneInfo) self._auimgr.Update() def _selectionChanged(self): """Selection was changed externally, updates selection info in layers.""" if self._blockSelectionChanged: return selected = self._checkList.GetSelections() for i, layer in enumerate(self._layerList): layer.Select(i in selected) def OnContextMenu(self, event): """Show context menu. So far offers only copying layer list to clipboard """ if len(self._layerList) < 1: event.Skip() return menu = Menu() llist = [layer.name for layer in self._layerList] texts = [','.join(llist), ','.join(reversed(llist))] labels = [_("Copy map names to clipboard (top to bottom)"), _("Copy map names to clipboard (bottom to top)")] for label, text in zip(labels, texts): id = wx.NewId() self.Bind( wx.EVT_MENU, lambda evt, t=text, id=id: self._copyText(t), id=id) menu.Append(id, label) # show the popup menu self.PopupMenu(menu) menu.Destroy() event.Skip() def _copyText(self, text): """Helper function for copying TODO: move to utils? """ if wx.TheClipboard.Open(): do = wx.TextDataObject() do.SetText(text) wx.TheClipboard.SetData(do) wx.TheClipboard.Close() def OnLayerChecked(self, event): """Layer was (un)checked, update layer's info.""" checkedIdxs = self._checkList.GetChecked() for i, layer in enumerate(self._layerList): if i in checkedIdxs and not layer.IsActive(): layer.Activate() self.layerActivated.emit(index=i, layer=layer) elif i not in checkedIdxs and layer.IsActive(): layer.Activate(False) self.layerActivated.emit(index=i, layer=layer) self.anyChange.emit() event.Skip() def OnAddRaster(self, event): """Opens d.rast dialog and adds layer. Dummy layer is added first.""" cmd = ['d.rast'] layer = self.AddRaster(name='', cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=cmd, completed=(self.GetOptData, layer, '')) event.Skip() def OnAddVector(self, event): """Opens d.vect dialog and adds layer. Dummy layer is added first.""" cmd = ['d.vect'] layer = self.AddVector(name='', cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=cmd, completed=(self.GetOptData, layer, '')) event.Skip() def OnAddRast3d(self, event): """Opens d.rast3d dialog and adds layer. Dummy layer is added first.""" cmd = ['d.rast3d'] layer = self.AddRast3d(name='', cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=cmd, completed=(self.GetOptData, layer, '')) event.Skip() def OnAddRGB(self, event): """Opens d.rgb dialog and adds layer. Dummy layer is added first.""" cmd = ['d.rgb'] layer = self.AddRGB(name='', cmd=cmd, hidden=True, dialog=None) GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=cmd, completed=(self.GetOptData, layer, '')) event.Skip() def OnRemove(self, event): """Removes selected layers from list.""" layers = self._layerList.GetSelectedLayers(activeOnly=False) for layer in layers: self.layerRemoved.emit( index=self._layerList.GetLayerIndex(layer), layer=layer) self._layerList.RemoveLayer(layer) self._update() self.anyChange.emit() event.Skip() def OnLayerUp(self, event): """Moves selected layers one step up. Note: not completely correct for multiple layers.""" layers = self._layerList.GetSelectedLayers() self._blockSelectionChanged = True for layer in layers: idx = self._layerList.GetLayerIndex(layer) if idx > 0: self.layerMovedUp.emit(index=idx, layer=layer) self._layerList.MoveLayerUp(layer) self._update() self._blockSelectionChanged = False self.anyChange.emit() event.Skip() def OnLayerDown(self, event): """Moves selected layers one step down. Note: not completely correct for multiple layers.""" layers = self._layerList.GetSelectedLayers() self._blockSelectionChanged = True for layer in layers: idx = self._layerList.GetLayerIndex(layer) if idx < len(self._layerList) - 1: self.layerMovedDown.emit( index=self._layerList.GetLayerIndex(layer), layer=layer) self._layerList.MoveLayerDown(layer) self._update() self._blockSelectionChanged = False self.anyChange.emit() event.Skip() def OnLayerChangeProperties(self, event): """Opens module dialog to edit layer command.""" layers = self._layerList.GetSelectedLayers() if not layers or len(layers) > 1: return self._layerChangeProperties(layers[0]) event.Skip() def _layerChangeProperties(self, layer): """Opens new module dialog or recycles it.""" GUI(parent=self, giface=None, modal=self._modal).ParseCommand( cmd=layer.cmd, completed=(self.GetOptData, layer, '')) def OnLayerChangeOpacity(self, event): """Opacity of a layer is changing.""" layers = self._layerList.GetSelectedLayers() if not layers or len(layers) > 1: return layer = layers[0] dlg = SetOpacityDialog(self, opacity=layer.opacity, title=_("Set opacity of <%s>") % layer.name) dlg.applyOpacity.connect(lambda value: self._setLayerOpacity(layer, value)) dlg.CentreOnParent() if dlg.ShowModal() == wx.ID_OK: self._setLayerOpacity(layer, dlg.GetOpacity()) dlg.Destroy() event.Skip() def _setLayerOpacity(self, layer, value): """Sets layer's opacity.'""" layer.opacity = value self._update() self.opacityChanged.emit( index=self._layerList.GetLayerIndex(layer), layer=layer) self.anyChange.emit() def _update(self): """Updates checklistbox according to layerList structure.""" items = [] active = [] selected = [] # remove hidden (temporary) layers first for layer in reversed(self._layerList): if layer.hidden: self._layerList.RemoveLayer(layer) for layer in self._layerList: if layer.opacity < 1: items.append( "{name} (opacity {opacity}%)".format( name=layer.name, opacity=int( layer.opacity * 100))) else: items.append(layer.name) active.append(layer.IsActive()) selected.append(layer.IsSelected()) self._checkList.SetItems(items) for i, check in enumerate(active): self._checkList.Check(i, check) for i, layer in enumerate(self._layerList): if selected[i]: self._checkList.Select(i) else: self._checkList.Deselect(i) def GetOptData(self, dcmd, layer, params, propwin): """Handler for module dialogs.""" if dcmd: layer.cmd = dcmd layer.selected = True mapName, found = GetLayerNameFromCmd(dcmd) if found: try: if layer.hidden: layer.hidden = False signal = self.layerAdded else: signal = self.cmdChanged layer.name = mapName signal.emit( index=self._layerList.GetLayerIndex(layer), layer=layer) except ValueError as e: self._layerList.RemoveLayer(layer) GError(parent=self, message=str(e), showTraceback=False) self._update() self.anyChange.emit() def AddRaster(self, name, cmd, hidden, dialog): """Ads new raster layer.""" layer = self._layerList.AddNewLayer(name=name, mapType='raster', active=True, cmd=cmd, hidden=hidden) return layer def AddRast3d(self, name, cmd, hidden, dialog): """Ads new raster3d layer.""" layer = self._layerList.AddNewLayer(name=name, mapType='raster_3d', active=True, cmd=cmd, hidden=hidden) return layer def AddVector(self, name, cmd, hidden, dialog): """Ads new vector layer.""" layer = self._layerList.AddNewLayer(name=name, mapType='vector', active=True, cmd=cmd, hidden=hidden) return layer def AddRGB(self, name, cmd, hidden, dialog): """Ads new vector layer.""" layer = self._layerList.AddNewLayer(name=name, mapType='rgb', active=True, cmd=cmd, hidden=hidden) return layer def GetLayerInfo(self, layer, key): """Just for compatibility, should be removed in the future""" value = getattr(layer, key) # hack to return empty list, required in OnCancel in forms # not sure why it should be empty if key == 'cmd' and len(value) == 1: return [] return value def Delete(self, layer): """Just for compatibility, should be removed in the future""" self._layerList.RemoveLayer(layer) class SimpleLmgrToolbar(BaseToolbar): """Toolbar of simple layer manager. Style of the toolbar can be changed (horizontal, vertical, which map types to include). """ def __init__(self, parent, lmgrStyle): """Toolbar constructor """ self._style = lmgrStyle if lmgrStyle & (SIMPLE_LMGR_TB_LEFT | SIMPLE_LMGR_TB_RIGHT): direction = wx.TB_VERTICAL else: direction = wx.TB_HORIZONTAL BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction) self.InitToolbar(self._getToolbarData(self._toolbarData())) # realize the toolbar self.Realize() def _toolbarData(self): """Toolbar data""" data = [('edit', icons['edit'], self.parent.OnLayerChangeProperties), ('remove', icons['remove'], self.parent.OnRemove), (None, ), ('up', icons['up'], self.parent.OnLayerUp), ('down', icons['down'], self.parent.OnLayerDown), (None, ), ('opacity', icons['opacity'], self.parent.OnLayerChangeOpacity), ] if self._style & SIMPLE_LMGR_RASTER3D: data.insert(0, ('addRaster3d', icons['addRast3d'], self.parent.OnAddRast3d)) if self._style & SIMPLE_LMGR_RGB: data.insert(0, ('addRGB', icons['addRGB'], self.parent.OnAddRGB)) if self._style & SIMPLE_LMGR_VECTOR: data.insert(0, ('addVector', BaseIcons['addVect'], self.parent.OnAddVector)) if self._style & SIMPLE_LMGR_RASTER: data.insert(0, ('addRaster', BaseIcons['addRast'], self.parent.OnAddRaster)) return data icons = { 'remove': MetaIcon(img='layer-remove', label=_("Remove"), desc=_("Remove selected map(s) from list")), 'up': MetaIcon(img='layer-up', label=_("Layer up"), desc=_("Move selected layer(s) up")), 'down': MetaIcon(img='layer-down', label=_("Layer down"), desc=_("Move selected layer(s) down")), 'edit': MetaIcon(img='layer-edit', label=_("Edit layer properties"), desc=_("Edit layer properties")), 'opacity': MetaIcon(img='layer-opacity', label=_("Change opacity"), desc=_("Change layer opacity")), 'addRast3d': MetaIcon(img='layer-raster3d-add', label=_("Add 3D raster map layer"), desc=_("Add 3D raster map layer")), 'addRGB': MetaIcon(img='layer-rgb-add', label=_('Add RGB map layer')) } class TestFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent=parent, title="Simple layer manager test") SimpleLayerManager(parent=self, layerList=LayerList()) def test(): app = wx.App() frame = TestFrame(None) frame.Show() app.MainLoop() if __name__ == '__main__': test()