tree.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. """
  2. @package datacatalog::tree
  3. @brief Data catalog tree classes
  4. Classes:
  5. - datacatalog::LocationMapTree
  6. - datacatalog::DataCatalogTree
  7. (C) 2014-2015 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 datacatalog.dialogs import CatalogReprojectionDialog
  27. from grass.pydispatch.signal import Signal
  28. import grass.script as gscript
  29. from grass.script import gisenv
  30. from grass.exceptions import CalledModuleError
  31. def filterModel(model, element=None, name=None):
  32. """Filter tree model based on type or name of map using regular expressions.
  33. Copies tree and remove nodes which don't match."""
  34. fmodel = copy.deepcopy(model)
  35. nodesToRemove = []
  36. if name:
  37. try:
  38. regex = re.compile(name)
  39. except:
  40. return fmodel
  41. for gisdbase in fmodel.root.children:
  42. for location in gisdbase.children:
  43. for mapset in location.children:
  44. for elem in mapset.children:
  45. if element and elem.label != element:
  46. nodesToRemove.append(elem)
  47. continue
  48. for node in elem.children:
  49. if name and regex.search(node.label) is None:
  50. nodesToRemove.append(node)
  51. for node in reversed(nodesToRemove):
  52. fmodel.RemoveNode(node)
  53. cleanUpTree(fmodel)
  54. return fmodel
  55. def cleanUpTree(model):
  56. """Removes empty element/mapsets/locations nodes.
  57. It first removes empty elements, then mapsets, then locations"""
  58. # removes empty elements
  59. nodesToRemove = []
  60. for gisdbase in model.root.children:
  61. for location in gisdbase.children:
  62. for mapset in location.children:
  63. for element in mapset.children:
  64. if not element.children:
  65. nodesToRemove.append(element)
  66. for node in reversed(nodesToRemove):
  67. model.RemoveNode(node)
  68. # removes empty mapsets
  69. nodesToRemove = []
  70. for gisdbase in model.root.children:
  71. for location in gisdbase.children:
  72. for mapset in location.children:
  73. if not mapset.children:
  74. nodesToRemove.append(mapset)
  75. for node in reversed(nodesToRemove):
  76. model.RemoveNode(node)
  77. # removes empty locations
  78. nodesToRemove = []
  79. for gisdbase in model.root.children:
  80. for location in gisdbase.children:
  81. if not location.children:
  82. nodesToRemove.append(location)
  83. for node in reversed(nodesToRemove):
  84. model.RemoveNode(node)
  85. def getLocationTree(gisdbase, location, queue, mapsets=None):
  86. """Creates dictionary with mapsets, elements, layers for given location.
  87. Returns tuple with the dictionary and error (or None)"""
  88. tmp_gisrc_file, env = gscript.create_environment(gisdbase, location, 'PERMANENT')
  89. env['GRASS_SKIP_MAPSET_OWNER_CHECK'] = '1'
  90. maps_dict = {}
  91. elements = ['raster', 'raster_3d', 'vector']
  92. try:
  93. if not mapsets:
  94. mapsets = gscript.read_command(
  95. 'g.mapsets',
  96. flags='l',
  97. separator='comma',
  98. quiet=True,
  99. env=env).strip()
  100. except CalledModuleError:
  101. queue.put(
  102. (maps_dict,
  103. _("Failed to read mapsets from location <{l}>.").format(
  104. l=location)))
  105. gscript.try_remove(tmp_gisrc_file)
  106. return
  107. else:
  108. listOfMapsets = mapsets.split(',')
  109. Debug.msg(
  110. 4, "Location <{0}>: {1} mapsets found".format(
  111. location, len(listOfMapsets)))
  112. for each in listOfMapsets:
  113. maps_dict[each] = {}
  114. for elem in elements:
  115. maps_dict[each][elem] = []
  116. try:
  117. maplist = gscript.read_command(
  118. 'g.list', flags='mt', type=elements,
  119. mapset=','.join(listOfMapsets),
  120. quiet=True, env=env).strip()
  121. except CalledModuleError:
  122. queue.put(
  123. (maps_dict,
  124. _("Failed to read maps from location <{l}>.").format(
  125. l=location)))
  126. gscript.try_remove(tmp_gisrc_file)
  127. return
  128. else:
  129. # fill dictionary
  130. listOfMaps = maplist.splitlines()
  131. Debug.msg(
  132. 4, "Location <{0}>: {1} maps found".format(
  133. location, len(listOfMaps)))
  134. for each in listOfMaps:
  135. ltype, wholename = each.split('/')
  136. name, mapset = wholename.split('@')
  137. maps_dict[mapset][ltype].append(name)
  138. queue.put((maps_dict, None))
  139. gscript.try_remove(tmp_gisrc_file)
  140. def map_exists(name, element, env, mapset=None):
  141. """Check is map is present in the mapset given in the environment
  142. :param name: name of the map
  143. :param element: data type ('raster', 'raster_3d', and 'vector')
  144. :param env environment created by function gscript.create_environment
  145. """
  146. if not mapset:
  147. mapset = gscript.run_command('g.mapset', flags='p', env=env).strip()
  148. # change type to element used by find file
  149. if element == 'raster':
  150. element = 'cell'
  151. elif element == 'raster_3d':
  152. element = 'grid3'
  153. # g.findfile returns non-zero when file was not found
  154. # se we ignore return code and just focus on stdout
  155. process = gscript.start_command(
  156. 'g.findfile',
  157. flags='n',
  158. element=element,
  159. file=name,
  160. mapset=mapset,
  161. stdout=gscript.PIPE,
  162. stderr=gscript.PIPE,
  163. env=env)
  164. output, errors = process.communicate()
  165. info = gscript.parse_key_val(output, sep='=')
  166. # file is the key questioned in grass.script.core find_file()
  167. # return code should be equivalent to checking the output
  168. if info['file']:
  169. return True
  170. else:
  171. return False
  172. class NameEntryDialog(TextEntryDialog):
  173. def __init__(self, element, mapset, env, **kwargs):
  174. TextEntryDialog.__init__(self, **kwargs)
  175. self._element = element
  176. self._mapset = mapset
  177. self._env = env
  178. id_OK = self.GetAffirmativeId()
  179. self.Bind(wx.EVT_BUTTON, self.OnOK, self.FindWindowById(id_OK))
  180. def OnOK(self, event):
  181. new = self.GetValue()
  182. if not new:
  183. return
  184. if map_exists(new, self._element, self._env, self._mapset):
  185. dlg = wx.MessageDialog(
  186. self,
  187. message=_(
  188. "Map of type {elem} <{name}> already exists in mapset <{mapset}>. "
  189. "Do you want to overwrite it?").format(
  190. elem=self._element,
  191. name=new,
  192. mapset=self._mapset),
  193. caption=_("Overwrite?"),
  194. style=wx.YES_NO)
  195. if dlg.ShowModal() == wx.ID_YES:
  196. dlg.Destroy()
  197. self._env['GRASS_OVERWRITE'] = '1'
  198. self.EndModal(wx.ID_OK)
  199. else:
  200. dlg.Destroy()
  201. return
  202. else:
  203. self.EndModal(wx.ID_OK)
  204. class DataCatalogNode(DictNode):
  205. """Node representing item in datacatalog."""
  206. def __init__(self, label, data=None):
  207. super(DataCatalogNode, self).__init__(label=label, data=data)
  208. def match(self, **kwargs):
  209. """Method used for searching according to given parameters.
  210. :param value: dictionary value to be matched
  211. :param key: data dictionary key
  212. """
  213. if not kwargs:
  214. return False
  215. for key in kwargs:
  216. if not (key in self.data and self.data[key] == kwargs[key]):
  217. return False
  218. return True
  219. class LocationMapTree(TreeView):
  220. def __init__(
  221. self, parent, model=None, style=wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS
  222. | wx.TR_LINES_AT_ROOT | wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT |
  223. wx.TR_SINGLE):
  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 = None
  367. self.selected_type = None
  368. self.selected_mapset = None
  369. self.selected_location = None
  370. def GetControl(self):
  371. """Returns control itself."""
  372. return self
  373. def DefineItems(self, item):
  374. """Set selected items."""
  375. self.selected_layer = None
  376. self.selected_type = None
  377. self.selected_mapset = None
  378. self.selected_location = None
  379. type = item.data['type']
  380. if type in ('raster', 'raster_3d', 'vector'):
  381. self.selected_layer = item
  382. type = 'element'
  383. item = item.parent
  384. if type == 'element':
  385. self.selected_type = item
  386. type = 'mapset'
  387. item = item.parent
  388. if type == 'mapset':
  389. self.selected_mapset = item
  390. type = 'location'
  391. item = item.parent
  392. if type == 'location':
  393. self.selected_location = item
  394. def OnSelChanged(self, event):
  395. self.selected_layer = None
  396. def OnRightClick(self, node):
  397. """Display popup menu."""
  398. self.DefineItems(node)
  399. if self.selected_layer:
  400. self._popupMenuLayer()
  401. elif self.selected_mapset and not self.selected_type:
  402. self._popupMenuMapset()
  403. elif self.selected_type:
  404. self._popupMenuElement()
  405. def OnDoubleClick(self, node):
  406. """Expand/Collapse node."""
  407. if self.IsNodeExpanded(node):
  408. self.CollapseNode(node, recursive=False)
  409. else:
  410. self.ExpandNode(node, recursive=False)
  411. def ExpandCurrentLocation(self):
  412. """Expand current location"""
  413. location = gscript.gisenv()['LOCATION_NAME']
  414. item = self._model.SearchNodes(name=location, type='location')
  415. if item:
  416. self.Select(item[0], select=True)
  417. self.ExpandNode(item[0], recursive=False)
  418. else:
  419. Debug.msg(1, "Location <%s> not found" % location)
  420. def GetCurrentLocationMapsetNode(self):
  421. """Get current mapset node"""
  422. genv = gisenv()
  423. location = genv['LOCATION_NAME']
  424. mapset = genv['MAPSET']
  425. locationItem = self._model.SearchNodes(name=location, type='location')
  426. if not locationItem:
  427. return None, None
  428. mapsetItem = self._model.SearchNodes(
  429. parent=locationItem[0],
  430. name=mapset, type='mapset')
  431. if not mapsetItem:
  432. return locationItem[0], None
  433. return locationItem[0], mapsetItem[0]
  434. def ExpandCurrentMapset(self):
  435. """Expand current mapset"""
  436. locationItem, mapsetItem = self.GetCurrentLocationMapsetNode()
  437. if mapsetItem:
  438. self.Select(mapsetItem, select=True)
  439. self.ExpandNode(mapsetItem, recursive=True)
  440. class DataCatalogTree(LocationMapTree):
  441. def __init__(self, parent, giface=None):
  442. """Data Catalog Tree constructor."""
  443. super(DataCatalogTree, self).__init__(parent)
  444. self._giface = giface
  445. self._restricted = True
  446. self._initVariablesCatalog()
  447. self.beginDrag = Signal('DataCatalogTree.beginDrag')
  448. self.endDrag = Signal('DataCatalogTree.endDrag')
  449. self.startEdit = Signal('DataCatalogTree.startEdit')
  450. self.endEdit = Signal('DataCatalogTree.endEdit')
  451. self.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda evt:
  452. self._emitSignal(evt.GetItem(), self.beginDrag, event=evt))
  453. self.Bind(wx.EVT_TREE_END_DRAG, lambda evt:
  454. self._emitSignal(evt.GetItem(), self.endDrag, event=evt))
  455. self.beginDrag.connect(self.OnBeginDrag)
  456. self.endDrag.connect(self.OnEndDrag)
  457. self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, lambda evt:
  458. self._emitSignal(evt.GetItem(), self.startEdit, event=evt))
  459. self.Bind(wx.EVT_TREE_END_LABEL_EDIT, lambda evt:
  460. self._emitSignal(evt.GetItem(), self.endEdit, event=evt))
  461. self.startEdit.connect(self.OnStartEditLabel)
  462. self.endEdit.connect(self.OnEditLabel)
  463. def _initVariablesCatalog(self):
  464. """Init variables."""
  465. self.copy_layer = None
  466. self.copy_type = None
  467. self.copy_mapset = None
  468. self.copy_location = None
  469. def SetRestriction(self, restrict):
  470. self._restricted = restrict
  471. def _runCommand(self, prog, **kwargs):
  472. cmdString = ' '.join(gscript.make_command(prog, **kwargs))
  473. ret = RunCommand(prog, parent=self, **kwargs)
  474. return ret, cmdString
  475. def InitTreeItems(self):
  476. """Add locations, mapsets and layers to the tree."""
  477. self._initTreeItems()
  478. def OnCopyMap(self, event):
  479. """Copy layer or mapset (just save it temporarily, copying is done by paste)"""
  480. self.copy_layer = self.selected_layer
  481. self.copy_type = self.selected_type
  482. self.copy_mapset = self.selected_mapset
  483. self.copy_location = self.selected_location
  484. label = _(
  485. "Map <{layer}> marked for copying. "
  486. "You can paste it to the current mapset "
  487. "<{mapset}>.".format(
  488. layer=self.copy_layer.label,
  489. mapset=gisenv()['MAPSET']))
  490. self.showNotification.emit(message=label)
  491. def OnRenameMap(self, event):
  492. """Rename layer with dialog"""
  493. old_name = self.selected_layer.label
  494. gisrc, env = gscript.create_environment(
  495. gisenv()['GISDBASE'],
  496. self.selected_location.label, mapset=self.selected_mapset.label)
  497. new_name = self._getNewMapName(
  498. _('New name'),
  499. _('Rename map'),
  500. old_name,
  501. env=env,
  502. mapset=self.selected_mapset.label,
  503. element=self.selected_type.label)
  504. if new_name:
  505. self.Rename(old_name, new_name)
  506. def OnStartEditLabel(self, node, event):
  507. """Start label editing"""
  508. self.DefineItems(node)
  509. Debug.msg(1, "Start label edit {name}".format(name=node.label))
  510. label = _("Editing {name}").format(name=node.label)
  511. self.showNotification.emit(message=label)
  512. if not self.selected_layer:
  513. event.Veto()
  514. def OnEditLabel(self, node, event):
  515. """End label editing"""
  516. if self.selected_layer and not event.IsEditCancelled():
  517. old_name = node.label
  518. Debug.msg(1, "End label edit {name}".format(name=old_name))
  519. new_name = event.GetLabel()
  520. self.Rename(old_name, new_name)
  521. def Rename(self, old, new):
  522. """Rename layer"""
  523. string = old + ',' + new
  524. gisrc, env = gscript.create_environment(
  525. gisenv()['GISDBASE'],
  526. self.selected_location.label, self.selected_mapset.label)
  527. label = _("Renaming map <{name}>...").format(name=string)
  528. self.showNotification.emit(message=label)
  529. if self.selected_type.label == 'vector':
  530. renamed, cmd = self._runCommand('g.rename', vector=string, env=env)
  531. elif self.selected_type.label == 'raster':
  532. renamed, cmd = self._runCommand('g.rename', raster=string, env=env)
  533. else:
  534. renamed, cmd = self._runCommand(
  535. 'g.rename', raster3d=string, env=env)
  536. if renamed == 0:
  537. self.selected_layer.label = new
  538. self.selected_layer.data['name'] = new
  539. self.RefreshNode(self.selected_layer)
  540. self.showNotification.emit(
  541. message=_("{cmd} -- completed").format(cmd=cmd))
  542. Debug.msg(1, "LAYER RENAMED TO: " + new)
  543. gscript.try_remove(gisrc)
  544. def OnPasteMap(self, event):
  545. """Paste layer"""
  546. # copying between mapsets of one location
  547. if not self.copy_layer:
  548. GMessage(_("No map selected for copying."), parent=self)
  549. return
  550. if self.selected_location == self.copy_location:
  551. gisrc, env = gscript.create_environment(
  552. gisenv()['GISDBASE'], self.selected_location.label, mapset=self.selected_mapset.label)
  553. new_name = self._getNewMapName(
  554. _('New name'),
  555. _('Copy map'),
  556. self.copy_layer.label,
  557. env=env,
  558. mapset=self.selected_mapset.label,
  559. element=self.copy_type.label)
  560. if not new_name:
  561. return
  562. if map_exists(
  563. new_name, element=self.copy_type.label, env=env,
  564. mapset=self.selected_mapset.label):
  565. GMessage(
  566. _("Failed to copy map: new map has the same name"),
  567. parent=self)
  568. return
  569. if not self.selected_type:
  570. found = self._model.SearchNodes(
  571. parent=self.selected_mapset, type='element',
  572. name=self.copy_type.label)
  573. self.selected_type = found[0] if found else None
  574. overwrite = False
  575. if self.selected_type:
  576. found = self._model.SearchNodes(
  577. parent=self.selected_type,
  578. type=self.copy_type.label,
  579. name=new_name)
  580. if found and found[0]:
  581. dlg = wx.MessageDialog(
  582. parent=self,
  583. message=_(
  584. "Map <{map}> already exists "
  585. "in the current mapset. "
  586. "Do you want to overwrite it?").format(
  587. map=new_name),
  588. caption=_("Overwrite?"),
  589. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  590. ret = dlg.ShowModal()
  591. dlg.Destroy()
  592. if ret == wx.ID_YES:
  593. overwrite = True
  594. string = self.copy_layer.label + '@' + self.copy_mapset.label + ',' + new_name
  595. pasted = 0
  596. label = _("Copying <{name}>...").format(name=string)
  597. self.showNotification.emit(message=label)
  598. if self.copy_type.label == 'vector':
  599. pasted, cmd = self._runCommand(
  600. 'g.copy', vector=string, overwrite=overwrite, env=env)
  601. node = 'vector'
  602. elif self.copy_type.label == 'raster':
  603. pasted, cmd = self._runCommand(
  604. 'g.copy', raster=string, overwrite=overwrite, env=env)
  605. node = 'raster'
  606. else:
  607. pasted, cmd = self._runCommand(
  608. 'g.copy', raster_3d=string, overwrite=overwrite, env=env)
  609. node = 'raster_3d'
  610. if pasted == 0:
  611. self.InsertLayer(
  612. name=new_name,
  613. mapset_node=self.selected_mapset,
  614. element_name=node)
  615. Debug.msg(1, "COPIED TO: " + new_name)
  616. self.showNotification.emit(
  617. message=_("g.copy completed").format(cmd=cmd))
  618. gscript.try_remove(gisrc)
  619. else:
  620. if self.copy_type.label == 'raster_3d':
  621. GError(_("Reprojection is not implemented for 3D rasters"), parent=self)
  622. return
  623. gisdbase = gisenv()['GISDBASE']
  624. dlg = CatalogReprojectionDialog(self, self._giface, gisdbase, self.copy_location.label,
  625. self.copy_mapset.label, self.copy_layer.label,
  626. gisdbase, self.selected_location.label, self.selected_mapset.label,
  627. etype=self.copy_type.label)
  628. dlg.Show()
  629. # expand selected mapset
  630. self.ExpandNode(self.selected_mapset, recursive=True)
  631. def InsertLayer(self, name, mapset_node, element_name):
  632. """Insert layer into model and refresh tree"""
  633. found_element = self._model.SearchNodes(
  634. parent=mapset_node, type='element', name=element_name)
  635. found_element = found_element[0] if found_element else None
  636. if not found_element:
  637. # add type node if not exists
  638. found_element = self._model.AppendNode(
  639. parent=mapset_node, label=element_name,
  640. data=dict(type='element', name=element_name))
  641. found = self._model.SearchNodes(parent=found_element, name=name)
  642. if len(found) == 0:
  643. self._model.AppendNode(parent=found_element, label=name,
  644. data=dict(type=element_name, name=name))
  645. self._model.SortChildren(found_element)
  646. self.RefreshNode(mapset_node, recursive=True)
  647. def OnDeleteMap(self, event):
  648. """Delete layer or mapset"""
  649. name = self.selected_layer.label
  650. gisrc, env = gscript.create_environment(
  651. gisenv()['GISDBASE'],
  652. self.selected_location.label, self.selected_mapset.label)
  653. if self._confirmDialog(
  654. question=_(
  655. "Do you really want to delete map <{m}> of type <{etype}> from mapset "
  656. "<{mapset}> in location <{loc}>?").format(
  657. m=name, mapset=self.selected_mapset.label,
  658. etype=self.selected_type.label,
  659. loc=self.selected_location.label),
  660. title=_('Delete map')) == wx.ID_YES:
  661. label = _("Deleting {name}...").format(name=name)
  662. self.showNotification.emit(message=label)
  663. if self.selected_type.label == 'vector':
  664. removed, cmd = self._runCommand(
  665. 'g.remove', flags='f', type='vector', name=name, env=env)
  666. elif self.selected_type.label == 'raster':
  667. removed, cmd = self._runCommand(
  668. 'g.remove', flags='f', type='raster', name=name, env=env)
  669. else:
  670. removed, cmd = self._runCommand(
  671. 'g.remove', flags='f', type='raster_3d', name=name, env=env)
  672. if removed == 0:
  673. self._model.RemoveNode(self.selected_layer)
  674. self.RefreshNode(self.selected_type, recursive=True)
  675. Debug.msg(1, "LAYER " + name + " DELETED")
  676. self.showNotification.emit(
  677. message=_("g.remove completed").format(cmd=cmd))
  678. gscript.try_remove(gisrc)
  679. def OnDisplayLayer(self, event):
  680. """Display layer in current graphics view"""
  681. layerName = []
  682. if self.selected_location.label == gisenv(
  683. )['LOCATION_NAME'] and self.selected_mapset:
  684. string = self.selected_layer.label + '@' + self.selected_mapset.label
  685. layerName.append(string)
  686. label = _("Displaying {name}...").format(name=string)
  687. self.showNotification.emit(message=label)
  688. label = "d." + self.selected_type.label[:4] + " --q map=" + string + \
  689. _(" -- completed. Go to Layers tab for further operations.")
  690. if self.selected_type.label == 'vector':
  691. self._giface.lmgr.AddMaps(layerName, 'vector', True)
  692. elif self.selected_type.label == 'raster':
  693. self._giface.lmgr.AddMaps(layerName, 'raster', True)
  694. else:
  695. self._giface.lmgr.AddMaps(layerName, 'raster_3d', True)
  696. # generate this message (command) automatically?
  697. label = "d.rast --q map=" + string + _(
  698. " -- completed. Go to Layers tab for further operations.")
  699. self.showNotification.emit(message=label)
  700. Debug.msg(1, "LAYER " + self.selected_layer.label + " DISPLAYED")
  701. else:
  702. GError(
  703. _("Failed to display layer: not in current mapset or invalid layer"),
  704. parent=self)
  705. def OnBeginDrag(self, node, event):
  706. """Just copy necessary data"""
  707. self.DefineItems(node)
  708. if self.selected_layer and not (self._restricted and gisenv()[
  709. 'LOCATION_NAME'] != self.selected_location.label):
  710. event.Allow()
  711. self.OnCopyMap(event)
  712. Debug.msg(1, "DRAG")
  713. else:
  714. event.Veto()
  715. def OnEndDrag(self, node, event):
  716. """Copy layer into target"""
  717. if not wx.GetMouseState().ControlDown():
  718. GMessage(_("Moving maps not implemented"), parent=self)
  719. event.Veto()
  720. return
  721. if node:
  722. self.DefineItems(node)
  723. if self._restricted and gisenv(
  724. )['MAPSET'] != self.selected_mapset.label:
  725. GMessage(
  726. _("Maps can be copied only to current mapset"),
  727. parent=self)
  728. event.Veto()
  729. return
  730. if self.selected_location == self.copy_location and self.selected_mapset:
  731. event.Allow()
  732. self.OnPasteMap(event)
  733. Debug.msg(1, "DROP DONE")
  734. else:
  735. event.Veto()
  736. def OnSwitchLocationMapset(self, event):
  737. genv = gisenv()
  738. if self.selected_location.label == genv['LOCATION_NAME']:
  739. self.changeMapset.emit(mapset=self.selected_mapset.label)
  740. else:
  741. self.changeLocation.emit(mapset=self.selected_mapset.label, location=self.selected_location.label)
  742. self.ExpandCurrentMapset()
  743. def Filter(self, text):
  744. """Filter tree based on name and type."""
  745. text = text.strip()
  746. if len(text.split(':')) > 1:
  747. name = text.split(':')[1].strip()
  748. elem = text.split(':')[0].strip()
  749. if 'r' == elem:
  750. element = 'raster'
  751. elif 'r3' == elem:
  752. element = 'raster_3d'
  753. elif 'v' == elem:
  754. element = 'vector'
  755. else:
  756. element = None
  757. else:
  758. element = None
  759. name = text.strip()
  760. self._model = filterModel(self._orig_model, name=name, element=element)
  761. self.RefreshItems()
  762. self.ExpandCurrentMapset()
  763. def _getNewMapName(self, message, title, value, element, mapset, env):
  764. """Dialog for simple text entry"""
  765. dlg = NameEntryDialog(parent=self, message=message, caption=title,
  766. element=element, env=env, mapset=mapset)
  767. dlg.SetValue(value)
  768. if dlg.ShowModal() == wx.ID_OK:
  769. name = dlg.GetValue()
  770. else:
  771. name = None
  772. dlg.Destroy()
  773. return name
  774. def _confirmDialog(self, question, title):
  775. """Confirm dialog"""
  776. dlg = wx.MessageDialog(self, question, title, wx.YES_NO)
  777. res = dlg.ShowModal()
  778. dlg.Destroy()
  779. return res
  780. def _isCurrent(self, genv):
  781. if self._restricted:
  782. currentMapset = currentLocation = False
  783. if self.selected_location.label == genv['LOCATION_NAME']:
  784. currentLocation = True
  785. if self.selected_mapset.label == genv['MAPSET']:
  786. currentMapset = True
  787. return currentLocation, currentMapset
  788. else:
  789. return True, True
  790. def _popupMenuLayer(self):
  791. """Create popup menu for layers"""
  792. menu = wx.Menu()
  793. genv = gisenv()
  794. currentLocation, currentMapset = self._isCurrent(genv)
  795. item = wx.MenuItem(menu, wx.NewId(), _("&Copy"))
  796. menu.AppendItem(item)
  797. self.Bind(wx.EVT_MENU, self.OnCopyMap, item)
  798. item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
  799. menu.AppendItem(item)
  800. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  801. if not(currentLocation and self.copy_layer):
  802. item.Enable(False)
  803. item = wx.MenuItem(menu, wx.NewId(), _("&Delete"))
  804. menu.AppendItem(item)
  805. self.Bind(wx.EVT_MENU, self.OnDeleteMap, item)
  806. item.Enable(currentMapset)
  807. item = wx.MenuItem(menu, wx.NewId(), _("&Rename"))
  808. menu.AppendItem(item)
  809. self.Bind(wx.EVT_MENU, self.OnRenameMap, item)
  810. item.Enable(currentMapset)
  811. if not isinstance(self._giface, StandaloneGrassInterface) and \
  812. self.selected_location.label == genv['LOCATION_NAME']:
  813. menu.AppendSeparator()
  814. item = wx.MenuItem(menu, wx.NewId(), _("&Display layer"))
  815. menu.AppendItem(item)
  816. self.Bind(wx.EVT_MENU, self.OnDisplayLayer, item)
  817. self.PopupMenu(menu)
  818. menu.Destroy()
  819. def _popupMenuMapset(self):
  820. """Create popup menu for mapsets"""
  821. menu = wx.Menu()
  822. genv = gisenv()
  823. currentLocation, currentMapset = self._isCurrent(genv)
  824. item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
  825. menu.AppendItem(item)
  826. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  827. if not(currentLocation and self.copy_layer):
  828. item.Enable(False)
  829. item = wx.MenuItem(menu, wx.NewId(), _("&Switch mapset"))
  830. menu.AppendItem(item)
  831. self.Bind(wx.EVT_MENU, self.OnSwitchLocationMapset, item)
  832. if (self.selected_location.label == genv['LOCATION_NAME']
  833. and self.selected_mapset.label == genv['MAPSET']):
  834. item.Enable(False)
  835. self.PopupMenu(menu)
  836. menu.Destroy()
  837. def _popupMenuElement(self):
  838. """Create popup menu for elements"""
  839. menu = wx.Menu()
  840. item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
  841. menu.AppendItem(item)
  842. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  843. genv = gisenv()
  844. currentLocation, currentMapset = self._isCurrent(genv)
  845. if not(currentLocation and self.copy_layer):
  846. item.Enable(False)
  847. self.PopupMenu(menu)
  848. menu.Destroy()