simplelmgr.py 17 KB

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