simplelmgr.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. # -*- coding: utf-8 -*-
  2. """!
  3. @package gui_core.simplelmgr
  4. @brief GUI class for simple layer management.
  5. Classes:
  6. - simplelmgr::SimpleLayerManager
  7. - simplelmgr::SimpleLmgrToolbar
  8. (C) 2013 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Anna Petrasova (kratochanna gmail.com)
  12. """
  13. import os
  14. import sys
  15. # adding a path to wxGUI modules
  16. if __name__ == '__main__':
  17. WXGUIBASE = os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython')
  18. if WXGUIBASE not in sys.path:
  19. sys.path.append(WXGUIBASE)
  20. import wx
  21. import wx.aui
  22. from grass.pydispatch.signal import Signal
  23. from gui_core.toolbars import BaseToolbar, BaseIcons
  24. from icons.icon import MetaIcon
  25. from gui_core.forms import GUI
  26. from gui_core.dialogs import SetOpacityDialog
  27. from core.utils import GetLayerNameFromCmd
  28. from core.gcmd import GError
  29. from core.layerlist import LayerList
  30. SIMPLE_LMGR_RASTER = 1
  31. SIMPLE_LMGR_VECTOR = 2
  32. SIMPLE_LMGR_RASTER3D = 4
  33. SIMPLE_LMGR_TB_TOP = 8
  34. SIMPLE_LMGR_TB_BOTTOM = 16
  35. SIMPLE_LMGR_TB_LEFT = 32
  36. SIMPLE_LMGR_TB_RIGHT = 64
  37. class SimpleLayerManager(wx.Panel):
  38. """!Simple layer manager class provides similar functionality to
  39. Layertree, but it's just list, not tree."""
  40. def __init__(self, parent, layerList,
  41. lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR | SIMPLE_LMGR_TB_LEFT,
  42. toolbarCls=None, modal=False):
  43. wx.Panel.__init__(self, parent=parent, name='SimpleLayerManager')
  44. self._style = lmgrStyle
  45. self._layerList = layerList
  46. self._checkList = wx.CheckListBox(self, style=wx.LB_EXTENDED)
  47. if not toolbarCls:
  48. toolbarCls = SimpleLmgrToolbar
  49. self._toolbar = toolbarCls(self, lmgrStyle=self._style)
  50. self._auimgr = wx.aui.AuiManager(self)
  51. self._modal = modal
  52. # d.* dialogs are recreated each time, attempt to hide it resulted
  53. # in completely mysterious memory corruption and crash when opening
  54. # any dialog with stock labels (wx.ID_OK and so on)
  55. # needed in order not to change selection when moving layers
  56. self._blockSelectionChanged = False
  57. self._checkList.Bind(wx.EVT_LISTBOX, lambda evt: self._selectionChanged())
  58. self._checkList.Bind(wx.EVT_LISTBOX_DCLICK, self.OnLayerChangeProperties)
  59. self._checkList.Bind(wx.EVT_CHECKLISTBOX, self.OnLayerChecked)
  60. self._checkList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
  61. # signal emitted when somethin in layer list changes
  62. self.opacityChanged = Signal('SimpleLayerManager.opacityChanged')
  63. self.cmdChanged = Signal('SimpleLayerManager.cmdChanged')
  64. self.layerAdded = Signal('SimpleLayerManager.layerAdded')
  65. self.layerRemoved = Signal('SimpleLayerManager.layerRemoved')
  66. self.layerActivated = Signal('SimpleLayerManager.layerActivated')
  67. self.layerMovedUp = Signal('SimpleLayerManager.layerMovedUp')
  68. self.layerMovedDown = Signal('SimpleLayerManager.layerMovedDown')
  69. # emitted by any change (e.g. for rerendering)
  70. self.anyChange = Signal('SimpleLayerManager.layerChange')
  71. self._layout()
  72. self.SetMinSize((200, -1))
  73. self._update()
  74. def _layout(self):
  75. self._auimgr.AddPane(self._checkList,
  76. wx.aui.AuiPaneInfo().
  77. Name("checklist").
  78. CenterPane().
  79. CloseButton(False).
  80. BestSize((self._checkList.GetBestSize())))
  81. paneInfo = wx.aui.AuiPaneInfo(). \
  82. Name("toolbar").Caption(_("Toolbar")).ToolbarPane(). \
  83. CloseButton(False).Layer(1).Gripper(False). \
  84. BestSize((self._toolbar.GetBestSize()))
  85. if self._style & SIMPLE_LMGR_TB_LEFT:
  86. paneInfo.Left()
  87. elif self._style & SIMPLE_LMGR_TB_RIGHT:
  88. paneInfo.Right()
  89. elif self._style & SIMPLE_LMGR_TB_TOP:
  90. paneInfo.Top()
  91. else:
  92. paneInfo.Bottom()
  93. self._auimgr.AddPane(self._toolbar, paneInfo)
  94. self._auimgr.Update()
  95. def _selectionChanged(self):
  96. """!Selection was changed externally,
  97. updates selection info in layers."""
  98. if self._blockSelectionChanged:
  99. return
  100. selected = self._checkList.GetSelections()
  101. for i, layer in enumerate(self._layerList):
  102. layer.Select(i in selected)
  103. def OnContextMenu(self, event):
  104. """!Show context menu.
  105. So far offers only copying layer list to clipboard
  106. """
  107. if len(self._layerList) < 1:
  108. event.Skip()
  109. return
  110. menu = wx.Menu()
  111. llist = [layer.name for layer in self._layerList]
  112. texts = [','.join(llist), ','.join(reversed(llist))]
  113. labels = [_("Copy map names to clipboard (top to bottom)"),
  114. _("Copy map names to clipboard (bottom to top)")]
  115. for label, text in zip(labels, texts):
  116. id = wx.NewId()
  117. self.Bind(wx.EVT_MENU, lambda evt, t=text, id=id: self._copyText(t), id=id)
  118. menu.Append(id, label)
  119. # show the popup menu
  120. self.PopupMenu(menu)
  121. menu.Destroy()
  122. event.Skip()
  123. def _copyText(self, text):
  124. """!Helper function for copying
  125. TODO: move to utils?
  126. """
  127. if wx.TheClipboard.Open():
  128. do = wx.TextDataObject()
  129. do.SetText(text)
  130. wx.TheClipboard.SetData(do)
  131. wx.TheClipboard.Close()
  132. def OnLayerChecked(self, event):
  133. """!Layer was (un)checked, update layer's info."""
  134. checkedIdxs = self._checkList.GetChecked()
  135. for i, layer in enumerate(self._layerList):
  136. if i in checkedIdxs and not layer.IsActive():
  137. layer.Activate()
  138. self.layerActivated.emit(index=i, layer=layer)
  139. elif i not in checkedIdxs and layer.IsActive():
  140. layer.Activate(False)
  141. self.layerActivated.emit(index=i, layer=layer)
  142. self.anyChange.emit()
  143. event.Skip()
  144. def OnAddRaster(self, event):
  145. """!Opens d.rast dialog and adds layer.
  146. Dummy layer is added first."""
  147. cmd = ['d.rast']
  148. layer = self.AddRaster(name='', cmd=cmd, hidden=True, dialog=None)
  149. GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
  150. completed=(self.GetOptData, layer, ''))
  151. event.Skip()
  152. def OnAddVector(self, event):
  153. """!Opens d.vect dialog and adds layer.
  154. Dummy layer is added first."""
  155. cmd = ['d.vect']
  156. layer = self.AddVector(name='', cmd=cmd, hidden=True, dialog=None)
  157. GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
  158. completed=(self.GetOptData, layer, ''))
  159. event.Skip()
  160. def OnAddRast3d(self, event):
  161. """!Opens d.rast3d dialog and adds layer.
  162. Dummy layer is added first."""
  163. cmd = ['d.rast3d']
  164. layer = self.AddRast3d(name='', cmd=cmd, hidden=True, dialog=None)
  165. GUI(parent=self, giface=None, modal=self._modal).ParseCommand(cmd=cmd,
  166. completed=(self.GetOptData, layer, ''))
  167. event.Skip()
  168. def OnRemove(self, event):
  169. """!Removes selected layers from list."""
  170. layers = self._layerList.GetSelectedLayers(activeOnly=False)
  171. for layer in layers:
  172. self.layerRemoved.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
  173. self._layerList.RemoveLayer(layer)
  174. self._update()
  175. self.anyChange.emit()
  176. event.Skip()
  177. def OnLayerUp(self, event):
  178. """!Moves selected layers one step up.
  179. Note: not completely correct for multiple layers."""
  180. layers = self._layerList.GetSelectedLayers()
  181. self._blockSelectionChanged = True
  182. for layer in layers:
  183. idx = self._layerList.GetLayerIndex(layer)
  184. if idx > 0:
  185. self.layerMovedUp.emit(index=idx, layer=layer)
  186. self._layerList.MoveLayerUp(layer)
  187. self._update()
  188. self._blockSelectionChanged = False
  189. self.anyChange.emit()
  190. event.Skip()
  191. def OnLayerDown(self, event):
  192. """!Moves selected layers one step down.
  193. Note: not completely correct for multiple layers."""
  194. layers = self._layerList.GetSelectedLayers()
  195. self._blockSelectionChanged = True
  196. for layer in layers:
  197. idx = self._layerList.GetLayerIndex(layer)
  198. if idx < len(self._layerList) - 1:
  199. self.layerMovedDown.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
  200. self._layerList.MoveLayerDown(layer)
  201. self._update()
  202. self._blockSelectionChanged = False
  203. self.anyChange.emit()
  204. event.Skip()
  205. def OnLayerChangeProperties(self, event):
  206. """Opens module dialog to edit layer command."""
  207. layers = self._layerList.GetSelectedLayers()
  208. if not layers or len(layers) > 1:
  209. return
  210. self._layerChangeProperties(layers[0])
  211. event.Skip()
  212. def _layerChangeProperties(self, layer):
  213. """!Opens new module dialog or recycles it."""
  214. GUI(parent=self, giface=None,
  215. modal=self._modal).ParseCommand(cmd=layer.cmd,
  216. completed=(self.GetOptData, layer, ''))
  217. def OnLayerChangeOpacity(self, event):
  218. """!Opacity of a layer is changing."""
  219. layers = self._layerList.GetSelectedLayers()
  220. if not layers or len(layers) > 1:
  221. return
  222. layer = layers[0]
  223. dlg = SetOpacityDialog(self, opacity=layer.opacity,
  224. title=_("Set opacity of <%s>") % layer.name)
  225. dlg.applyOpacity.connect(lambda value:
  226. self._setLayerOpacity(layer, value))
  227. dlg.CentreOnParent()
  228. if dlg.ShowModal() == wx.ID_OK:
  229. self._setLayerOpacity(layer, dlg.GetOpacity())
  230. dlg.Destroy()
  231. event.Skip()
  232. def _setLayerOpacity(self, layer, value):
  233. """!Sets layer's opacity.'"""
  234. layer.opacity = value
  235. self._update()
  236. self.opacityChanged.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
  237. self.anyChange.emit()
  238. def _update(self):
  239. """!Updates checklistbox according to layerList structure."""
  240. items = []
  241. active = []
  242. selected = []
  243. for layer in self._layerList:
  244. if layer.opacity < 1:
  245. items.append("{name} (opacity {opacity}%)".format(name=layer.name,
  246. opacity=int(layer.opacity * 100)))
  247. else:
  248. items.append(layer.name)
  249. active.append(layer.IsActive())
  250. selected.append(layer.IsSelected())
  251. self._checkList.SetItems(items)
  252. for i, check in enumerate(active):
  253. self._checkList.Check(i, check)
  254. for i, layer in enumerate(self._layerList):
  255. if selected[i]:
  256. self._checkList.Select(i)
  257. else:
  258. self._checkList.Deselect(i)
  259. def GetOptData(self, dcmd, layer, params, propwin):
  260. """!Handler for module dialogs."""
  261. if dcmd:
  262. layer.cmd = dcmd
  263. layer.selected = True
  264. mapName, found = GetLayerNameFromCmd(dcmd)
  265. if found:
  266. try:
  267. if layer.hidden:
  268. layer.hidden = False
  269. signal = self.layerAdded
  270. else:
  271. signal = self.cmdChanged
  272. layer.name = mapName
  273. signal.emit(index=self._layerList.GetLayerIndex(layer), layer=layer)
  274. except ValueError, e:
  275. self._layerList.RemoveLayer(layer)
  276. GError(parent=self,
  277. message=str(e),
  278. showTraceback=False)
  279. self._update()
  280. self.anyChange.emit()
  281. def AddRaster(self, name, cmd, hidden, dialog):
  282. """!Ads new raster layer."""
  283. layer = self._layerList.AddNewLayer(name=name, mapType='rast',
  284. active=True,
  285. cmd=cmd, hidden=hidden)
  286. return layer
  287. def AddRast3d(self, name, cmd, hidden, dialog):
  288. """!Ads new raster3d layer."""
  289. layer = self._layerList.AddNewLayer(name=name, mapType='rast3d',
  290. active=True,
  291. cmd=cmd, hidden=hidden)
  292. return layer
  293. def AddVector(self, name, cmd, hidden, dialog):
  294. """!Ads new vector layer."""
  295. layer = self._layerList.AddNewLayer(name=name, mapType='vect',
  296. active=True,
  297. cmd=cmd, hidden=hidden)
  298. return layer
  299. def GetLayerInfo(self, layer, key):
  300. """!Just for compatibility, should be removed in the future"""
  301. value = getattr(layer, key)
  302. # hack to return empty list, required in OnCancel in forms
  303. # not sure why it should be empty
  304. if key == 'cmd' and len(value) == 1:
  305. return []
  306. return value
  307. def Delete(self, layer):
  308. """!Just for compatibility, should be removed in the future"""
  309. self._layerList.RemoveLayer(layer)
  310. class SimpleLmgrToolbar(BaseToolbar):
  311. """!Toolbar of simple layer manager.
  312. Style of the toolbar can be changed (horizontal,
  313. vertical, which map types to include).
  314. """
  315. def __init__(self, parent, lmgrStyle):
  316. """!Toolbar constructor
  317. """
  318. self._style = lmgrStyle
  319. if lmgrStyle & (SIMPLE_LMGR_TB_LEFT | SIMPLE_LMGR_TB_RIGHT):
  320. direction = wx.TB_VERTICAL
  321. else:
  322. direction = wx.TB_HORIZONTAL
  323. BaseToolbar.__init__(self, parent, style=wx.NO_BORDER | direction)
  324. self.InitToolbar(self._getToolbarData(self._toolbarData()))
  325. # realize the toolbar
  326. self.Realize()
  327. def _toolbarData(self):
  328. """!Toolbar data"""
  329. data = [('edit', icons['edit'],
  330. self.parent.OnLayerChangeProperties),
  331. ('remove', icons['remove'],
  332. self.parent.OnRemove),
  333. (None, ),
  334. ('up', icons['up'],
  335. self.parent.OnLayerUp),
  336. ('down', icons['down'],
  337. self.parent.OnLayerDown),
  338. (None, ),
  339. ('opacity', icons['opacity'],
  340. self.parent.OnLayerChangeOpacity),
  341. ]
  342. if self._style & SIMPLE_LMGR_RASTER3D:
  343. data.insert(0, ('addRaster3d', icons['addRast3d'],
  344. self.parent.OnAddRast3d))
  345. if self._style & SIMPLE_LMGR_VECTOR:
  346. data.insert(0, ('addVector', BaseIcons['addVect'],
  347. self.parent.OnAddVector))
  348. if self._style & SIMPLE_LMGR_RASTER:
  349. data.insert(0, ('addRaster', BaseIcons['addRast'],
  350. self.parent.OnAddRaster))
  351. return data
  352. icons = {
  353. 'remove': MetaIcon(img='layer-remove',
  354. label=_("Remove"),
  355. desc=_("Remove selected map(s) from list")),
  356. 'up': MetaIcon(img='layer-up',
  357. label=_("Layer up"),
  358. desc=_("Move selected layer(s) up")),
  359. 'down': MetaIcon(img='layer-down',
  360. label=_("Layer down"),
  361. desc=_("Move selected layer(s) down")),
  362. 'edit': MetaIcon(img='layer-edit',
  363. label=_("Edit layer properties"),
  364. desc=_("Edit layer properties")),
  365. 'opacity': MetaIcon(img='layer-opacity',
  366. label=_("Change opacity"),
  367. desc=_("Change layer opacity")),
  368. 'addRast3d': MetaIcon(img='layer-raster3d-add',
  369. label=_("Add 3D raster map layer"),
  370. desc=_("Add 3D raster map layer")),
  371. }
  372. class TestFrame(wx.Frame):
  373. def __init__(self, parent):
  374. wx.Frame.__init__(self, parent=parent)
  375. SimpleLayerManager(parent=self, layerList=LayerList())
  376. def test():
  377. app = wx.App()
  378. frame = TestFrame(None)
  379. frame.Show()
  380. app.MainLoop()
  381. if __name__ == '__main__':
  382. test()