tree.py 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662
  1. """
  2. @package datacatalog::tree
  3. @brief Data catalog tree classes
  4. Classes:
  5. - datacatalog::NameEntryDialog
  6. - datacatalog::DataCatalogNode
  7. - datacatalog::DataCatalogTree
  8. (C) 2014-2018 by Tereza Fiedlerova, and the GRASS Development Team
  9. This program is free software under the GNU General Public
  10. License (>=v2). Read the file COPYING that comes with GRASS
  11. for details.
  12. @author Tereza Fiedlerova
  13. @author Anna Petrasova (kratochanna gmail com)
  14. @author Linda Kladivova (l.kladivova@seznam.cz)
  15. """
  16. import os
  17. import re
  18. import copy
  19. from multiprocessing import Process, Queue, cpu_count
  20. import wx
  21. from core.gcmd import RunCommand, GError, GMessage, GWarning
  22. from core.utils import GetListOfLocations
  23. from core.debug import Debug
  24. from core.gthread import gThread
  25. from gui_core.dialogs import TextEntryDialog
  26. from core.giface import StandaloneGrassInterface
  27. from core.treemodel import TreeModel, DictNode
  28. from gui_core.treeview import TreeView
  29. from gui_core.wrap import Menu
  30. from datacatalog.dialogs import CatalogReprojectionDialog
  31. from icons.icon import MetaIcon
  32. from core.settings import UserSettings
  33. from startup.guiutils import (
  34. create_mapset_interactively,
  35. create_location_interactively,
  36. rename_mapset_interactively,
  37. rename_location_interactively,
  38. delete_mapsets_interactively,
  39. delete_locations_interactively,
  40. download_location_interactively,
  41. delete_grassdb_interactively,
  42. can_switch_mapset_interactive,
  43. switch_mapset_interactively,
  44. get_reason_mapset_not_removable,
  45. get_reasons_location_not_removable,
  46. get_mapset_name_invalid_reason,
  47. get_location_name_invalid_reason
  48. )
  49. from grass.grassdb.manage import (
  50. rename_mapset,
  51. rename_location
  52. )
  53. from grass.pydispatch.signal import Signal
  54. import grass.script as gscript
  55. from grass.script import gisenv
  56. from grass.grassdb.data import map_exists
  57. from grass.grassdb.checks import (get_mapset_owner, is_mapset_locked,
  58. is_different_mapset_owner)
  59. from grass.exceptions import CalledModuleError
  60. def filterModel(model, element=None, name=None):
  61. """Filter tree model based on type or name of map using regular expressions.
  62. Copies tree and remove nodes which don't match."""
  63. fmodel = copy.deepcopy(model)
  64. nodesToRemove = []
  65. if name:
  66. try:
  67. regex = re.compile(name)
  68. except:
  69. return fmodel
  70. for gisdbase in fmodel.root.children:
  71. for location in gisdbase.children:
  72. for mapset in location.children:
  73. for layer in mapset.children:
  74. if element and layer.data['type'] != element:
  75. nodesToRemove.append(layer)
  76. continue
  77. if name and regex.search(layer.data['name']) is None:
  78. nodesToRemove.append(layer)
  79. for node in reversed(nodesToRemove):
  80. fmodel.RemoveNode(node)
  81. cleanUpTree(fmodel)
  82. return fmodel
  83. def cleanUpTree(model):
  84. """Removes empty element/mapsets/locations nodes.
  85. It first removes empty elements, then mapsets, then locations"""
  86. # removes empty mapsets
  87. nodesToRemove = []
  88. for gisdbase in model.root.children:
  89. for location in gisdbase.children:
  90. for mapset in location.children:
  91. if not mapset.children:
  92. nodesToRemove.append(mapset)
  93. for node in reversed(nodesToRemove):
  94. model.RemoveNode(node)
  95. # removes empty locations
  96. nodesToRemove = []
  97. for gisdbase in model.root.children:
  98. for location in gisdbase.children:
  99. if not location.children:
  100. nodesToRemove.append(location)
  101. for node in reversed(nodesToRemove):
  102. model.RemoveNode(node)
  103. def getLocationTree(gisdbase, location, queue, mapsets=None):
  104. """Creates dictionary with mapsets, elements, layers for given location.
  105. Returns tuple with the dictionary and error (or None)"""
  106. tmp_gisrc_file, env = gscript.create_environment(gisdbase, location, 'PERMANENT')
  107. env['GRASS_SKIP_MAPSET_OWNER_CHECK'] = '1'
  108. maps_dict = {}
  109. elements = ['raster', 'raster_3d', 'vector']
  110. try:
  111. if not mapsets:
  112. mapsets = gscript.read_command(
  113. 'g.mapsets',
  114. flags='l',
  115. separator='comma',
  116. quiet=True,
  117. env=env).strip()
  118. except CalledModuleError:
  119. queue.put(
  120. (maps_dict,
  121. _("Failed to read mapsets from location <{l}>.").format(
  122. l=location)))
  123. gscript.try_remove(tmp_gisrc_file)
  124. return
  125. else:
  126. mapsets = mapsets.split(',')
  127. Debug.msg(
  128. 4, "Location <{0}>: {1} mapsets found".format(
  129. location, len(mapsets)))
  130. for each in mapsets:
  131. maps_dict[each] = []
  132. try:
  133. maplist = gscript.read_command(
  134. 'g.list', flags='mt', type=elements,
  135. mapset=','.join(mapsets),
  136. quiet=True, env=env).strip()
  137. except CalledModuleError:
  138. queue.put(
  139. (maps_dict,
  140. _("Failed to read maps from location <{l}>.").format(
  141. l=location)))
  142. gscript.try_remove(tmp_gisrc_file)
  143. return
  144. else:
  145. # fill dictionary
  146. listOfMaps = maplist.splitlines()
  147. Debug.msg(
  148. 4, "Location <{0}>: {1} maps found".format(
  149. location, len(listOfMaps)))
  150. for each in listOfMaps:
  151. ltype, wholename = each.split('/')
  152. name, mapset = wholename.split('@', maxsplit=1)
  153. maps_dict[mapset].append({'name': name, 'type': ltype})
  154. queue.put((maps_dict, None))
  155. gscript.try_remove(tmp_gisrc_file)
  156. class NameEntryDialog(TextEntryDialog):
  157. def __init__(self, element, mapset, env, **kwargs):
  158. TextEntryDialog.__init__(self, **kwargs)
  159. self._element = element
  160. self._mapset = mapset
  161. self._env = env
  162. id_OK = self.GetAffirmativeId()
  163. self.Bind(wx.EVT_BUTTON, self.OnOK, self.FindWindowById(id_OK))
  164. def OnOK(self, event):
  165. new = self.GetValue()
  166. if not new:
  167. return
  168. if map_exists(new, self._element, env=self._env, mapset=self._mapset):
  169. dlg = wx.MessageDialog(
  170. self,
  171. message=_(
  172. "Map of type {elem} <{name}> already exists in mapset <{mapset}>. "
  173. "Do you want to overwrite it?").format(
  174. elem=self._element,
  175. name=new,
  176. mapset=self._mapset),
  177. caption=_("Overwrite?"),
  178. style=wx.YES_NO)
  179. if dlg.ShowModal() == wx.ID_YES:
  180. dlg.Destroy()
  181. self._env['GRASS_OVERWRITE'] = '1'
  182. self.EndModal(wx.ID_OK)
  183. else:
  184. dlg.Destroy()
  185. return
  186. else:
  187. self.EndModal(wx.ID_OK)
  188. class DataCatalogNode(DictNode):
  189. """Node representing item in datacatalog."""
  190. def __init__(self, data=None):
  191. super(DataCatalogNode, self).__init__(data=data)
  192. @property
  193. def label(self):
  194. data = self.data
  195. if data['type'] == 'mapset':
  196. owner = data['owner'] if data['owner'] else _("name unknown")
  197. if data['current']:
  198. return _("{name} (current)").format(**data)
  199. elif data['is_different_owner'] and data['lock']:
  200. return _("{name} (in use, owner: {owner})").format(
  201. name=data["name"], owner=owner
  202. )
  203. elif data['lock']:
  204. return _("{name} (in use)").format(**data)
  205. elif data['is_different_owner']:
  206. return _("{name} (owner: {owner})").format(name=data["name"],
  207. owner=owner)
  208. return _("{name}").format(**data)
  209. def match(self, **kwargs):
  210. """Method used for searching according to given parameters.
  211. :param value: dictionary value to be matched
  212. :param key: data dictionary key
  213. """
  214. if not kwargs:
  215. return False
  216. for key in kwargs:
  217. if not (key in self.data and self.data[key] == kwargs[key]):
  218. return False
  219. return True
  220. class DataCatalogTree(TreeView):
  221. def __init__(
  222. self, parent, model=None, giface=None,
  223. style=wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS |
  224. wx.TR_LINES_AT_ROOT | wx.TR_HAS_BUTTONS |
  225. wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE):
  226. """Location Map Tree constructor."""
  227. self._model = TreeModel(DataCatalogNode)
  228. self._orig_model = self._model
  229. super(
  230. DataCatalogTree,
  231. self).__init__(
  232. parent=parent,
  233. model=self._model,
  234. id=wx.ID_ANY,
  235. style=style)
  236. self._giface = giface
  237. self._restricted = True
  238. self.showNotification = Signal('Tree.showNotification')
  239. self.parent = parent
  240. self.contextMenu.connect(self.OnRightClick)
  241. self.itemActivated.connect(self.OnDoubleClick)
  242. self._giface.currentMapsetChanged.connect(self._updateAfterMapsetChanged)
  243. self._iconTypes = ['grassdb', 'location', 'mapset', 'raster',
  244. 'vector', 'raster_3d']
  245. self._initImages()
  246. self.thread = gThread()
  247. self._resetSelectVariables()
  248. self._resetCopyVariables()
  249. self.current_grassdb_node = None
  250. self.current_location_node = None
  251. self.current_mapset_node = None
  252. self.UpdateCurrentDbLocationMapsetNode()
  253. # Get databases from settings
  254. # add current to settings if it's not included
  255. self.grassdatabases = self._getValidSavedGrassDBs()
  256. currentDB = gisenv()['GISDBASE']
  257. if currentDB not in self.grassdatabases:
  258. self.grassdatabases.append(currentDB)
  259. self._saveGrassDBs()
  260. self.beginDrag = Signal('DataCatalogTree.beginDrag')
  261. self.endDrag = Signal('DataCatalogTree.endDrag')
  262. self.startEdit = Signal('DataCatalogTree.startEdit')
  263. self.endEdit = Signal('DataCatalogTree.endEdit')
  264. self.Bind(wx.EVT_TREE_BEGIN_DRAG, lambda evt:
  265. self._emitSignal(evt.GetItem(), self.beginDrag, event=evt))
  266. self.Bind(wx.EVT_TREE_END_DRAG, lambda evt:
  267. self._emitSignal(evt.GetItem(), self.endDrag, event=evt))
  268. self.beginDrag.connect(self.OnBeginDrag)
  269. self.endDrag.connect(self.OnEndDrag)
  270. self.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, lambda evt:
  271. self._emitSignal(evt.GetItem(), self.startEdit, event=evt))
  272. self.Bind(wx.EVT_TREE_END_LABEL_EDIT, lambda evt:
  273. self._emitSignal(evt.GetItem(), self.endEdit, event=evt))
  274. self.startEdit.connect(self.OnStartEditLabel)
  275. self.endEdit.connect(self.OnEditLabel)
  276. def _resetSelectVariables(self):
  277. """Reset variables related to item selection."""
  278. self.selected_grassdb = []
  279. self.selected_layer = []
  280. self.selected_mapset = []
  281. self.selected_location = []
  282. self.mixed = False
  283. def _resetCopyVariables(self):
  284. """Reset copy related variables."""
  285. self.copy_mode = False
  286. self.copy_layer = None
  287. self.copy_mapset = None
  288. self.copy_location = None
  289. self.copy_grassdb = None
  290. def _getValidSavedGrassDBs(self):
  291. """Returns list of GRASS databases from settings.
  292. Returns only existing directories."""
  293. dbs = UserSettings.Get(group='datacatalog',
  294. key='grassdbs',
  295. subkey='listAsString')
  296. dbs = [db for db in dbs.split(',') if os.path.isdir(db)]
  297. return dbs
  298. def _saveGrassDBs(self):
  299. """Save current grass dbs in tree to settings"""
  300. UserSettings.Set(group='datacatalog',
  301. key='grassdbs',
  302. subkey='listAsString',
  303. value=",".join(self.grassdatabases))
  304. grassdbSettings = {}
  305. UserSettings.ReadSettingsFile(settings=grassdbSettings)
  306. if 'datacatalog' not in grassdbSettings:
  307. grassdbSettings['datacatalog'] = UserSettings.Get(group='datacatalog')
  308. # update only dbs
  309. grassdbSettings['datacatalog']['grassdbs'] = UserSettings.Get(group='datacatalog', key='grassdbs')
  310. UserSettings.SaveToFile(grassdbSettings)
  311. def _reloadMapsetNode(self, mapset_node):
  312. """Recursively reload the model of a specific mapset node"""
  313. if mapset_node.children:
  314. del mapset_node.children[:]
  315. q = Queue()
  316. p = Process(
  317. target=getLocationTree,
  318. args=(
  319. mapset_node.parent.parent.data['name'],
  320. mapset_node.parent.data['name'],
  321. q,
  322. mapset_node.data['name']))
  323. p.start()
  324. maps, error = q.get()
  325. self._populateMapsetItem(mapset_node,
  326. maps[mapset_node.data['name']])
  327. self._orig_model = copy.deepcopy(self._model)
  328. return error
  329. def _reloadLocationNode(self, location_node):
  330. """Recursively reload the model of a specific location node"""
  331. if location_node.children:
  332. del location_node.children[:]
  333. q = Queue()
  334. p = Process(
  335. target=getLocationTree,
  336. args=(
  337. location_node.parent.data['name'],
  338. location_node.data['name'],
  339. q,
  340. None))
  341. p.start()
  342. maps, error = q.get()
  343. for mapset in maps:
  344. mapset_path = os.path.join(location_node.parent.data['name'],
  345. location_node.data['name'],
  346. mapset)
  347. mapset_node = self._model.AppendNode(
  348. parent=location_node,
  349. data=dict(type='mapset',
  350. name=mapset,
  351. current=False,
  352. lock=is_mapset_locked(mapset_path),
  353. is_different_owner=is_different_mapset_owner(mapset_path),
  354. owner=get_mapset_owner(mapset_path)))
  355. self._populateMapsetItem(mapset_node,
  356. maps[mapset])
  357. self._model.SortChildren(location_node)
  358. self._orig_model = copy.deepcopy(self._model)
  359. return error
  360. def _reloadGrassDBNode(self, grassdb_node):
  361. """Recursively reload the model of a specific grassdb node.
  362. Runs reloading locations in parallel."""
  363. if grassdb_node.children:
  364. del grassdb_node.children[:]
  365. locations = GetListOfLocations(grassdb_node.data['name'])
  366. loc_count = proc_count = 0
  367. queue_list = []
  368. proc_list = []
  369. loc_list = []
  370. try:
  371. nprocs = cpu_count()
  372. except NotImplementedError:
  373. nprocs = 4
  374. results = dict()
  375. errors = []
  376. location_nodes = []
  377. all_location_nodes = []
  378. nlocations = len(locations)
  379. for location in locations:
  380. results[location] = dict()
  381. varloc = self._model.AppendNode(parent=grassdb_node,
  382. data=dict(type='location',
  383. name=location))
  384. location_nodes.append(varloc)
  385. all_location_nodes.append(varloc)
  386. loc_count += 1
  387. Debug.msg(
  388. 3, "Scanning location <{0}> ({1}/{2})".format(location, loc_count, nlocations))
  389. q = Queue()
  390. p = Process(target=getLocationTree,
  391. args=(grassdb_node.data['name'], location, q))
  392. p.start()
  393. queue_list.append(q)
  394. proc_list.append(p)
  395. loc_list.append(location)
  396. proc_count += 1
  397. # Wait for all running processes
  398. if proc_count == nprocs or loc_count == nlocations:
  399. Debug.msg(4, "Process subresults")
  400. for i in range(len(loc_list)):
  401. maps, error = queue_list[i].get()
  402. proc_list[i].join()
  403. if error:
  404. errors.append(error)
  405. for key in sorted(maps.keys()):
  406. mapset_path = os.path.join(location_nodes[i].parent.data['name'],
  407. location_nodes[i].data['name'],
  408. key)
  409. mapset_node = self._model.AppendNode(
  410. parent=location_nodes[i],
  411. data=dict(type='mapset',
  412. name=key,
  413. lock=is_mapset_locked(mapset_path),
  414. current=False,
  415. is_different_owner=is_different_mapset_owner(mapset_path),
  416. owner=get_mapset_owner(mapset_path)))
  417. self._populateMapsetItem(mapset_node, maps[key])
  418. proc_count = 0
  419. proc_list = []
  420. queue_list = []
  421. loc_list = []
  422. location_nodes = []
  423. for node in all_location_nodes:
  424. self._model.SortChildren(node)
  425. self._model.SortChildren(grassdb_node)
  426. self._orig_model = copy.deepcopy(self._model)
  427. return errors
  428. def _reloadTreeItems(self):
  429. """Updates grass databases, locations, mapsets and layers in the tree.
  430. It runs in thread, so it should not directly interact with GUI.
  431. In case of any errors it returns the errors as a list of strings, otherwise None.
  432. """
  433. errors = []
  434. for grassdatabase in self.grassdatabases:
  435. grassdb_nodes = self._model.SearchNodes(name=grassdatabase,
  436. type='grassdb')
  437. if not grassdb_nodes:
  438. grassdb_node = self._model.AppendNode(parent=self._model.root,
  439. data=dict(type='grassdb',
  440. name=grassdatabase))
  441. else:
  442. grassdb_node = grassdb_nodes[0]
  443. error = self._reloadGrassDBNode(grassdb_node)
  444. if error:
  445. errors += error
  446. if errors:
  447. return errors
  448. return None
  449. def _renameNode(self, node, name):
  450. """Rename node (map, mapset, location), sort and refresh.
  451. Should be called after actual renaming of a map, mapset, location."""
  452. node.data['name'] = name
  453. self._model.SortChildren(node.parent)
  454. self.RefreshNode(node.parent, recursive=True)
  455. def UpdateCurrentDbLocationMapsetNode(self):
  456. """Update variables storing current mapset/location/grassdb node.
  457. Updates associated mapset node data ('lock' and 'current').
  458. """
  459. def is_current_mapset_node_locked():
  460. mapset_path = os.path.join(self.current_grassdb_node.data['name'],
  461. self.current_location_node.data['name'],
  462. self.current_mapset_node.data["name"])
  463. return is_mapset_locked(mapset_path)
  464. if self.current_mapset_node:
  465. self.current_mapset_node.data["current"] = False
  466. self.current_mapset_node.data["lock"] = is_current_mapset_node_locked()
  467. self.current_grassdb_node, self.current_location_node, self.current_mapset_node = \
  468. self.GetCurrentDbLocationMapsetNode()
  469. if self.current_mapset_node:
  470. self.current_mapset_node.data["current"] = True
  471. self.current_mapset_node.data["lock"] = is_current_mapset_node_locked()
  472. def ReloadTreeItems(self):
  473. """Reload dbs, locations, mapsets and layers in the tree."""
  474. self.busy = wx.BusyCursor()
  475. self.thread.Run(callable=self._reloadTreeItems,
  476. ondone=self._loadItemsDone)
  477. def _loadItemsDone(self, event):
  478. Debug.msg(1, "Tree filled")
  479. del self.busy
  480. if event.ret is not None:
  481. self._giface.WriteWarning('\n'.join(event.ret))
  482. self.UpdateCurrentDbLocationMapsetNode()
  483. self.RefreshItems()
  484. self.ExpandCurrentMapset()
  485. def ReloadCurrentMapset(self):
  486. """Reload current mapset tree only."""
  487. self.UpdateCurrentDbLocationMapsetNode()
  488. if not self.current_grassdb_node or not self.current_location_node or not self.current_mapset_node:
  489. return
  490. self._reloadMapsetNode(self.current_mapset_node)
  491. self.RefreshNode(self.current_mapset_node, recursive=True)
  492. def _populateMapsetItem(self, mapset_node, data):
  493. for item in data:
  494. self._model.AppendNode(parent=mapset_node,
  495. data=dict(**item))
  496. self._model.SortChildren(mapset_node)
  497. def _initImages(self):
  498. bmpsize = (16, 16)
  499. icons = {
  500. 'grassdb': MetaIcon(img='grassdb').GetBitmap(bmpsize),
  501. 'location': MetaIcon(img='location').GetBitmap(bmpsize),
  502. 'mapset': MetaIcon(img='mapset').GetBitmap(bmpsize),
  503. 'raster': MetaIcon(img='raster').GetBitmap(bmpsize),
  504. 'vector': MetaIcon(img='vector').GetBitmap(bmpsize),
  505. 'raster_3d': MetaIcon(img='raster3d').GetBitmap(bmpsize)
  506. }
  507. il = wx.ImageList(bmpsize[0], bmpsize[1], mask=False)
  508. for each in self._iconTypes:
  509. il.Add(icons[each])
  510. self.AssignImageList(il)
  511. def GetControl(self):
  512. """Returns control itself."""
  513. return self
  514. def DefineItems(self, selected):
  515. """Set selected items."""
  516. self._resetSelectVariables()
  517. mixed = []
  518. for item in selected:
  519. type = item.data['type']
  520. if type in ('raster', 'raster_3d', 'vector'):
  521. self.selected_layer.append(item)
  522. self.selected_mapset.append(item.parent)
  523. self.selected_location.append(item.parent.parent)
  524. self.selected_grassdb.append(item.parent.parent.parent)
  525. mixed.append('layer')
  526. elif type == 'mapset':
  527. self.selected_layer.append(None)
  528. self.selected_mapset.append(item)
  529. self.selected_location.append(item.parent)
  530. self.selected_grassdb.append(item.parent.parent)
  531. mixed.append('mapset')
  532. elif type == 'location':
  533. self.selected_layer.append(None)
  534. self.selected_mapset.append(None)
  535. self.selected_location.append(item)
  536. self.selected_grassdb.append(item.parent)
  537. mixed.append('location')
  538. elif type == 'grassdb':
  539. self.selected_layer.append(None)
  540. self.selected_mapset.append(None)
  541. self.selected_location.append(None)
  542. self.selected_grassdb.append(item)
  543. mixed.append('grassdb')
  544. self.mixed = False
  545. if len(set(mixed)) > 1:
  546. self.mixed = True
  547. def OnSelChanged(self, event):
  548. self.selected_layer = None
  549. def OnRightClick(self, node):
  550. """Display popup menu."""
  551. self.DefineItems(self.GetSelected())
  552. if self.mixed:
  553. self._popupMenuEmpty()
  554. return
  555. if not self.selected_layer:
  556. self._popupMenuEmpty()
  557. elif self.selected_layer[0]:
  558. self._popupMenuLayer()
  559. elif self.selected_mapset[0] and len(self.selected_mapset) == 1:
  560. self._popupMenuMapset()
  561. elif self.selected_location[0] and not self.selected_mapset[0] and len(self.selected_location) == 1:
  562. self._popupMenuLocation()
  563. elif self.selected_grassdb[0] and not self.selected_location[0] and len(self.selected_grassdb) == 1:
  564. self._popupMenuGrassDb()
  565. elif len(self.selected_grassdb) > 1 and not self.selected_location[0]:
  566. self._popupMenuEmpty()
  567. elif len(self.selected_location) > 1 and not self.selected_mapset[0]:
  568. self._popupMenuMultipleLocations()
  569. elif len(self.selected_mapset) > 1:
  570. self._popupMenuMultipleMapsets()
  571. else:
  572. self._popupMenuEmpty()
  573. def OnDoubleClick(self, node):
  574. """Double click on item/node.
  575. Display selected layer if node is a map layer otherwise
  576. expand/collapse node.
  577. """
  578. if not isinstance(self._giface, StandaloneGrassInterface):
  579. self.DefineItems([node])
  580. selected_layer = self.selected_layer[0]
  581. selected_mapset = self.selected_mapset[0]
  582. selected_loc = self.selected_location[0]
  583. if selected_layer is not None:
  584. genv = gisenv()
  585. # Check if the layer is in different location
  586. if selected_loc.data['name'] != genv['LOCATION_NAME']:
  587. dlg = wx.MessageDialog(
  588. parent=self,
  589. message=_(
  590. "Map <{0}@{1}> is not in the current location"
  591. " and therefore cannot be displayed."
  592. "\n\n"
  593. "To display this map switch to mapset <{1}> first."
  594. ).format(selected_layer.data['name'],
  595. selected_mapset.data['name']),
  596. caption=_("Unable to display the map"),
  597. style=wx.OK | wx.ICON_WARNING
  598. )
  599. dlg.ShowModal()
  600. dlg.Destroy()
  601. else:
  602. self.DisplayLayer()
  603. return
  604. # expand/collapse location/mapset...
  605. if self.IsNodeExpanded(node):
  606. self.CollapseNode(node, recursive=False)
  607. else:
  608. self.ExpandNode(node, recursive=False)
  609. def ExpandCurrentLocation(self):
  610. """Expand current location"""
  611. location = gscript.gisenv()['LOCATION_NAME']
  612. item = self._model.SearchNodes(name=location, type='location')
  613. if item:
  614. self.Select(item[0], select=True)
  615. self.ExpandNode(item[0], recursive=False)
  616. else:
  617. Debug.msg(1, "Location <%s> not found" % location)
  618. def GetCurrentDbLocationMapsetNode(self):
  619. """Get current mapset node"""
  620. genv = gisenv()
  621. gisdbase = genv['GISDBASE']
  622. location = genv['LOCATION_NAME']
  623. mapset = genv['MAPSET']
  624. grassdbItem = self._model.SearchNodes(
  625. name=gisdbase, type='grassdb')
  626. if not grassdbItem:
  627. return None, None, None
  628. locationItem = self._model.SearchNodes(
  629. parent=grassdbItem[0],
  630. name=location, type='location')
  631. if not locationItem:
  632. return grassdbItem[0], None, None
  633. mapsetItem = self._model.SearchNodes(
  634. parent=locationItem[0],
  635. name=mapset,
  636. type='mapset')
  637. if not mapsetItem:
  638. return grassdbItem[0], locationItem[0], None
  639. return grassdbItem[0], locationItem[0], mapsetItem[0]
  640. def OnGetItemImage(self, index, which=wx.TreeItemIcon_Normal, column=0):
  641. """Overriden method to return image for each item."""
  642. node = self._model.GetNodeByIndex(index)
  643. try:
  644. return self._iconTypes.index(node.data['type'])
  645. except ValueError:
  646. return 0
  647. def OnGetItemTextColour(self, index):
  648. """Overriden method to return colour for each item.
  649. Used to distinquish lock and ownership on mapsets."""
  650. node = self._model.GetNodeByIndex(index)
  651. if node.data['type'] == 'mapset':
  652. if node.data['current']:
  653. return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
  654. elif node.data['lock'] or node.data['is_different_owner']:
  655. return wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)
  656. return wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
  657. def OnGetItemFont(self, index):
  658. """Overriden method to return font for each item.
  659. Used to highlight current db/loc/mapset."""
  660. node = self._model.GetNodeByIndex(index)
  661. font = self.GetFont()
  662. if node.data['type'] in ('grassdb', 'location', 'mapset'):
  663. if node in (self.current_grassdb_node, self.current_location_node, self.current_mapset_node):
  664. font.SetWeight(wx.FONTWEIGHT_BOLD)
  665. else:
  666. font.SetWeight(wx.FONTWEIGHT_NORMAL)
  667. return font
  668. def ExpandCurrentMapset(self):
  669. """Expand current mapset"""
  670. if self.current_mapset_node:
  671. self.Select(self.current_mapset_node, select=True)
  672. self.ExpandNode(self.current_mapset_node, recursive=True)
  673. def SetRestriction(self, restrict):
  674. self._restricted = restrict
  675. def _runCommand(self, prog, **kwargs):
  676. cmdString = ' '.join(gscript.make_command(prog, **kwargs))
  677. ret = RunCommand(prog, parent=self, **kwargs)
  678. return ret, cmdString
  679. def OnMoveMap(self, event):
  680. """Move layer or mapset (just save it temporarily, copying is done by paste)"""
  681. self.copy_mode = False
  682. self.copy_layer = self.selected_layer[:]
  683. self.copy_mapset = self.selected_mapset[:]
  684. self.copy_location = self.selected_location[:]
  685. self.copy_grassdb = self.selected_grassdb[:]
  686. if len(self.copy_layer) > 1:
  687. label = _("{c} maps marked for moving.").format(c=len(self.selected_layer))
  688. else:
  689. label = _("Map <{layer}> marked for moving.").format(layer=self.copy_layer[0].data['name'])
  690. self.showNotification.emit(message=label)
  691. def OnCopyMap(self, event):
  692. """Copy layer or mapset (just save it temporarily, copying is done by paste)"""
  693. self.copy_mode = True
  694. self.copy_layer = self.selected_layer[:]
  695. self.copy_mapset = self.selected_mapset[:]
  696. self.copy_location = self.selected_location[:]
  697. self.copy_grassdb = self.selected_grassdb[:]
  698. if len(self.copy_layer) > 1:
  699. label = _("{c} maps marked for copying.").format(c=len(self.selected_layer))
  700. else:
  701. label = _("Map <{layer}> marked for copying.").format(layer=self.copy_layer[0].data['name'])
  702. self.showNotification.emit(message=label)
  703. def OnRenameMap(self, event):
  704. """Rename layer with dialog"""
  705. old_name = self.selected_layer[0].data['name']
  706. gisrc, env = gscript.create_environment(
  707. self.selected_grassdb[0].data['name'],
  708. self.selected_location[0].data['name'],
  709. self.selected_mapset[0].data['name'])
  710. new_name = self._getNewMapName(
  711. _('New name'),
  712. _('Rename map'),
  713. old_name,
  714. env=env,
  715. mapset=self.selected_mapset[0].data['name'],
  716. element=self.selected_layer[0].data['type'])
  717. if new_name:
  718. self.Rename(old_name, new_name)
  719. def CreateMapset(self, grassdb_node, location_node):
  720. """Creates new mapset interactively and adds it to the tree."""
  721. mapset = create_mapset_interactively(self, grassdb_node.data['name'],
  722. location_node.data['name'])
  723. if mapset:
  724. self.InsertMapset(name=mapset,
  725. location_node=location_node)
  726. def OnCreateMapset(self, event):
  727. """Create new mapset"""
  728. self.CreateMapset(self.selected_grassdb[0], self.selected_location[0])
  729. def CreateLocation(self, grassdb_node):
  730. """
  731. Creates new location interactively and adds it to the tree.
  732. """
  733. grassdatabase, location, mapset = (
  734. create_location_interactively(self, grassdb_node.data['name'])
  735. )
  736. if location:
  737. grassdb_nodes = self._model.SearchNodes(name=grassdatabase, type='grassdb')
  738. if not grassdb_nodes:
  739. grassdb_node = self.InsertGrassDb(name=grassdatabase)
  740. self.InsertLocation(location, grassdb_node)
  741. def OnCreateLocation(self, event):
  742. """Create new location"""
  743. self.CreateLocation(self.selected_grassdb[0])
  744. def OnRenameMapset(self, event):
  745. """
  746. Rename selected mapset
  747. """
  748. newmapset = rename_mapset_interactively(
  749. self,
  750. self.selected_grassdb[0].data['name'],
  751. self.selected_location[0].data['name'],
  752. self.selected_mapset[0].data['name'])
  753. if newmapset:
  754. self._renameNode(self.selected_mapset[0], newmapset)
  755. def OnRenameLocation(self, event):
  756. """
  757. Rename selected location
  758. """
  759. newlocation = rename_location_interactively(
  760. self,
  761. self.selected_grassdb[0].data['name'],
  762. self.selected_location[0].data['name'])
  763. if newlocation:
  764. self._renameNode(self.selected_location[0], newlocation)
  765. def OnStartEditLabel(self, node, event):
  766. """Start label editing"""
  767. self.DefineItems([node])
  768. # Not allowed for grassdb node
  769. if node.data['type'] == 'grassdb':
  770. event.Veto()
  771. # Check selected mapset
  772. elif node.data['type'] == 'mapset':
  773. if (
  774. self._restricted
  775. or get_reason_mapset_not_removable(self.selected_grassdb[0].data['name'],
  776. self.selected_location[0].data['name'],
  777. self.selected_mapset[0].data['name'],
  778. check_permanent=True)
  779. ):
  780. event.Veto()
  781. # Check selected location
  782. elif node.data['type'] == 'location':
  783. if (
  784. self._restricted
  785. or get_reasons_location_not_removable(self.selected_grassdb[0].data['name'],
  786. self.selected_location[0].data['name'])
  787. ):
  788. event.Veto()
  789. elif node.data['type'] in ('raster', 'raster_3d', 'vector'):
  790. currentGrassDb, currentLocation, currentMapset = self._isCurrent(gisenv())
  791. if not currentMapset:
  792. event.Veto()
  793. def OnEditLabel(self, node, event):
  794. """End label editing"""
  795. if event.IsEditCancelled():
  796. return
  797. old_name = node.data['name']
  798. Debug.msg(1, "End label edit {name}".format(name=old_name))
  799. new_name = event.GetLabel()
  800. if node.data['type'] in ('raster', 'raster_3d', 'vector'):
  801. self.Rename(old_name, new_name)
  802. elif node.data['type'] == 'mapset':
  803. message = get_mapset_name_invalid_reason(
  804. self.selected_grassdb[0].data['name'],
  805. self.selected_location[0].data['name'],
  806. new_name)
  807. if message:
  808. GError(parent=self, message=message,
  809. caption=_("Cannot rename mapset"),
  810. showTraceback=False)
  811. event.Veto()
  812. return
  813. rename_mapset(self.selected_grassdb[0].data['name'],
  814. self.selected_location[0].data['name'],
  815. self.selected_mapset[0].data['name'],
  816. new_name)
  817. self._renameNode(self.selected_mapset[0], new_name)
  818. label = _(
  819. "Renaming mapset <{oldmapset}> to <{newmapset}> completed").format(
  820. oldmapset=old_name, newmapset=new_name)
  821. self.showNotification.emit(message=label)
  822. elif node.data['type'] == 'location':
  823. message = get_location_name_invalid_reason(
  824. self.selected_grassdb[0].data['name'],
  825. new_name)
  826. if message:
  827. GError(parent=self, message=message,
  828. caption=_("Cannot rename location"),
  829. showTraceback=False)
  830. event.Veto()
  831. return
  832. rename_location(self.selected_grassdb[0].data['name'],
  833. self.selected_location[0].data['name'],
  834. new_name)
  835. self._renameNode(self.selected_location[0], new_name)
  836. label = _(
  837. "Renaming location <{oldlocation}> to <{newlocation}> completed").format(
  838. oldlocation=old_name, newlocation=new_name)
  839. self.showNotification.emit(message=label)
  840. def Rename(self, old, new):
  841. """Rename layer"""
  842. string = old + ',' + new
  843. gisrc, env = gscript.create_environment(
  844. self.selected_grassdb[0].data['name'],
  845. self.selected_location[0].data['name'],
  846. self.selected_mapset[0].data['name'])
  847. label = _("Renaming map <{name}>...").format(name=string)
  848. self.showNotification.emit(message=label)
  849. if self.selected_layer[0].data['type'] == 'vector':
  850. renamed, cmd = self._runCommand('g.rename', vector=string, env=env)
  851. elif self.selected_layer[0].data['type'] == 'raster':
  852. renamed, cmd = self._runCommand('g.rename', raster=string, env=env)
  853. else:
  854. renamed, cmd = self._runCommand(
  855. 'g.rename', raster3d=string, env=env)
  856. if renamed == 0:
  857. self._renameNode(self.selected_layer[0], new)
  858. self.showNotification.emit(
  859. message=_("{cmd} -- completed").format(cmd=cmd))
  860. Debug.msg(1, "LAYER RENAMED TO: " + new)
  861. gscript.try_remove(gisrc)
  862. def OnPasteMap(self, event):
  863. # copying between mapsets of one location
  864. if not self.copy_layer:
  865. if self.copy_mode:
  866. GMessage(_("No map selected for copying."), parent=self)
  867. else:
  868. GMessage(_("No map selected for moving."), parent=self)
  869. return
  870. for i in range(len(self.copy_layer)):
  871. gisrc, env = gscript.create_environment(self.selected_grassdb[0].data['name'],
  872. self.selected_location[0].data['name'],
  873. self.selected_mapset[0].data['name'])
  874. gisrc2, env2 = gscript.create_environment(self.copy_grassdb[i].data['name'],
  875. self.copy_location[i].data['name'],
  876. self.copy_mapset[i].data['name'])
  877. new_name = self.copy_layer[i].data['name']
  878. if self.selected_location[0] == self.copy_location[i]:
  879. # within one mapset
  880. if self.selected_mapset[0] == self.copy_mapset[i]:
  881. # ignore when just moves map
  882. if self.copy_mode is False:
  883. return
  884. new_name = self._getNewMapName(_('New name for <{n}>').format(n=self.copy_layer[i].data['name']),
  885. _('Select new name'),
  886. self.copy_layer[i].data['name'], env=env,
  887. mapset=self.selected_mapset[0].data['name'],
  888. element=self.copy_layer[i].data['type'])
  889. if not new_name:
  890. return
  891. # within one location, different mapsets
  892. else:
  893. if map_exists(new_name, element=self.copy_layer[i].data['type'], env=env,
  894. mapset=self.selected_mapset[0].data['name']):
  895. new_name = self._getNewMapName(_('New name for <{n}>').format(n=self.copy_layer[i].data['name']),
  896. _('Select new name'),
  897. self.copy_layer[i].data['name'], env=env,
  898. mapset=self.selected_mapset[0].data['name'],
  899. element=self.copy_layer[i].data['type'])
  900. if not new_name:
  901. return
  902. string = self.copy_layer[i].data['name'] + '@' + self.copy_mapset[i].data['name'] + ',' + new_name
  903. pasted = 0
  904. if self.copy_mode:
  905. label = _("Copying <{name}>...").format(name=string)
  906. else:
  907. label = _("Moving <{name}>...").format(name=string)
  908. self.showNotification.emit(message=label)
  909. if self.copy_layer[i].data['type'] == 'vector':
  910. pasted, cmd = self._runCommand('g.copy', vector=string, env=env)
  911. node = 'vector'
  912. elif self.copy_layer[i].data['type'] == 'raster':
  913. pasted, cmd = self._runCommand('g.copy', raster=string, env=env)
  914. node = 'raster'
  915. else:
  916. pasted, cmd = self._runCommand('g.copy', raster_3d=string, env=env)
  917. node = 'raster_3d'
  918. if pasted == 0:
  919. self.InsertLayer(name=new_name, mapset_node=self.selected_mapset[0],
  920. element_name=node)
  921. Debug.msg(1, "COPIED TO: " + new_name)
  922. if self.copy_mode:
  923. self.showNotification.emit(message=_("g.copy completed"))
  924. else:
  925. self.showNotification.emit(message=_("g.copy completed"))
  926. # remove old
  927. if not self.copy_mode:
  928. self._removeMapAfterCopy(self.copy_layer[i], self.copy_mapset[i], env2)
  929. gscript.try_remove(gisrc)
  930. gscript.try_remove(gisrc2)
  931. # expand selected mapset
  932. else:
  933. if self.copy_layer[i].data['type'] == 'raster_3d':
  934. GError(_("Reprojection is not implemented for 3D rasters"), parent=self)
  935. return
  936. if map_exists(new_name, element=self.copy_layer[i].data['type'], env=env,
  937. mapset=self.selected_mapset[0].data['name']):
  938. new_name = self._getNewMapName(_('New name'), _('Select new name'),
  939. self.copy_layer[i].data['name'], env=env,
  940. mapset=self.selected_mapset[0].data['name'],
  941. element=self.copy_layer[i].data['type'])
  942. if not new_name:
  943. continue
  944. callback = lambda gisrc2=gisrc2, gisrc=gisrc, cLayer=self.copy_layer[i], \
  945. cMapset=self.copy_mapset[i], cMode=self.copy_mode, name=new_name: \
  946. self._onDoneReprojection(env2, gisrc2, gisrc, cLayer, cMapset, cMode, name)
  947. dlg = CatalogReprojectionDialog(self, self._giface,
  948. self.copy_grassdb[i].data['name'],
  949. self.copy_location[i].data['name'],
  950. self.copy_mapset[i].data['name'],
  951. self.copy_layer[i].data['name'],
  952. env2,
  953. self.selected_grassdb[0].data['name'],
  954. self.selected_location[0].data['name'],
  955. self.selected_mapset[0].data['name'],
  956. new_name,
  957. self.copy_layer[i].data['type'],
  958. env, callback)
  959. if dlg.ShowModal() == wx.ID_CANCEL:
  960. return
  961. self.ExpandNode(self.selected_mapset[0], recursive=True)
  962. self._resetCopyVariables()
  963. def _onDoneReprojection(self, iEnv, iGisrc, oGisrc, cLayer, cMapset, cMode, name):
  964. self.InsertLayer(name=name, mapset_node=self.selected_mapset[0],
  965. element_name=cLayer.data['type'])
  966. if not cMode:
  967. self._removeMapAfterCopy(cLayer, cMapset, iEnv)
  968. gscript.try_remove(iGisrc)
  969. gscript.try_remove(oGisrc)
  970. self.ExpandNode(self.selected_mapset[0], recursive=True)
  971. def _removeMapAfterCopy(self, cLayer, cMapset, env):
  972. removed, cmd = self._runCommand('g.remove', type=cLayer.data['type'],
  973. name=cLayer.data['name'], flags='f', env=env)
  974. if removed == 0:
  975. self._model.RemoveNode(cLayer)
  976. self.RefreshNode(cMapset, recursive=True)
  977. Debug.msg(1, "LAYER " + cLayer.data['name'] + " DELETED")
  978. self.showNotification.emit(message=_("g.remove completed"))
  979. def InsertLayer(self, name, mapset_node, element_name):
  980. """Insert layer into model and refresh tree"""
  981. self._model.AppendNode(parent=mapset_node,
  982. data=dict(type=element_name, name=name))
  983. self._model.SortChildren(mapset_node)
  984. self.RefreshNode(mapset_node, recursive=True)
  985. def InsertMapset(self, name, location_node):
  986. """Insert new mapset into model and refresh tree.
  987. Assumes mapset is empty."""
  988. mapset_path = os.path.join(location_node.parent.data['name'],
  989. location_node.data['name'],
  990. name)
  991. mapset_node = self._model.AppendNode(parent=location_node,
  992. data=dict(type='mapset',
  993. name=name,
  994. current=False,
  995. lock=is_mapset_locked(mapset_path),
  996. is_different_owner=is_different_mapset_owner(mapset_path),
  997. owner=get_mapset_owner(mapset_path)))
  998. self._model.SortChildren(location_node)
  999. self.RefreshNode(location_node, recursive=True)
  1000. return mapset_node
  1001. def InsertLocation(self, name, grassdb_node):
  1002. """Insert new location into model and refresh tree"""
  1003. location_node = self._model.AppendNode(parent=grassdb_node,
  1004. data=dict(type='location', name=name))
  1005. # reload new location since it has a mapset
  1006. self._reloadLocationNode(location_node)
  1007. self._model.SortChildren(grassdb_node)
  1008. self.RefreshNode(grassdb_node, recursive=True)
  1009. return location_node
  1010. def InsertGrassDb(self, name):
  1011. """
  1012. Insert new grass db into model, update user setting and refresh tree.
  1013. Check if not already added.
  1014. """
  1015. grassdb_node = self._model.SearchNodes(name=name,
  1016. type='grassdb')
  1017. if not grassdb_node:
  1018. grassdb_node = self._model.AppendNode(parent=self._model.root,
  1019. data=dict(type="grassdb", name=name))
  1020. self._reloadGrassDBNode(grassdb_node)
  1021. self.RefreshItems()
  1022. # Update user's settings
  1023. self.grassdatabases.append(name)
  1024. self._saveGrassDBs()
  1025. return grassdb_node
  1026. def OnDeleteMap(self, event):
  1027. """Delete layer or mapset"""
  1028. names = [self.selected_layer[i].data['name'] + '@' + self.selected_mapset[i].data['name']
  1029. for i in range(len(self.selected_layer))]
  1030. if len(names) < 10:
  1031. question = _("Do you really want to delete map(s) <{m}>?").format(m=', '.join(names))
  1032. else:
  1033. question = _("Do you really want to delete {n} maps?").format(n=len(names))
  1034. if self._confirmDialog(question, title=_('Delete map')) == wx.ID_YES:
  1035. label = _("Deleting {name}...").format(name=names)
  1036. self.showNotification.emit(message=label)
  1037. for i in range(len(self.selected_layer)):
  1038. gisrc, env = gscript.create_environment(
  1039. self.selected_grassdb[i].data['name'],
  1040. self.selected_location[i].data['name'],
  1041. self.selected_mapset[i].data['name'])
  1042. removed, cmd = self._runCommand(
  1043. 'g.remove', flags='f', type=self.selected_layer[i].data['type'],
  1044. name=self.selected_layer[i].data['name'], env=env)
  1045. if removed == 0:
  1046. self._model.RemoveNode(self.selected_layer[i])
  1047. self.RefreshNode(self.selected_mapset[i], recursive=True)
  1048. Debug.msg(1, "LAYER " + self.selected_layer[i].data['name'] + " DELETED")
  1049. # remove map layer from layer tree if exists
  1050. if not isinstance(self._giface, StandaloneGrassInterface):
  1051. name = self.selected_layer[i].data['name'] + '@' + self.selected_mapset[i].data['name']
  1052. layers = self._giface.GetLayerList().GetLayersByName(name)
  1053. for layer in layers:
  1054. self._giface.GetLayerList().DeleteLayer(layer)
  1055. gscript.try_remove(gisrc)
  1056. self.UnselectAll()
  1057. self.showNotification.emit(message=_("g.remove completed"))
  1058. def OnDeleteMapset(self, event):
  1059. """
  1060. Delete selected mapset or mapsets
  1061. """
  1062. mapsets = []
  1063. for i in range(len(self.selected_mapset)):
  1064. # Append to the list of tuples
  1065. mapsets.append((
  1066. self.selected_grassdb[i].data['name'],
  1067. self.selected_location[i].data['name'],
  1068. self.selected_mapset[i].data['name']
  1069. ))
  1070. if delete_mapsets_interactively(self, mapsets):
  1071. locations = set([each for each in self.selected_location])
  1072. for loc_node in locations:
  1073. self._reloadLocationNode(loc_node)
  1074. self.UpdateCurrentDbLocationMapsetNode()
  1075. self.RefreshNode(loc_node, recursive=True)
  1076. def OnDeleteLocation(self, event):
  1077. """
  1078. Delete selected location or locations
  1079. """
  1080. locations = []
  1081. for i in range(len(self.selected_location)):
  1082. # Append to the list of tuples
  1083. locations.append((
  1084. self.selected_grassdb[i].data['name'],
  1085. self.selected_location[i].data['name']
  1086. ))
  1087. if delete_locations_interactively(self, locations):
  1088. grassdbs = set([each for each in self.selected_grassdb])
  1089. for grassdb_node in grassdbs:
  1090. self._reloadGrassDBNode(grassdb_node)
  1091. self.UpdateCurrentDbLocationMapsetNode()
  1092. self.RefreshNode(grassdb_node, recursive=True)
  1093. def DownloadLocation(self, grassdb_node):
  1094. """
  1095. Download new location interactively.
  1096. """
  1097. grassdatabase, location, mapset = (
  1098. download_location_interactively(self, grassdb_node.data['name'])
  1099. )
  1100. if location:
  1101. self._reloadGrassDBNode(grassdb_node)
  1102. self.UpdateCurrentDbLocationMapsetNode()
  1103. self.RefreshItems()
  1104. def OnDownloadLocation(self, event):
  1105. """
  1106. Download location online
  1107. """
  1108. self.DownloadLocation(self.selected_grassdb[0])
  1109. def DeleteGrassDb(self, grassdb_node):
  1110. """
  1111. Delete grassdb from disk.
  1112. """
  1113. grassdb = grassdb_node.data['name']
  1114. if (delete_grassdb_interactively(self, grassdb)):
  1115. self.RemoveGrassDB(grassdb_node)
  1116. def OnDeleteGrassDb(self, event):
  1117. """
  1118. Delete grassdb from disk.
  1119. """
  1120. self.DeleteGrassDb(self.selected_grassdb[0])
  1121. def OnRemoveGrassDb(self, event):
  1122. """
  1123. Remove grassdb node from data catalogue.
  1124. """
  1125. self.RemoveGrassDB(self.selected_grassdb[0])
  1126. def RemoveGrassDB(self, grassdb_node):
  1127. """
  1128. Remove grassdb node from tree
  1129. and updates settings. Doesn't check if it's current db.
  1130. """
  1131. self.grassdatabases.remove(grassdb_node.data['name'])
  1132. self._model.RemoveNode(grassdb_node)
  1133. self.RefreshItems()
  1134. # Update user's settings
  1135. self._saveGrassDBs()
  1136. def OnDisplayLayer(self, event):
  1137. """
  1138. Display layer in current graphics view
  1139. """
  1140. self.DisplayLayer()
  1141. def DisplayLayer(self):
  1142. """Display selected layer in current graphics view"""
  1143. all_names = []
  1144. names = {'raster': [], 'vector': [], 'raster_3d': []}
  1145. for i in range(len(self.selected_layer)):
  1146. name = self.selected_layer[i].data['name'] + '@' + self.selected_mapset[i].data['name']
  1147. names[self.selected_layer[i].data['type']].append(name)
  1148. all_names.append(name)
  1149. #if self.selected_location[0].data['name'] == gisenv()['LOCATION_NAME'] and self.selected_mapset[0]:
  1150. for ltype in names:
  1151. if names[ltype]:
  1152. self._giface.lmgr.AddMaps(list(reversed(names[ltype])), ltype, True)
  1153. if len(self._giface.GetLayerList()) == 1:
  1154. # zoom to map if there is only one map layer
  1155. self._giface.GetMapWindow().ZoomToMap()
  1156. Debug.msg(1, "Displayed layer(s): " + str(all_names))
  1157. def OnBeginDrag(self, node, event):
  1158. """Just copy necessary data"""
  1159. self.DefineItems(self.GetSelected())
  1160. if self.selected_location and None in self.selected_mapset and \
  1161. None in self.selected_layer:
  1162. GMessage(_("Move or copy location isn't allowed"))
  1163. event.Veto()
  1164. return
  1165. elif self.selected_location and self.selected_mapset and \
  1166. None in self.selected_layer:
  1167. GMessage(_("Move or copy mapset isn't allowed"))
  1168. event.Veto()
  1169. return
  1170. if self.selected_layer and not (self._restricted and gisenv()[
  1171. 'LOCATION_NAME'] != self.selected_location[0].data['name']):
  1172. event.Allow()
  1173. self.OnCopyMap(event)
  1174. Debug.msg(1, "DRAG")
  1175. else:
  1176. event.Veto()
  1177. def OnEndDrag(self, node, event):
  1178. """Copy layer into target"""
  1179. self.copy_mode = wx.GetMouseState().ControlDown()
  1180. if node:
  1181. self.DefineItems([node])
  1182. if None not in self.selected_mapset:
  1183. if self._restricted and gisenv()['MAPSET'] != self.selected_mapset[0].data['name']:
  1184. GMessage(_("To move or copy maps to other mapsets, unlock editing of other mapsets"),
  1185. parent=self)
  1186. event.Veto()
  1187. return
  1188. event.Allow()
  1189. Debug.msg(1, "DROP DONE")
  1190. self.OnPasteMap(event)
  1191. else:
  1192. GMessage(_("To move or copy maps to other location, "
  1193. "please drag them to a mapset in the "
  1194. "destination location"),
  1195. parent=self)
  1196. event.Veto()
  1197. return
  1198. def OnSwitchMapset(self, event):
  1199. """Switch to location and mapset"""
  1200. genv = gisenv()
  1201. grassdb = self.selected_grassdb[0].data['name']
  1202. location = self.selected_location[0].data['name']
  1203. mapset = self.selected_mapset[0].data['name']
  1204. if can_switch_mapset_interactive(self, grassdb, location, mapset):
  1205. # Switch to mapset in the same location
  1206. if (grassdb == genv['GISDBASE'] and location == genv['LOCATION_NAME']):
  1207. switch_mapset_interactively(self, self._giface, None, None, mapset)
  1208. # Switch to mapset in the same grassdb
  1209. elif grassdb == genv['GISDBASE']:
  1210. switch_mapset_interactively(self, self._giface, None, location, mapset)
  1211. # Switch to mapset in a different grassdb
  1212. else:
  1213. switch_mapset_interactively(self, self._giface, grassdb, location, mapset)
  1214. def _updateAfterMapsetChanged(self):
  1215. """Update tree after current mapset has changed"""
  1216. self.UpdateCurrentDbLocationMapsetNode()
  1217. self.ExpandCurrentMapset()
  1218. self.RefreshItems()
  1219. def OnMetadata(self, event):
  1220. """Show metadata of any raster/vector/3draster"""
  1221. def done(event):
  1222. gscript.try_remove(event.userData)
  1223. for i in range(len(self.selected_layer)):
  1224. if self.selected_layer[i].data['type'] == 'raster':
  1225. cmd = ['r.info']
  1226. elif self.selected_layer[i].data['type'] == 'vector':
  1227. cmd = ['v.info']
  1228. elif self.selected_layer[i].data['type'] == 'raster_3d':
  1229. cmd = ['r3.info']
  1230. cmd.append('map=%s@%s' % (self.selected_layer[i].data['name'], self.selected_mapset[i].data['name']))
  1231. gisrc, env = gscript.create_environment(
  1232. self.selected_grassdb[i].data['name'],
  1233. self.selected_location[i].data['name'],
  1234. self.selected_mapset[i].data['name'])
  1235. # print output to command log area
  1236. # temp gisrc file must be deleted onDone
  1237. self._giface.RunCmd(cmd, env=env, onDone=done, userData=gisrc)
  1238. def OnCopyName(self, event):
  1239. """Copy layer name to clipboard"""
  1240. if wx.TheClipboard.Open():
  1241. do = wx.TextDataObject()
  1242. text = []
  1243. for i in range(len(self.selected_layer)):
  1244. text.append('%s@%s' % (self.selected_layer[i].data['name'], self.selected_mapset[i].data['name']))
  1245. do.SetText(','.join(text))
  1246. wx.TheClipboard.SetData(do)
  1247. wx.TheClipboard.Close()
  1248. def Filter(self, text):
  1249. """Filter tree based on name and type."""
  1250. text = text.strip()
  1251. if len(text.split(':')) > 1:
  1252. name = text.split(':')[1].strip()
  1253. elem = text.split(':')[0].strip()
  1254. if 'r' == elem:
  1255. element = 'raster'
  1256. elif 'r3' == elem:
  1257. element = 'raster_3d'
  1258. elif 'v' == elem:
  1259. element = 'vector'
  1260. else:
  1261. element = None
  1262. else:
  1263. element = None
  1264. name = text.strip()
  1265. self._model = filterModel(self._orig_model, name=name, element=element)
  1266. self.UpdateCurrentDbLocationMapsetNode()
  1267. self.RefreshItems()
  1268. self.ExpandCurrentMapset()
  1269. def _getNewMapName(self, message, title, value, element, mapset, env):
  1270. """Dialog for simple text entry"""
  1271. dlg = NameEntryDialog(parent=self, message=message, caption=title,
  1272. element=element, env=env, mapset=mapset)
  1273. dlg.SetValue(value)
  1274. if dlg.ShowModal() == wx.ID_OK:
  1275. name = dlg.GetValue()
  1276. else:
  1277. name = None
  1278. dlg.Destroy()
  1279. return name
  1280. def _confirmDialog(self, question, title):
  1281. """Confirm dialog"""
  1282. dlg = wx.MessageDialog(self, question, title, wx.YES_NO)
  1283. res = dlg.ShowModal()
  1284. dlg.Destroy()
  1285. return res
  1286. def _isCurrent(self, genv):
  1287. if self._restricted:
  1288. currentMapset = currentLocation = currentGrassDb = True
  1289. for i in range(len(self.selected_grassdb)):
  1290. if self.selected_grassdb[i].data['name'] != genv['GISDBASE']:
  1291. currentGrassDb = False
  1292. currentLocation = False
  1293. currentMapset = False
  1294. break
  1295. if currentLocation and self.selected_location[0]:
  1296. for i in range(len(self.selected_location)):
  1297. if self.selected_location[i].data['name'] != genv['LOCATION_NAME']:
  1298. currentLocation = False
  1299. currentMapset = False
  1300. break
  1301. if currentMapset and self.selected_mapset[0]:
  1302. for i in range(len(self.selected_mapset)):
  1303. if self.selected_mapset[i].data['name'] != genv['MAPSET']:
  1304. currentMapset = False
  1305. break
  1306. return currentGrassDb, currentLocation, currentMapset
  1307. else:
  1308. return True, True, True
  1309. def _popupMenuLayer(self):
  1310. """Create popup menu for layers"""
  1311. menu = Menu()
  1312. genv = gisenv()
  1313. currentGrassDb, currentLocation, currentMapset = self._isCurrent(genv)
  1314. item = wx.MenuItem(menu, wx.ID_ANY, _("&Cut"))
  1315. menu.AppendItem(item)
  1316. self.Bind(wx.EVT_MENU, self.OnMoveMap, item)
  1317. if not currentMapset:
  1318. item.Enable(False)
  1319. item = wx.MenuItem(menu, wx.ID_ANY, _("&Copy"))
  1320. menu.AppendItem(item)
  1321. self.Bind(wx.EVT_MENU, self.OnCopyMap, item)
  1322. item = wx.MenuItem(menu, wx.ID_ANY, _("Copy &name"))
  1323. menu.AppendItem(item)
  1324. self.Bind(wx.EVT_MENU, self.OnCopyName, item)
  1325. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  1326. menu.AppendItem(item)
  1327. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  1328. if not(currentMapset and self.copy_layer):
  1329. item.Enable(False)
  1330. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete"))
  1331. menu.AppendItem(item)
  1332. self.Bind(wx.EVT_MENU, self.OnDeleteMap, item)
  1333. item.Enable(currentMapset)
  1334. item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename"))
  1335. menu.AppendItem(item)
  1336. self.Bind(wx.EVT_MENU, self.OnRenameMap, item)
  1337. item.Enable(currentMapset and len(self.selected_layer) == 1)
  1338. menu.AppendSeparator()
  1339. if not isinstance(self._giface, StandaloneGrassInterface):
  1340. if all([each.data['name'] == genv['LOCATION_NAME'] for each in self.selected_location]):
  1341. if len(self.selected_layer) > 1:
  1342. item = wx.MenuItem(menu, wx.ID_ANY, _("&Display layers"))
  1343. else:
  1344. item = wx.MenuItem(menu, wx.ID_ANY, _("&Display layer"))
  1345. menu.AppendItem(item)
  1346. self.Bind(wx.EVT_MENU, self.OnDisplayLayer, item)
  1347. item = wx.MenuItem(menu, wx.ID_ANY, _("Show &metadata"))
  1348. menu.AppendItem(item)
  1349. self.Bind(wx.EVT_MENU, self.OnMetadata, item)
  1350. self.PopupMenu(menu)
  1351. menu.Destroy()
  1352. def _popupMenuMapset(self):
  1353. """Create popup menu for mapsets"""
  1354. menu = Menu()
  1355. genv = gisenv()
  1356. currentGrassDb, currentLocation, currentMapset = self._isCurrent(genv)
  1357. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  1358. menu.AppendItem(item)
  1359. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  1360. if not(currentMapset and self.copy_layer):
  1361. item.Enable(False)
  1362. item = wx.MenuItem(menu, wx.ID_ANY, _("&Switch mapset"))
  1363. menu.AppendItem(item)
  1364. self.Bind(wx.EVT_MENU, self.OnSwitchMapset, item)
  1365. if (
  1366. self.selected_grassdb[0].data['name'] == genv['GISDBASE']
  1367. and self.selected_location[0].data['name'] == genv['LOCATION_NAME']
  1368. and self.selected_mapset[0].data['name'] == genv['MAPSET']
  1369. ):
  1370. item.Enable(False)
  1371. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete mapset"))
  1372. menu.AppendItem(item)
  1373. self.Bind(wx.EVT_MENU, self.OnDeleteMapset, item)
  1374. if self._restricted:
  1375. item.Enable(False)
  1376. item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename mapset"))
  1377. menu.AppendItem(item)
  1378. self.Bind(wx.EVT_MENU, self.OnRenameMapset, item)
  1379. if self._restricted:
  1380. item.Enable(False)
  1381. self.PopupMenu(menu)
  1382. menu.Destroy()
  1383. def _popupMenuLocation(self):
  1384. """Create popup menu for locations"""
  1385. menu = Menu()
  1386. item = wx.MenuItem(menu, wx.ID_ANY, _("&Create mapset"))
  1387. menu.AppendItem(item)
  1388. self.Bind(wx.EVT_MENU, self.OnCreateMapset, item)
  1389. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete location"))
  1390. menu.AppendItem(item)
  1391. self.Bind(wx.EVT_MENU, self.OnDeleteLocation, item)
  1392. if self._restricted:
  1393. item.Enable(False)
  1394. item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename location"))
  1395. menu.AppendItem(item)
  1396. self.Bind(wx.EVT_MENU, self.OnRenameLocation, item)
  1397. if self._restricted:
  1398. item.Enable(False)
  1399. self.PopupMenu(menu)
  1400. menu.Destroy()
  1401. def _popupMenuGrassDb(self):
  1402. """Create popup menu for grass db"""
  1403. menu = Menu()
  1404. genv = gisenv()
  1405. currentGrassDb, currentLocation, currentMapset = self._isCurrent(genv)
  1406. item = wx.MenuItem(menu, wx.ID_ANY, _("&Create new location"))
  1407. menu.AppendItem(item)
  1408. self.Bind(wx.EVT_MENU, self.OnCreateLocation, item)
  1409. item = wx.MenuItem(menu, wx.ID_ANY, _("&Download sample location"))
  1410. menu.AppendItem(item)
  1411. self.Bind(wx.EVT_MENU, self.OnDownloadLocation, item)
  1412. item = wx.MenuItem(menu, wx.ID_ANY, _("&Remove GRASS database from data catalog"))
  1413. menu.AppendItem(item)
  1414. self.Bind(wx.EVT_MENU, self.OnRemoveGrassDb, item)
  1415. if self.selected_grassdb[0].data['name'] == genv['GISDBASE']:
  1416. item.Enable(False)
  1417. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete GRASS database from disk"))
  1418. menu.AppendItem(item)
  1419. self.Bind(wx.EVT_MENU, self.OnDeleteGrassDb, item)
  1420. if self._restricted:
  1421. item.Enable(False)
  1422. self.PopupMenu(menu)
  1423. menu.Destroy()
  1424. def _popupMenuElement(self):
  1425. """Create popup menu for elements"""
  1426. menu = Menu()
  1427. item = wx.MenuItem(menu, wx.ID_ANY, _("&Paste"))
  1428. menu.AppendItem(item)
  1429. self.Bind(wx.EVT_MENU, self.OnPasteMap, item)
  1430. genv = gisenv()
  1431. currentGrassDb, currentLocation, currentMapset = self._isCurrent(genv)
  1432. if not(currentMapset and self.copy_layer):
  1433. item.Enable(False)
  1434. self.PopupMenu(menu)
  1435. menu.Destroy()
  1436. def _popupMenuMultipleLocations(self):
  1437. """Create popup menu for multiple selected locations"""
  1438. menu = Menu()
  1439. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete locations"))
  1440. menu.AppendItem(item)
  1441. self.Bind(wx.EVT_MENU, self.OnDeleteLocation, item)
  1442. if self._restricted:
  1443. item.Enable(False)
  1444. self.PopupMenu(menu)
  1445. menu.Destroy()
  1446. def _popupMenuMultipleMapsets(self):
  1447. """Create popup menu for multiple selected mapsets"""
  1448. menu = Menu()
  1449. item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete mapsets"))
  1450. menu.AppendItem(item)
  1451. self.Bind(wx.EVT_MENU, self.OnDeleteMapset, item)
  1452. if self._restricted:
  1453. item.Enable(False)
  1454. self.PopupMenu(menu)
  1455. menu.Destroy()
  1456. def _popupMenuEmpty(self):
  1457. """Create empty popup when multiple different types of items are selected"""
  1458. menu = Menu()
  1459. item = wx.MenuItem(menu, wx.ID_ANY, _("No available options"))
  1460. menu.AppendItem(item)
  1461. item.Enable(False)
  1462. self.PopupMenu(menu)
  1463. menu.Destroy()