tree.py 90 KB

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