tree.py 44 KB

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