simplelmgr.py 18 KB

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