tree.py 80 KB

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