tree.py 35 KB

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