tree.py 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. """
  2. @package datacatalog::tree
  3. @brief Data catalog tree classes
  4. Classes:
  5. - datacatalog::LocationMapTree
  6. - datacatalog::DataCatalogTree
  7. (C) 2014-2018 by Tereza Fiedlerova, and the GRASS Development Team
  8. This program is free software under the GNU General Public
  9. License (>=v2). Read the file COPYING that comes with GRASS
  10. for details.
  11. @author Tereza Fiedlerova
  12. @author Anna Petrasova (kratochanna gmail com)
  13. """
  14. import os
  15. import re
  16. import copy
  17. from multiprocessing import Process, Queue, cpu_count
  18. import wx
  19. from core.gcmd import RunCommand, GError, GMessage, GWarning
  20. from core.utils import GetListOfLocations
  21. from core.debug import Debug
  22. from gui_core.dialogs import TextEntryDialog
  23. from core.giface import StandaloneGrassInterface
  24. from core.treemodel import TreeModel, DictNode
  25. from gui_core.treeview import TreeView
  26. from gui_core.wrap import Menu
  27. from datacatalog.dialogs import CatalogReprojectionDialog
  28. from grass.pydispatch.signal import Signal
  29. import grass.script as gscript
  30. from grass.script import gisenv
  31. from grass.exceptions import CalledModuleError
  32. def filterModel(model, element=None, name=None):
  33. """Filter tree model based on type or name of map using regular expressions.
  34. Copies tree and remove nodes which don't match."""
  35. fmodel = copy.deepcopy(model)
  36. nodesToRemove = []
  37. if name:
  38. try:
  39. regex = re.compile(name)
  40. except:
  41. return fmodel
  42. for gisdbase in fmodel.root.children:
  43. for location in gisdbase.children:
  44. for mapset in location.children:
  45. for elem in mapset.children:
  46. if element and elem.label != element:
  47. nodesToRemove.append(elem)
  48. continue
  49. for node in elem.children:
  50. if name and regex.search(node.label) is None:
  51. nodesToRemove.append(node)
  52. for node in reversed(nodesToRemove):
  53. fmodel.RemoveNode(node)
  54. cleanUpTree(fmodel)
  55. return fmodel
  56. def cleanUpTree(model):
  57. """Removes empty element/mapsets/locations nodes.
  58. It first removes empty elements, then mapsets, then locations"""
  59. # removes empty elements
  60. nodesToRemove = []
  61. for gisdbase in model.root.children:
  62. for location in gisdbase.children:
  63. for mapset in location.children:
  64. for element in mapset.children:
  65. if not element.children:
  66. nodesToRemove.append(element)
  67. for node in reversed(nodesToRemove):
  68. model.RemoveNode(node)
  69. # removes empty mapsets
  70. nodesToRemove = []
  71. for gisdbase in model.root.children:
  72. for location in gisdbase.children:
  73. for mapset in location.children:
  74. if not mapset.children:
  75. nodesToRemove.append(mapset)
  76. for node in reversed(nodesToRemove):
  77. model.RemoveNode(node)
  78. # removes empty locations
  79. nodesToRemove = []
  80. for gisdbase in model.root.children:
  81. for location in gisdbase.children:
  82. if not location.children:
  83. nodesToRemove.append(location)
  84. for node in reversed(nodesToRemove):
  85. model.RemoveNode(node)
  86. def getLocationTree(gisdbase, location, queue, mapsets=None):
  87. """Creates dictionary with mapsets, elements, layers for given location.
  88. Returns tuple with the dictionary and error (or None)"""
  89. tmp_gisrc_file, env = gscript.create_environment(gisdbase, location, 'PERMANENT')
  90. env['GRASS_SKIP_MAPSET_OWNER_CHECK'] = '1'
  91. maps_dict = {}
  92. elements = ['raster', 'raster_3d', 'vector']
  93. try:
  94. if not mapsets:
  95. mapsets = gscript.read_command(
  96. 'g.mapsets',
  97. flags='l',
  98. separator='comma',
  99. quiet=True,
  100. env=env).strip()
  101. except CalledModuleError:
  102. queue.put(
  103. (maps_dict,
  104. _("Failed to read mapsets from location <{l}>.").format(
  105. l=location)))
  106. gscript.try_remove(tmp_gisrc_file)
  107. return
  108. else:
  109. listOfMapsets = mapsets.split(',')
  110. Debug.msg(
  111. 4, "Location <{0}>: {1} mapsets found".format(
  112. location, len(listOfMapsets)))
  113. for each in listOfMapsets:
  114. maps_dict[each] = {}
  115. for elem in elements:
  116. maps_dict[each][elem] = []
  117. try:
  118. maplist = gscript.read_command(
  119. 'g.list', flags='mt', type=elements,
  120. mapset=','.join(listOfMapsets),
  121. quiet=True, env=env).strip()
  122. except CalledModuleError:
  123. queue.put(
  124. (maps_dict,
  125. _("Failed to read maps from location <{l}>.").format(
  126. l=location)))
  127. gscript.try_remove(tmp_gisrc_file)
  128. return
  129. else:
  130. # fill dictionary
  131. listOfMaps = maplist.splitlines()
  132. Debug.msg(
  133. 4, "Location <{0}>: {1} maps found".format(
  134. location, len(listOfMaps)))
  135. for each in listOfMaps:
  136. ltype, wholename = each.split('/')
  137. name, mapset = wholename.split('@')
  138. maps_dict[mapset][ltype].append(name)
  139. queue.put((maps_dict, None))
  140. gscript.try_remove(tmp_gisrc_file)
  141. def map_exists(name, element, env, mapset=None):
  142. """Check is map is present in the mapset given in the environment
  143. :param name: name of the map
  144. :param element: data type ('raster', 'raster_3d', and 'vector')
  145. :param env environment created by function gscript.create_environment
  146. """
  147. if not mapset:
  148. mapset = gscript.run_command('g.mapset', flags='p', env=env).strip()
  149. # change type to element used by find file
  150. if element == 'raster':
  151. element = 'cell'
  152. elif element == 'raster_3d':
  153. element = 'grid3'
  154. # g.findfile returns non-zero when file was not found
  155. # se we ignore return code and just focus on stdout
  156. process = gscript.start_command(
  157. 'g.findfile',
  158. flags='n',
  159. element=element,
  160. file=name,
  161. mapset=mapset,
  162. stdout=gscript.PIPE,
  163. stderr=gscript.PIPE,
  164. env=env)
  165. output, errors = process.communicate()
  166. info = gscript.parse_key_val(output, sep='=')
  167. # file is the key questioned in grass.script.core find_file()
  168. # return code should be equivalent to checking the output
  169. if info['file']:
  170. return True
  171. else:
  172. return False
  173. class NameEntryDialog(TextEntryDialog):
  174. def __init__(self, element, mapset, env, **kwargs):
  175. TextEntryDialog.__init__(self, **kwargs)
  176. self._element = element
  177. self._mapset = mapset
  178. self._env = env
  179. id_OK = self.GetAffirmativeId()
  180. self.Bind(wx.EVT_BUTTON, self.OnOK, self.FindWindowById(id_OK))
  181. def OnOK(self, event):
  182. new = self.GetValue()
  183. if not new:
  184. return
  185. if map_exists(new, self._element, self._env, self._mapset):
  186. dlg = wx.MessageDialog(
  187. self,
  188. message=_(
  189. "Map of type {elem} <{name}> already exists in mapset <{mapset}>. "
  190. "Do you want to overwrite it?").format(
  191. elem=self._element,
  192. name=new,
  193. mapset=self._mapset),
  194. caption=_("Overwrite?"),
  195. style=wx.YES_NO)
  196. if dlg.ShowModal() == wx.ID_YES:
  197. dlg.Destroy()
  198. self._env['GRASS_OVERWRITE'] = '1'
  199. self.EndModal(wx.ID_OK)
  200. else:
  201. dlg.Destroy()
  202. return
  203. else:
  204. self.EndModal(wx.ID_OK)
  205. class DataCatalogNode(DictNode):
  206. """Node representing item in datacatalog."""
  207. def __init__(self, label, data=None):
  208. super(DataCatalogNode, self).__init__(label=label, data=data)
  209. def match(self, **kwargs):
  210. """Method used for searching according to given parameters.
  211. :param value: dictionary value to be matched
  212. :param key: data dictionary key
  213. """
  214. if not kwargs:
  215. return False
  216. for key in kwargs:
  217. if not (key in self.data and self.data[key] == kwargs[key]):
  218. return False
  219. return True
  220. class LocationMapTree(TreeView):
  221. def __init__(
  222. self, parent, model=None, style=wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS
  223. | wx.TR_LINES_AT_ROOT | wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE):
  224. """Location Map Tree constructor."""
  225. self._model = TreeModel(DataCatalogNode)
  226. self._orig_model = self._model
  227. super(
  228. LocationMapTree,
  229. self).__init__(
  230. parent=parent,
  231. model=self._model,
  232. id=wx.ID_ANY,
  233. style=style)
  234. self.showNotification = Signal('Tree.showNotification')
  235. self.changeMapset = Signal('Tree.changeMapset')
  236. self.changeLocation = Signal('Tree.changeLocation')
  237. self.parent = parent
  238. self.contextMenu.connect(self.OnRightClick)
  239. self.itemActivated.connect(self.OnDoubleClick)
  240. self._initVariables()
  241. def _initTreeItems(self, locations=None, mapsets=None):
  242. """Add locations, mapsets and layers to the tree.
  243. Runs in multiple processes. Saves resulting data and error."""
  244. # mapsets param currently unused
  245. genv = gisenv()
  246. if not locations:
  247. locations = GetListOfLocations(genv['GISDBASE'])
  248. loc_count = proc_count = 0
  249. queue_list = []
  250. proc_list = []
  251. loc_list = []
  252. nprocs = 4
  253. try:
  254. nprocs = cpu_count()
  255. except NotImplementedError:
  256. nprocs = 4
  257. results = dict()
  258. errors = []
  259. location_nodes = []
  260. nlocations = len(locations)
  261. grassdata_node = self._model.AppendNode(
  262. parent=self._model.root, label=_('GRASS locations in {0}').format(
  263. genv['GISDBASE']), data=dict(
  264. type='grassdata'))
  265. for location in locations:
  266. results[location] = dict()
  267. varloc = self._model.AppendNode(
  268. parent=grassdata_node, label=location, data=dict(
  269. type='location', name=location))
  270. location_nodes.append(varloc)
  271. loc_count += 1
  272. Debug.msg(
  273. 3, "Scanning location <{0}> ({1}/{2})".format(location, loc_count, nlocations))
  274. q = Queue()
  275. p = Process(target=getLocationTree,
  276. args=(genv['GISDBASE'], location, q))
  277. p.start()
  278. queue_list.append(q)
  279. proc_list.append(p)
  280. loc_list.append(location)
  281. proc_count += 1
  282. # Wait for all running processes
  283. if proc_count == nprocs or loc_count == nlocations:
  284. Debug.msg(4, "Process subresults")
  285. for i in range(len(loc_list)):
  286. maps, error = queue_list[i].get()
  287. proc_list[i].join()
  288. if error:
  289. errors.append(error)
  290. for key in sorted(maps.keys()):
  291. mapset_node = self._model.AppendNode(
  292. parent=location_nodes[i],
  293. label=key, data=dict(
  294. type='mapset', name=key))
  295. self._populateMapsetItem(mapset_node, maps[key])
  296. proc_count = 0
  297. proc_list = []
  298. queue_list = []
  299. loc_list = []
  300. location_nodes = []
  301. if errors:
  302. wx.CallAfter(GWarning, '\n'.join(errors))
  303. Debug.msg(1, "Tree filled")
  304. self.RefreshItems()
  305. def InitTreeItems(self):
  306. """Load locations, mapsets and layers in the tree."""
  307. raise NotImplementedError()
  308. def ReloadTreeItems(self):
  309. """Reload locations, mapsets and layers in the tree."""
  310. self._orig_model = self._model
  311. self._model.RemoveNode(self._model.root)
  312. self.InitTreeItems()
  313. def ReloadCurrentMapset(self):
  314. """Reload current mapset tree only."""
  315. def get_first_child(node):
  316. try:
  317. child = mapsetItem.children[0]
  318. except IndexError:
  319. child = None
  320. return child
  321. genv = gisenv()
  322. locationItem, mapsetItem = self.GetCurrentLocationMapsetNode()
  323. if not locationItem or not mapsetItem:
  324. return
  325. if mapsetItem.children:
  326. node = get_first_child(mapsetItem)
  327. while node:
  328. self._model.RemoveNode(node)
  329. node = get_first_child(mapsetItem)
  330. q = Queue()
  331. p = Process(
  332. target=getLocationTree,
  333. args=(
  334. genv['GISDBASE'],
  335. locationItem.data['name'],
  336. q,
  337. mapsetItem.data['name']))
  338. p.start()
  339. maps, error = q.get()
  340. if error:
  341. raise CalledModuleError(error)
  342. self._populateMapsetItem(mapsetItem, maps[mapsetItem.data['name']])
  343. self._orig_model = copy.deepcopy(self._model)
  344. self.RefreshNode(mapsetItem)
  345. self.RefreshItems()
  346. def _populateMapsetItem(self, mapset_node, data):
  347. for elem in data:
  348. if data[elem]:
  349. element_node = self._model.AppendNode(
  350. parent=mapset_node, label=elem,
  351. data=dict(type='element', name=elem))
  352. for layer in data[elem]:
  353. self._model.AppendNode(parent=element_node, label=layer,
  354. data=dict(type=elem, name=layer))
  355. def _popupMenuLayer(self):
  356. """Create popup menu for layers"""
  357. raise NotImplementedError()
  358. def _popupMenuMapset(self):
  359. """Create popup menu for mapsets"""
  360. raise NotImplementedError()
  361. def _popupMenuElement(self):
  362. """Create popup menu for elements"""
  363. raise NotImplementedError()
  364. def _initVariables(self):
  365. """Init variables."""
  366. self.selected_layer = []
  367. self.selected_type = []
  368. self.selected_mapset = []
  369. self.selected_location = []
  370. self.mixed = False
  371. def GetControl(self):
  372. """Returns control itself."""
  373. return self
  374. def DefineItems(self, selected):
  375. """Set selected items."""
  376. self._initVariables()
  377. mixed = []
  378. for item in selected:
  379. type = item.data['type']
  380. if type in ('raster', 'raster_3d', 'vector'):
  381. self.selected_layer.append(item)
  382. self.selected_type.append(item.parent)
  383. self.selected_mapset.append(item.parent.parent)
  384. self.selected_location.append(item.parent.parent.parent)
  385. mixed.append('layer')
  386. elif type == 'element':
  387. self.selected_layer.append(None)
  388. self.selected_type.append(item)
  389. self.selected_mapset.append(item.parent)
  390. self.selected_location.append(item.parent.parent)
  391. mixed.append('element')
  392. elif type == 'mapset':
  393. self.selected_layer.append(None)
  394. self.selected_type.append(None)
  395. self.selected_mapset.append(item)
  396. self.selected_location.append(item.parent)
  397. mixed.append('mapset')
  398. elif type == 'location':
  399. self.selected_layer.append(None)
  400. self.selected_type.append(None)
  401. self.selected_mapset.append(None)
  402. self.selected_location.append(item)
  403. mixed.append('location')
  404. self.mixed = False
  405. if len(set(mixed)) > 1:
  406. self.mixed = True
  407. def OnSelChanged(self, event):
  408. self.selected_layer = None
  409. def OnRightClick(self, node):
  410. """Display popup menu."""
  411. self.DefineItems(self.GetSelected())
  412. if self.mixed:
  413. self._popupMenuEmpty()
  414. return
  415. if not self.selected_layer:
  416. self._popupMenuEmpty()
  417. elif self.selected_layer[0]:
  418. self._popupMenuLayer()
  419. elif self.selected_mapset[0] and not self.selected_type[0] and len(self.selected_mapset) == 1:
  420. self._popupMenuMapset()
  421. elif self.selected_type[0] and len(self.selected_type) == 1:
  422. self._popupMenuElement()
  423. else:
  424. self._popupMenuEmpty()
  425. def OnDoubleClick(self, node):
  426. """Double click on item/node.
  427. Display selected layer if node is a map layer otherwise
  428. expand/collapse node.
  429. """
  430. if not isinstance(self._giface, StandaloneGrassInterface):
  431. self.DefineItems([node])
  432. if self.selected_layer[0] is not None:
  433. # display selected layer and return
  434. self.DisplayLayer()
  435. return
  436. # expand/collapse location/mapset...
  437. if self.IsNodeExpanded(node):
  438. self.CollapseNode(node, recursive=False)
  439. else:
  440. self.ExpandNode(node, recursive=False)
  441. def ExpandCurrentLocation(self):
  442. """Expand current location"""
  443. location = gscript.gisenv()['LOCATION_NAME']
  444. item = self._model.SearchNodes(name=location, type='location')
  445. if item:
  446. self.Select(item[0], select=True)
  447. self.ExpandNode(item[0], recursive=False)
  448. else:
  449. Debug.msg(1, "Location <%s> not found" % location)
  450. def GetCurrentLocationMapsetNode(self):
  451. """Get current mapset node"""
  452. genv = gisenv()
  453. location = genv['LOCATION_NAME']
  454. mapset = genv['MAPSET']
  455. locationItem = self._model.SearchNodes(name=location, type='location')
  456. if not locationItem:
  457. return None, None
  458. mapsetItem = self._model.SearchNodes(
  459. parent=locationItem[0],
  460. name=mapset, type='mapset')
  461. if not mapsetItem:
  462. return locationItem[0], None
  463. return locationItem[0], mapsetItem[0]
  464. def ExpandCurrentMapset(self):
  465. """Expand current mapset"""
  466. locationItem, mapsetItem = self.GetCurrentLocationMapsetNode()
  467. if mapsetItem:
  468. self.Select(mapsetItem, select=True)
  469. self.ExpandNode(mapsetItem, recursive=True)
  470. class DataCatalogTree(LocationMapTree):
  471. def __init__(self, parent, giface=None):
  472. """Data Catalog Tree constructor."""
  473. super(DataCatalogTree, self).__init__(parent)
  474. self._giface = giface
  475. self._restricted = True
  476. self._initVariablesCatalog()
  477. self.beginDrag = Signal('DataCatalogTree.beginDrag')
  478. self.endDrag = Signal('DataCatalogTree.endDrag')
  479. self.startEdit = Signal('DataCatalogTree.startEdit')
  480. self.endEdit = Signal('DataCatalogTree.endEdit')
  481. self.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda evt:
  482. self._emitSignal(evt.GetItem(), self.beginDrag, event=evt))
  483. self.Bind(wx.EVT_TREE_END_DRAG, lambda evt:
  484. self._emitSignal(evt.GetItem(), self.endDrag, event=evt))
  485. self.beginDrag.connect(self.OnBeginDrag)
  486. self.endDrag.connect(self.OnEndDrag)
  487. self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, lambda evt:
  488. self._emitSignal(evt.GetItem(), self.startEdit, event=evt))
  489. self.Bind(wx.EVT_TREE_END_LABEL_EDIT, lambda evt:
  490. self._emitSignal(evt.GetItem(), self.endEdit, event=evt))
  491. self.startEdit.connect(self.OnStartEditLabel)
  492. self.endEdit.connect(self.OnEditLabel)
  493. def _initVariablesCatalog(self):
  494. """Init variables."""
  495. self.copy_mode = False
  496. self.copy_layer = None
  497. self.copy_type = None
  498. self.copy_mapset = None
  499. self.copy_location = None
  500. def SetRestriction(self, restrict):
  501. self._restricted = restrict
  502. def _runCommand(self, prog, **kwargs):
  503. cmdString = ' '.join(gscript.make_command(prog, **kwargs))
  504. ret = RunCommand(prog, parent=self, **kwargs)
  505. return ret, cmdString
  506. def InitTreeItems(self):
  507. """Add locations, mapsets and layers to the tree."""
  508. self._initTreeItems()
  509. def OnMoveMap(self, event):
  510. """Move layer or mapset (just save it temporarily, copying is done by paste)"""
  511. self.copy_mode = False
  512. self.copy_layer = self.selected_layer[:]
  513. self.copy_type = self.selected_type[:]
  514. self.copy_mapset = self.selected_mapset[:]
  515. self.copy_location = self.selected_location[:]
  516. if len(self.copy_layer) > 1:
  517. label = _("{c} maps marked for moving.").format(c=len(self.selected_layer))
  518. else:
  519. label = _("Map <{layer}> marked for moving.").format(layer=self.copy_layer[0].label)
  520. self.showNotification.emit(message=label)
  521. def OnCopyMap(self, event):
  522. """Copy layer or mapset (just save it temporarily, copying is done by paste)"""
  523. self.copy_mode = True
  524. self.copy_layer = self.selected_layer[:]
  525. self.copy_type = self.selected_type[:]
  526. self.copy_mapset = self.selected_mapset[:]
  527. self.copy_location = self.selected_location[:]
  528. if len(self.copy_layer) > 1:
  529. label = _("{c} maps marked for copying.").format(c=len(self.selected_layer))
  530. else:
  531. label = _("Map <{layer}> marked for copying.").format(layer=self.copy_layer[0].label)
  532. self.showNotification.emit(message=label)
  533. def OnRenameMap(self, event):
  534. """Rename layer with dialog"""
  535. old_name = self.selected_layer[0].label
  536. gisrc, env = gscript.create_environment(
  537. gisenv()['GISDBASE'],
  538. self.selected_location[0].label, mapset=self.selected_mapset[0].label)
  539. new_name = self._getNewMapName(
  540. _('New name'),
  541. _('Rename map'),
  542. old_name,
  543. env=env,
  544. mapset=self.selected_mapset[0].label,
  545. element=self.selected_type[0].label)
  546. if new_name:
  547. self.Rename(old_name, new_name)
  548. def OnStartEditLabel(self, node, event):
  549. """Start label editing"""
  550. self.DefineItems([node])
  551. Debug.msg(1, "Start label edit {name}".format(name=node.label))
  552. label = _("Editing {name}").format(name=node.label)
  553. self.showNotification.emit(message=label)
  554. if not self.selected_layer:
  555. event.Veto()
  556. def OnEditLabel(self, node, event):
  557. """End label editing"""
  558. if self.selected_layer and not event.IsEditCancelled():
  559. old_name = node.label
  560. Debug.msg(1, "End label edit {name}".format(name=old_name))
  561. new_name = event.GetLabel()
  562. self.Rename(old_name, new_name)
  563. def Rename(self, old, new):
  564. """Rename layer"""
  565. string = old + ',' + new
  566. gisrc, env = gscript.create_environment(
  567. gisenv()['GISDBASE'],
  568. self.selected_location[0].label, self.selected_mapset[0].label)
  569. label = _("Renaming map <{name}>...").format(name=string)
  570. self.showNotification.emit(message=label)
  571. if self.selected_type[0].label == 'vector':
  572. renamed, cmd = self._runCommand('g.rename', vector=string, env=env)
  573. elif self.selected_type[0].label == 'raster':
  574. renamed, cmd = self._runCommand('g.rename', raster=string, env=env)
  575. else:
  576. renamed, cmd = self._runCommand(
  577. 'g.rename', raster3d=string, env=env)
  578. if renamed == 0:
  579. self.selected_layer[0].label = new
  580. self.selected_layer[0].data['name'] = new
  581. self.RefreshNode(self.selected_layer[0])
  582. self.showNotification.emit(
  583. message=_("{cmd} -- completed").format(cmd=cmd))
  584. Debug.msg(1, "LAYER RENAMED TO: " + new)
  585. gscript.try_remove(gisrc)
  586. def OnPasteMap(self, event):
  587. # copying between mapsets of one location
  588. if not self.copy_layer:
  589. if self.copy_mode:
  590. GMessage(_("No map selected for copying."), parent=self)
  591. else:
  592. GMessage(_("No map selected for moving."), parent=self)
  593. return
  594. for i in range(len(self.copy_layer)):
  595. gisrc, env = gscript.create_environment(
  596. gisenv()['GISDBASE'], self.selected_location[0].label, mapset=self.selected_mapset[0].label)
  597. gisrc2, env2 = gscript.create_environment(
  598. gisenv()['GISDBASE'], self.copy_location[i].label, mapset=self.copy_mapset[i].label)
  599. new_name = self.copy_layer[i].label
  600. if self.selected_location[0] == self.copy_location[i]:
  601. # within one mapset
  602. if self.selected_mapset[0] == self.copy_mapset[i]:
  603. # ignore when just moves map
  604. if self.copy_mode is False:
  605. return
  606. new_name = self._getNewMapName(_('New name for <{n}>').format(n=self.copy_layer[i].label),
  607. _('Select new name'),
  608. self.copy_layer[i].label, env=env,
  609. mapset=self.selected_mapset[0].label,
  610. element=self.copy_type[i].label)
  611. if not new_name:
  612. return
  613. # within one location, different mapsets
  614. else:
  615. if map_exists(new_name, element=self.copy_type[i].label, env=env,
  616. mapset=self.selected_mapset[0].label):
  617. new_name = self._getNewMapName(_('New name for <{n}>').format(n=self.copy_layer[i].label),
  618. _('Select new name'),
  619. self.copy_layer[i].label, env=env,
  620. mapset=self.selected_mapset[0].label,
  621. element=self.copy_type[i].label)
  622. if not new_name:
  623. return
  624. string = self.copy_layer[i].label + '@' + self.copy_mapset[i].label + ',' + new_name
  625. pasted = 0
  626. if self.copy_mode:
  627. label = _("Copying <{name}>...").format(name=string)
  628. else:
  629. label = _("Moving <{name}>...").format(name=string)
  630. self.showNotification.emit(message=label)
  631. if self.copy_type[i].label == 'vector':
  632. pasted, cmd = self._runCommand('g.copy', vector=string, env=env)
  633. node = 'vector'
  634. elif self.copy_type[i].label == 'raster':
  635. pasted, cmd = self._runCommand('g.copy', raster=string, env=env)
  636. node = 'raster'
  637. else:
  638. pasted, cmd = self._runCommand('g.copy', raster_3d=string, env=env)
  639. node = 'raster_3d'
  640. if pasted == 0:
  641. self.InsertLayer(name=new_name, mapset_node=self.selected_mapset[0],
  642. element_name=node)
  643. Debug.msg(1, "COPIED TO: " + new_name)
  644. if self.copy_mode:
  645. self.showNotification.emit(message=_("g.copy completed"))
  646. else:
  647. self.showNotification.emit(message=_("g.copy completed"))
  648. # remove old
  649. if not self.copy_mode:
  650. self._removeMapAfterCopy(self.copy_layer[i], self.copy_type[i], env2)
  651. gscript.try_remove(gisrc)
  652. gscript.try_remove(gisrc2)
  653. # expand selected mapset
  654. else:
  655. if self.copy_type[i].label == 'raster_3d':
  656. GError(_("Reprojection is not implemented for 3D rasters"), parent=self)
  657. return
  658. if map_exists(new_name, element=self.copy_type[i].label, env=env,
  659. mapset=self.selected_mapset[0].label):
  660. new_name = self._getNewMapName(_('New name'), _('Select new name'),
  661. self.copy_layer[i].label, env=env,
  662. mapset=self.selected_mapset[0].label,
  663. element=self.copy_type[i].label)
  664. if not new_name:
  665. continue
  666. gisdbase = gisenv()['GISDBASE']
  667. callback = lambda gisrc2=gisrc2, gisrc=gisrc, cLayer=self.copy_layer[i], \
  668. cType=self.copy_type[i], cMode=self.copy_mode, name=new_name: \
  669. self._onDoneReprojection(env2, gisrc2, gisrc, cLayer, cType, cMode, name)
  670. dlg = CatalogReprojectionDialog(self, self._giface, gisdbase, self.copy_location[i].label,
  671. self.copy_mapset[i].label, self.copy_layer[i].label, env2,
  672. gisdbase, self.selected_location[0].label, self.selected_mapset[0].label,
  673. new_name, self.copy_type[i].label, env, callback)
  674. dlg.ShowModal()
  675. self.ExpandNode(self.selected_mapset[0], recursive=True)
  676. self._initVariablesCatalog()
  677. def _onDoneReprojection(self, iEnv, iGisrc, oGisrc, cLayer, cType, cMode, name):
  678. self.InsertLayer(name=name, mapset_node=self.selected_mapset[0],
  679. element_name=cType.label)
  680. if not cMode:
  681. self._removeMapAfterCopy(cLayer, cType, iEnv)
  682. gscript.try_remove(iGisrc)
  683. gscript.try_remove(oGisrc)
  684. self.ExpandNode(self.selected_mapset[0], recursive=True)
  685. def _removeMapAfterCopy(self, cLayer, cType, env):
  686. removed, cmd = self._runCommand('g.remove', type=cType.label,
  687. name=cLayer.label, flags='f', env=env)
  688. if removed == 0:
  689. self._model.RemoveNode(cLayer)
  690. self.RefreshNode(cType, recursive=True)
  691. Debug.msg(1, "LAYER " + cLayer.label + " DELETED")
  692. self.showNotification.emit(message=_("g.remove completed"))
  693. def InsertLayer(self, name, mapset_node, element_name):
  694. """Insert layer into model and refresh tree"""
  695. found_element = self._model.SearchNodes(
  696. parent=mapset_node, type='element', name=element_name)
  697. found_element = found_element[0] if found_element else None
  698. if not found_element:
  699. # add type node if not exists
  700. found_element = self._model.AppendNode(
  701. parent=mapset_node, label=element_name,
  702. data=dict(type='element', name=element_name))
  703. found = self._model.SearchNodes(parent=found_element, name=name)
  704. if len(found) == 0:
  705. self._model.AppendNode(parent=found_element, label=name,
  706. data=dict(type=element_name, name=name))
  707. self._model.SortChildren(found_element)
  708. self.RefreshNode(mapset_node, recursive=True)
  709. def OnDeleteMap(self, event):
  710. """Delete layer or mapset"""
  711. names = [self.selected_layer[i].label + '@' + self.selected_mapset[i].label
  712. for i in range(len(self.selected_layer))]
  713. if len(names) < 10:
  714. question = _("Do you really want to delete map(s) <{m}>?").format(m=', '.join(names))
  715. else:
  716. question = _("Do you really want to delete {n} maps?").format(n=len(names))
  717. if self._confirmDialog(question, title=_('Delete map')) == wx.ID_YES:
  718. label = _("Deleting {name}...").format(name=names)
  719. self.showNotification.emit(message=label)
  720. for i in range(len(self.selected_layer)):
  721. gisrc, env = gscript.create_environment(
  722. gisenv()['GISDBASE'],
  723. self.selected_location[i].label, self.selected_mapset[i].label)
  724. removed, cmd = self._runCommand(
  725. 'g.remove', flags='f', type=self.selected_type[i].label,
  726. name=self.selected_layer[i].label, env=env)
  727. if removed == 0:
  728. self._model.RemoveNode(self.selected_layer[i])
  729. self.RefreshNode(self.selected_type[i], recursive=True)
  730. Debug.msg(1, "LAYER " + self.selected_layer[i].label + " DELETED")
  731. # remove map layer from layer tree if exists
  732. if not isinstance(self._giface, StandaloneGrassInterface):
  733. name = self.selected_layer[i].label + '@' + self.selected_mapset[i].label
  734. layers = self._giface.GetLayerList().GetLayersByName(name)
  735. for layer in layers:
  736. self._giface.GetLayerList().DeleteLayer(layer)
  737. gscript.try_remove(gisrc)
  738. self.UnselectAll()
  739. self.showNotification.emit(message=_("g.remove completed"))
  740. def OnDisplayLayer(self, event):
  741. """Display layer in current graphics view"""
  742. self.DisplayLayer()
  743. def DisplayLayer(self):
  744. """Display selected layer in current graphics view"""
  745. all_names = []
  746. names = {'raster': [], 'vector': [], 'raster_3d': []}
  747. for i in range(len(self.selected_layer)):
  748. name = self.selected_layer[i].label + '@' + self.selected_mapset[i].label
  749. names[self.selected_type[i].label].append(name)
  750. all_names.append(name)
  751. #if self.selected_location[0].label == gisenv()['LOCATION_NAME'] and self.selected_mapset[0]:
  752. for ltype in names:
  753. if names[ltype]:
  754. self._giface.lmgr.AddMaps(list(reversed(names[ltype])), ltype, True)
  755. if len(self._giface.GetLayerList()) == 1:
  756. # zoom to map if there is only one map layer
  757. self._giface.GetMapWindow().ZoomToMap()
  758. Debug.msg(1, "Displayed layer(s): " + str(all_names))
  759. def OnBeginDrag(self, node, event):
  760. """Just copy necessary data"""
  761. self.DefineItems(self.GetSelected())
  762. if self.selected_layer and not (self._restricted and gisenv()[
  763. 'LOCATION_NAME'] != self.selected_location[0].label):
  764. event.Allow()
  765. self.OnCopyMap(event)
  766. Debug.msg(1, "DRAG")
  767. else:
  768. event.Veto()
  769. def OnEndDrag(self, node, event):
  770. """Copy layer into target"""
  771. self.copy_mode = wx.GetMouseState().ControlDown()
  772. if node:
  773. self.DefineItems([node])
  774. if self._restricted and gisenv()['MAPSET'] != self.selected_mapset[0].label:
  775. GMessage(_("To move or copy maps to other mapsets, unlock editing of other mapsets"),
  776. parent=self)
  777. event.Veto()
  778. return
  779. event.Allow()
  780. Debug.msg(1, "DROP DONE")
  781. self.OnPasteMap(event)
  782. def OnSwitchLocationMapset(self, event):
  783. genv = gisenv()
  784. if self.selected_location[0].label == genv['LOCATION_NAME']:
  785. self.changeMapset.emit(mapset=self.selected_mapset[0].label)
  786. else:
  787. self.changeLocation.emit(mapset=self.selected_mapset[0].label, location=self.selected_location[0].label)
  788. self.ExpandCurrentMapset()
  789. def OnMetadata(self, event):
  790. """Show metadata of any raster/vector/3draster"""
  791. def done(event):
  792. gscript.try_remove(event.userData)
  793. for i in range(len(self.selected_layer)):
  794. if self.selected_type[i].label == 'raster':
  795. cmd = ['r.info']
  796. elif self.selected_type[i].label == 'vector':
  797. cmd = ['v.info']
  798. elif self.selected_type[i].label == 'raster_3d':
  799. cmd = ['r3.info']
  800. cmd.append('map=%s@%s' % (self.selected_layer[i].label, self.selected_mapset[i].label))
  801. gisrc, env = gscript.create_environment(
  802. gisenv()['GISDBASE'],
  803. self.selected_location[i].label, self.selected_mapset[i].label)
  804. # print output to command log area
  805. # temp gisrc file must be deleted onDone
  806. self._giface.RunCmd(cmd, env=env, onDone=done, userData=gisrc)
  807. def OnCopyName(self, event):
  808. """Copy layer name to clipboard"""
  809. if wx.TheClipboard.Open():
  810. do = wx.TextDataObject()
  811. text = []
  812. for i in range(len(self.selected_layer)):
  813. text.append('%s@%s' % (self.selected_layer[i].label, self.selected_mapset[i].label))
  814. do.SetText(','.join(text))
  815. wx.TheClipboard.SetData(do)
  816. wx.TheClipboard.Close()
  817. def Filter(self, text):
  818. """Filter tree based on name and type."""
  819. text = text.strip()
  820. if len(text.split(':')) > 1:
  821. name = text.split(':')[1].strip()
  822. elem = text.split(':')[0].strip()
  823. if 'r' == elem:
  824. element = 'raster'
  825. elif 'r3' == elem:
  826. element = 'raster_3d'
  827. elif 'v' == elem:
  828. element = 'vector'
  829. else:
  830. element = None
  831. else:
  832. element = None
  833. name = text.strip()
  834. self._model = filterModel(self._orig_model, name=name, element=element)
  835. self.RefreshItems()
  836. self.ExpandCurrentMapset()
  837. def _getNewMapName(self, message, title, value, element, mapset, env):
  838. """Dialog for simple text entry"""
  839. dlg = NameEntryDialog(parent=self, message=message, caption=title,
  840. element=element, env=env, mapset=mapset)
  841. dlg.SetValue(value)
  842. if dlg.ShowModal() == wx.ID_OK:
  843. name = dlg.GetValue()
  844. else:
  845. name = None
  846. dlg.Destroy()
  847. return name
  848. def _confirmDialog(self, question, title):
  849. """Confirm dialog"""
  850. dlg = wx.MessageDialog(self, question, title, wx.YES_NO)
  851. res = dlg.ShowModal()
  852. dlg.Destroy()
  853. return res
  854. def _isCurrent(self, genv):
  855. if self._restricted:
  856. currentMapset = currentLocation = True
  857. for i in range(len(self.selected_location)):
  858. if self.selected_location[i].label != genv['LOCATION_NAME']:
  859. currentLocation = False
  860. currentMapset = False
  861. break
  862. if currentMapset:
  863. for i in range(len(self.selected_mapset)):
  864. if self.selected_mapset[i].label != genv['MAPSET']:
  865. currentMapset = False
  866. break
  867. return currentLocation, currentMapset
  868. else:
  869. return True, True
  870. def _popupMenuLayer(self):
  871. """Create popup menu for layers"""
  872. menu = Menu()
  873. genv = gisenv()
  874. currentLocation, currentMapset = self._isCurrent(genv)
  875. item = wx.MenuItem(menu, wx.ID_ANY, _("&Cut"))
  876. menu.AppendItem(item)
  877. self.Bind(wx.EVT_MENU, self.OnMoveMap, item)
  878. if not currentMapset:
  879. item.Enable(False)
  880. item = wx.MenuItem(menu, wx.ID_ANY, _("&Copy"))
  881. menu.AppendItem(item)
  882. self.Bind(wx.EVT_MENU, self.OnCopyMap, item)
  883. item = wx.MenuItem(menu, wx.ID_ANY, _("Copy &name"))
  884. menu.AppendItem(item)
  885. self.Bind(wx.EVT_MENU, self.OnCopyName, item)
  886. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  887. menu.AppendItem(item)
  888. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  889. if not(currentMapset and self.copy_layer):
  890. item.Enable(False)
  891. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete"))
  892. menu.AppendItem(item)
  893. self.Bind(wx.EVT_MENU, self.OnDeleteMap, item)
  894. item.Enable(currentMapset)
  895. item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename"))
  896. menu.AppendItem(item)
  897. self.Bind(wx.EVT_MENU, self.OnRenameMap, item)
  898. item.Enable(currentMapset and len(self.selected_layer) == 1)
  899. menu.AppendSeparator()
  900. if not isinstance(self._giface, StandaloneGrassInterface):
  901. if all([each.label == genv['LOCATION_NAME'] for each in self.selected_location]):
  902. if len(self.selected_layer) > 1:
  903. item = wx.MenuItem(menu, wx.ID_ANY, _("&Display layers"))
  904. else:
  905. item = wx.MenuItem(menu, wx.ID_ANY, _("&Display layer"))
  906. menu.AppendItem(item)
  907. self.Bind(wx.EVT_MENU, self.OnDisplayLayer, item)
  908. item = wx.MenuItem(menu, wx.ID_ANY, _("Show &metadata"))
  909. menu.AppendItem(item)
  910. self.Bind(wx.EVT_MENU, self.OnMetadata, item)
  911. self.PopupMenu(menu)
  912. menu.Destroy()
  913. def _popupMenuMapset(self):
  914. """Create popup menu for mapsets"""
  915. menu = Menu()
  916. genv = gisenv()
  917. currentLocation, currentMapset = self._isCurrent(genv)
  918. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  919. menu.AppendItem(item)
  920. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  921. if not(currentMapset and self.copy_layer):
  922. item.Enable(False)
  923. item = wx.MenuItem(menu, wx.ID_ANY, _("&Switch mapset"))
  924. menu.AppendItem(item)
  925. self.Bind(wx.EVT_MENU, self.OnSwitchLocationMapset, item)
  926. if (self.selected_location[0].label == genv['LOCATION_NAME']
  927. and self.selected_mapset[0].label == genv['MAPSET']):
  928. item.Enable(False)
  929. self.PopupMenu(menu)
  930. menu.Destroy()
  931. def _popupMenuElement(self):
  932. """Create popup menu for elements"""
  933. menu = Menu()
  934. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  935. menu.AppendItem(item)
  936. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  937. genv = gisenv()
  938. currentLocation, currentMapset = self._isCurrent(genv)
  939. if not(currentMapset and self.copy_layer):
  940. item.Enable(False)
  941. self.PopupMenu(menu)
  942. menu.Destroy()
  943. def _popupMenuEmpty(self):
  944. """Create empty popup when multiple different types of items are selected"""
  945. menu = Menu()
  946. item = wx.MenuItem(menu, wx.ID_ANY, _("No available options"))
  947. menu.AppendItem(item)
  948. item.Enable(False)
  949. self.PopupMenu(menu)
  950. menu.Destroy()