simplelmgr.py 17 KB

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