tree.py 44 KB

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