guiutils.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. """
  2. @package startup.guiutils
  3. @brief General GUI-dependent utilities for GUI startup of GRASS GIS
  4. (C) 2018 by Vaclav Petras the GRASS Development Team
  5. This program is free software under the GNU General Public License
  6. (>=v2). Read the file COPYING that comes with GRASS for details.
  7. @author Vaclav Petras <wenzeslaus gmail com>
  8. @author Linda Kladivova <l.kladivova@seznam.cz>
  9. This is for code which depend on something from GUI (wx or wxGUI).
  10. """
  11. import os
  12. import sys
  13. import wx
  14. import grass.script as gs
  15. from core import globalvar
  16. from core.gcmd import GError, GMessage, DecodeString, RunCommand
  17. from gui_core.dialogs import TextEntryDialog
  18. from location_wizard.dialogs import RegionDef
  19. from gui_core.widgets import GenericMultiValidator
  20. from startup.utils import (create_mapset, delete_mapset, delete_location,
  21. rename_mapset, rename_location, mapset_exists,
  22. location_exists, get_default_mapset_name)
  23. def SetSessionMapset(database, location, mapset):
  24. """Sets database, location and mapset for the current session"""
  25. RunCommand("g.gisenv", set="GISDBASE=%s" % database)
  26. RunCommand("g.gisenv", set="LOCATION_NAME=%s" % location)
  27. RunCommand("g.gisenv", set="MAPSET=%s" % mapset)
  28. class MapsetDialog(TextEntryDialog):
  29. def __init__(self, parent=None, default=None, message=None, caption=None,
  30. database=None, location=None):
  31. self.database = database
  32. self.location = location
  33. # list of tuples consisting of conditions and callbacks
  34. checks = [(gs.legal_name, self._nameValidationFailed),
  35. (self._checkMapsetNotExists, self._mapsetAlreadyExists),
  36. (self._checkOGR, self._reservedMapsetName)]
  37. validator = GenericMultiValidator(checks)
  38. TextEntryDialog.__init__(
  39. self, parent=parent,
  40. message=message,
  41. caption=caption,
  42. defaultValue=default,
  43. validator=validator,
  44. )
  45. def _nameValidationFailed(self, ctrl):
  46. message = _(
  47. "Name '{}' is not a valid name for location or mapset. "
  48. "Please use only ASCII characters excluding characters {} "
  49. "and space.").format(ctrl.GetValue(), '/"\'@,=*~')
  50. GError(parent=self, message=message, caption=_("Invalid name"))
  51. def _checkOGR(self, text):
  52. """Check user's input for reserved mapset name."""
  53. if text.lower() == 'ogr':
  54. return False
  55. return True
  56. def _reservedMapsetName(self, ctrl):
  57. message = _(
  58. "Name '{}' is reserved for direct "
  59. "read access to OGR layers. Please use "
  60. "another name for your mapset.").format(ctrl.GetValue())
  61. GError(parent=self, message=message,
  62. caption=_("Reserved mapset name"))
  63. def _checkMapsetNotExists(self, text):
  64. """Check whether user's input mapset exists or not."""
  65. if mapset_exists(self.database, self.location, text):
  66. return False
  67. return True
  68. def _mapsetAlreadyExists(self, ctrl):
  69. message = _(
  70. "Mapset '{}' already exists. Please consider using "
  71. "another name for your mapset.").format(ctrl.GetValue())
  72. GError(parent=self, message=message,
  73. caption=_("Existing mapset path"))
  74. class LocationDialog(TextEntryDialog):
  75. def __init__(self, parent=None, default=None, message=None, caption=None,
  76. database=None):
  77. self.database = database
  78. # list of tuples consisting of conditions and callbacks
  79. checks = [(gs.legal_name, self._nameValidationFailed),
  80. (self._checkLocationNotExists, self._locationAlreadyExists)]
  81. validator = GenericMultiValidator(checks)
  82. TextEntryDialog.__init__(
  83. self, parent=parent,
  84. message=message,
  85. caption=caption,
  86. defaultValue=default,
  87. validator=validator,
  88. )
  89. def _nameValidationFailed(self, ctrl):
  90. message = _(
  91. "Name '{}' is not a valid name for location or mapset. "
  92. "Please use only ASCII characters excluding characters {} "
  93. "and space.").format(ctrl.GetValue(), '/"\'@,=*~')
  94. GError(parent=self, message=message, caption=_("Invalid name"))
  95. def _checkLocationNotExists(self, text):
  96. """Check whether user's input location exists or not."""
  97. if location_exists(self.database, text):
  98. return False
  99. return True
  100. def _locationAlreadyExists(self, ctrl):
  101. message = _(
  102. "Location '{}' already exists. Please consider using "
  103. "another name for your location.").format(ctrl.GetValue())
  104. GError(parent=self, message=message,
  105. caption=_("Existing location path"))
  106. # TODO: similar to (but not the same as) read_gisrc function in grass.py
  107. def read_gisrc():
  108. """Read variables from a current GISRC file
  109. Returns a dictionary representation of the file content.
  110. """
  111. grassrc = {}
  112. gisrc = os.getenv("GISRC")
  113. if gisrc and os.path.isfile(gisrc):
  114. try:
  115. rc = open(gisrc, "r")
  116. for line in rc.readlines():
  117. try:
  118. key, val = line.split(":", 1)
  119. except ValueError as e:
  120. sys.stderr.write(
  121. _('Invalid line in GISRC file (%s):%s\n' % (e, line)))
  122. grassrc[key.strip()] = DecodeString(val.strip())
  123. finally:
  124. rc.close()
  125. return grassrc
  126. def GetVersion():
  127. """Gets version and revision
  128. Returns tuple `(version, revision)`. For standard releases revision
  129. is an empty string.
  130. Revision string is currently wrapped in parentheses with added
  131. leading space. This is an implementation detail and legacy and may
  132. change anytime.
  133. """
  134. versionFile = open(os.path.join(globalvar.ETCDIR, "VERSIONNUMBER"))
  135. versionLine = versionFile.readline().rstrip('\n')
  136. versionFile.close()
  137. try:
  138. grassVersion, grassRevision = versionLine.split(' ', 1)
  139. if grassVersion.endswith('dev'):
  140. grassRevisionStr = ' (%s)' % grassRevision
  141. else:
  142. grassRevisionStr = ''
  143. except ValueError:
  144. grassVersion = versionLine
  145. grassRevisionStr = ''
  146. return (grassVersion, grassRevisionStr)
  147. def create_mapset_interactively(guiparent, grassdb, location):
  148. """
  149. Create new mapset
  150. """
  151. dlg = MapsetDialog(
  152. parent=guiparent,
  153. default=get_default_mapset_name(),
  154. message=_("Name for the new mapset:"),
  155. caption=_("Create new mapset"),
  156. database=grassdb,
  157. location=location,
  158. )
  159. if dlg.ShowModal() == wx.ID_OK:
  160. mapset = dlg.GetValue()
  161. try:
  162. create_mapset(grassdb, location, mapset)
  163. except OSError as err:
  164. GError(
  165. parent=guiparent,
  166. message=_("Unable to create new mapset: %s") % err,
  167. showTraceback=False,
  168. )
  169. else:
  170. mapset = None
  171. dlg.Destroy()
  172. return mapset
  173. def create_location_interactively(guiparent, grassdb):
  174. """
  175. Create new location using Location Wizard.
  176. Returns tuple (database, location, mapset) where mapset is "PERMANENT"
  177. by default or another mapset a user created and may want to switch to.
  178. """
  179. from location_wizard.wizard import LocationWizard
  180. gWizard = LocationWizard(parent=guiparent,
  181. grassdatabase=grassdb)
  182. if gWizard.location is None:
  183. gWizard_output = (None, None, None)
  184. # Returns Nones after Cancel
  185. return gWizard_output
  186. if gWizard.georeffile:
  187. message = _(
  188. "Do you want to import {}"
  189. "to the newly created location?"
  190. ).format(gWizard.georeffile)
  191. dlg = wx.MessageDialog(parent=guiparent,
  192. message=message,
  193. caption=_("Import data?"),
  194. style=wx.YES_NO | wx.YES_DEFAULT |
  195. wx.ICON_QUESTION)
  196. dlg.CenterOnParent()
  197. if dlg.ShowModal() == wx.ID_YES:
  198. import_file(guiparent, gWizard.georeffile)
  199. dlg.Destroy()
  200. if gWizard.default_region:
  201. defineRegion = RegionDef(guiparent, location=gWizard.location)
  202. defineRegion.CenterOnParent()
  203. defineRegion.ShowModal()
  204. defineRegion.Destroy()
  205. if gWizard.user_mapset:
  206. mapset = create_mapset_interactively(guiparent,
  207. gWizard.grassdatabase,
  208. gWizard.location)
  209. # Returns database and location created by user
  210. # and a mapset user may want to switch to
  211. gWizard_output = (gWizard.grassdatabase, gWizard.location,
  212. mapset)
  213. else:
  214. # Returns PERMANENT mapset when user mapset not defined
  215. gWizard_output = (gWizard.grassdatabase, gWizard.location,
  216. "PERMANENT")
  217. return gWizard_output
  218. def rename_mapset_interactively(guiparent, grassdb, location, mapset):
  219. """
  220. Rename selected mapset
  221. """
  222. newmapset = None
  223. if mapset == "PERMANENT":
  224. GMessage(
  225. parent=guiparent,
  226. message=_(
  227. "Mapset <PERMANENT> is required for valid GRASS location.\n\n"
  228. "This mapset cannot be renamed."
  229. ),
  230. )
  231. return newmapset
  232. dlg = MapsetDialog(
  233. parent=guiparent,
  234. default=mapset,
  235. message=_("Current name: %s\n\nEnter new name:") % mapset,
  236. caption=_("Rename selected mapset"),
  237. database=grassdb,
  238. location=location,
  239. )
  240. if dlg.ShowModal() == wx.ID_OK:
  241. newmapset = dlg.GetValue()
  242. try:
  243. rename_mapset(grassdb, location, mapset, newmapset)
  244. except OSError as err:
  245. newmapset = None
  246. wx.MessageBox(
  247. parent=guiparent,
  248. caption=_("Error"),
  249. message=_("Unable to rename mapset.\n\n%s") % err,
  250. style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
  251. )
  252. dlg.Destroy()
  253. return newmapset
  254. def rename_location_interactively(guiparent, grassdb, location):
  255. """
  256. Rename selected location
  257. """
  258. dlg = LocationDialog(
  259. parent=guiparent,
  260. default=location,
  261. message=_("Current name: %s\n\nEnter new name:") % location,
  262. caption=_("Rename selected location"),
  263. database=grassdb,
  264. )
  265. if dlg.ShowModal() == wx.ID_OK:
  266. newlocation = dlg.GetValue()
  267. try:
  268. rename_location(grassdb, location, newlocation)
  269. except OSError as err:
  270. newlocation = None
  271. wx.MessageBox(
  272. parent=guiparent,
  273. caption=_("Error"),
  274. message=_("Unable to rename location.\n\n%s") % err,
  275. style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
  276. )
  277. else:
  278. newlocation = None
  279. dlg.Destroy()
  280. return newlocation
  281. def download_location_interactively(guiparent, grassdb):
  282. """
  283. Download new location using Location Wizard.
  284. Returns tuple (database, location, mapset) where mapset is "PERMANENT"
  285. by default or in future it could be the mapset the user may want to
  286. switch to.
  287. """
  288. from startup.locdownload import LocationDownloadDialog
  289. result = (None, None, None)
  290. loc_download = LocationDownloadDialog(parent=guiparent,
  291. database=grassdb)
  292. loc_download.ShowModal()
  293. if loc_download.GetLocation() is not None:
  294. # Returns database and location created by user
  295. # and a mapset user may want to switch to
  296. result = (grassdb, loc_download.GetLocation(), "PERMANENT")
  297. loc_download.Destroy()
  298. return result
  299. def delete_mapset_interactively(guiparent, grassdb, location, mapset):
  300. """
  301. Delete selected mapset
  302. """
  303. if mapset == "PERMANENT":
  304. GMessage(
  305. parent=guiparent,
  306. message=_(
  307. "Mapset <PERMANENT> is required for valid GRASS location.\n\n"
  308. "This mapset cannot be deleted."
  309. ),
  310. )
  311. return False
  312. dlg = wx.MessageDialog(
  313. parent=guiparent,
  314. message=_(
  315. "Do you want to continue with deleting mapset <%(mapset)s> "
  316. "from location <%(location)s>?\n\n"
  317. "ALL MAPS included in this mapset will be "
  318. "PERMANENTLY DELETED!"
  319. )
  320. % {"mapset": mapset, "location": location},
  321. caption=_("Delete selected mapset"),
  322. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
  323. )
  324. if dlg.ShowModal() == wx.ID_YES:
  325. try:
  326. delete_mapset(grassdb, location, mapset)
  327. dlg.Destroy()
  328. return True
  329. except OSError as err:
  330. wx.MessageBox(
  331. parent=guiparent,
  332. caption=_("Error"),
  333. message=_("Unable to delete mapset.\n\n%s") % err,
  334. style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
  335. )
  336. dlg.Destroy()
  337. return False
  338. def delete_location_interactively(guiparent, grassdb, location):
  339. """
  340. Delete selected location
  341. """
  342. dlg = wx.MessageDialog(
  343. parent=guiparent,
  344. message=_(
  345. "Do you want to continue with deleting "
  346. "location <%s>?\n\n"
  347. "ALL MAPS included in this location will be "
  348. "PERMANENTLY DELETED!"
  349. )
  350. % (location),
  351. caption=_("Delete selected location"),
  352. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
  353. )
  354. if dlg.ShowModal() == wx.ID_YES:
  355. try:
  356. delete_location(grassdb, location)
  357. dlg.Destroy()
  358. return True
  359. except OSError as err:
  360. wx.MessageBox(
  361. parent=guiparent,
  362. caption=_("Error"),
  363. message=_("Unable to delete location.\n\n%s") % err,
  364. style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
  365. )
  366. dlg.Destroy()
  367. return False
  368. def import_file(guiparent, filePath):
  369. """Tries to import file as vector or raster.
  370. If successfull sets default region from imported map.
  371. """
  372. RunCommand('db.connect', flags='c')
  373. mapName = os.path.splitext(os.path.basename(filePath))[0]
  374. vectors = RunCommand('v.in.ogr', input=filePath, flags='l',
  375. read=True)
  376. wx.BeginBusyCursor()
  377. wx.GetApp().Yield()
  378. if vectors:
  379. # vector detected
  380. returncode, error = RunCommand(
  381. 'v.in.ogr', input=filePath, output=mapName, flags='e',
  382. getErrorMsg=True)
  383. else:
  384. returncode, error = RunCommand(
  385. 'r.in.gdal', input=filePath, output=mapName, flags='e',
  386. getErrorMsg=True)
  387. wx.EndBusyCursor()
  388. if returncode != 0:
  389. GError(
  390. parent=guiparent,
  391. message=_(
  392. "Import of <%(name)s> failed.\n"
  393. "Reason: %(msg)s") % ({
  394. 'name': filePath,
  395. 'msg': error}))
  396. else:
  397. GMessage(
  398. message=_(
  399. "Data file <%(name)s> imported successfully. "
  400. "The location's default region was set from "
  401. "this imported map.") % {
  402. 'name': filePath},
  403. parent=guiparent)