123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643 |
- """
- @package startup.guiutils
- @brief General GUI-dependent utilities for GUI startup of GRASS GIS
- (C) 2018 by Vaclav Petras the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Vaclav Petras <wenzeslaus gmail com>
- @author Linda Kladivova <l.kladivova@seznam.cz>
- This is for code which depend on something from GUI (wx or wxGUI).
- """
- import os
- import sys
- import wx
- import grass.script as gs
- from grass.script import gisenv
- from grass.grassdb.checks import mapset_exists, location_exists
- from grass.grassdb.create import create_mapset, get_default_mapset_name
- from grass.grassdb.manage import (
- delete_mapset,
- delete_location,
- rename_mapset,
- rename_location,
- )
- from core import globalvar
- from core.gcmd import GError, GMessage, DecodeString, RunCommand
- from gui_core.dialogs import TextEntryDialog
- from location_wizard.dialogs import RegionDef
- from gui_core.widgets import GenericMultiValidator
- def SetSessionMapset(database, location, mapset):
- """Sets database, location and mapset for the current session"""
- RunCommand("g.gisenv", set="GISDBASE=%s" % database)
- RunCommand("g.gisenv", set="LOCATION_NAME=%s" % location)
- RunCommand("g.gisenv", set="MAPSET=%s" % mapset)
- class MapsetDialog(TextEntryDialog):
- def __init__(self, parent=None, default=None, message=None, caption=None,
- database=None, location=None):
- self.database = database
- self.location = location
- # list of tuples consisting of conditions and callbacks
- checks = [(gs.legal_name, self._nameValidationFailed),
- (self._checkMapsetNotExists, self._mapsetAlreadyExists),
- (self._checkOGR, self._reservedMapsetName)]
- validator = GenericMultiValidator(checks)
- TextEntryDialog.__init__(
- self, parent=parent,
- message=message,
- caption=caption,
- defaultValue=default,
- validator=validator,
- )
- def _nameValidationFailed(self, ctrl):
- message = _(
- "Name '{}' is not a valid name for location or mapset. "
- "Please use only ASCII characters excluding characters {} "
- "and space.").format(ctrl.GetValue(), '/"\'@,=*~')
- GError(parent=self, message=message, caption=_("Invalid name"))
- def _checkOGR(self, text):
- """Check user's input for reserved mapset name."""
- if text.lower() == 'ogr':
- return False
- return True
- def _reservedMapsetName(self, ctrl):
- message = _(
- "Name '{}' is reserved for direct "
- "read access to OGR layers. Please use "
- "another name for your mapset.").format(ctrl.GetValue())
- GError(parent=self, message=message,
- caption=_("Reserved mapset name"))
- def _checkMapsetNotExists(self, text):
- """Check whether user's input mapset exists or not."""
- if mapset_exists(self.database, self.location, text):
- return False
- return True
- def _mapsetAlreadyExists(self, ctrl):
- message = _(
- "Mapset '{}' already exists. Please consider using "
- "another name for your mapset.").format(ctrl.GetValue())
- GError(parent=self, message=message,
- caption=_("Existing mapset path"))
- class LocationDialog(TextEntryDialog):
- def __init__(self, parent=None, default=None, message=None, caption=None,
- database=None):
- self.database = database
- # list of tuples consisting of conditions and callbacks
- checks = [(gs.legal_name, self._nameValidationFailed),
- (self._checkLocationNotExists, self._locationAlreadyExists)]
- validator = GenericMultiValidator(checks)
- TextEntryDialog.__init__(
- self, parent=parent,
- message=message,
- caption=caption,
- defaultValue=default,
- validator=validator,
- )
- def _nameValidationFailed(self, ctrl):
- message = _(
- "Name '{}' is not a valid name for location or mapset. "
- "Please use only ASCII characters excluding characters {} "
- "and space.").format(ctrl.GetValue(), '/"\'@,=*~')
- GError(parent=self, message=message, caption=_("Invalid name"))
- def _checkLocationNotExists(self, text):
- """Check whether user's input location exists or not."""
- if location_exists(self.database, text):
- return False
- return True
- def _locationAlreadyExists(self, ctrl):
- message = _(
- "Location '{}' already exists. Please consider using "
- "another name for your location.").format(ctrl.GetValue())
- GError(parent=self, message=message,
- caption=_("Existing location path"))
- # TODO: similar to (but not the same as) read_gisrc function in grass.py
- def read_gisrc():
- """Read variables from a current GISRC file
- Returns a dictionary representation of the file content.
- """
- grassrc = {}
- gisrc = os.getenv("GISRC")
- if gisrc and os.path.isfile(gisrc):
- try:
- rc = open(gisrc, "r")
- for line in rc.readlines():
- try:
- key, val = line.split(":", 1)
- except ValueError as e:
- sys.stderr.write(
- _('Invalid line in GISRC file (%s):%s\n' % (e, line)))
- grassrc[key.strip()] = DecodeString(val.strip())
- finally:
- rc.close()
- return grassrc
- def GetVersion():
- """Gets version and revision
- Returns tuple `(version, revision)`. For standard releases revision
- is an empty string.
- Revision string is currently wrapped in parentheses with added
- leading space. This is an implementation detail and legacy and may
- change anytime.
- """
- versionFile = open(os.path.join(globalvar.ETCDIR, "VERSIONNUMBER"))
- versionLine = versionFile.readline().rstrip('\n')
- versionFile.close()
- try:
- grassVersion, grassRevision = versionLine.split(' ', 1)
- if grassVersion.endswith('dev'):
- grassRevisionStr = ' (%s)' % grassRevision
- else:
- grassRevisionStr = ''
- except ValueError:
- grassVersion = versionLine
- grassRevisionStr = ''
- return (grassVersion, grassRevisionStr)
- def create_mapset_interactively(guiparent, grassdb, location):
- """
- Create new mapset
- """
- dlg = MapsetDialog(
- parent=guiparent,
- default=get_default_mapset_name(),
- message=_("Name for the new mapset:"),
- caption=_("Create new mapset"),
- database=grassdb,
- location=location,
- )
- mapset = None
- if dlg.ShowModal() == wx.ID_OK:
- mapset = dlg.GetValue()
- try:
- create_mapset(grassdb, location, mapset)
- except OSError as err:
- mapset = None
- GError(
- parent=guiparent,
- message=_("Unable to create new mapset: {}").format(err),
- showTraceback=False,
- )
- dlg.Destroy()
- return mapset
- def create_location_interactively(guiparent, grassdb):
- """
- Create new location using Location Wizard.
- Returns tuple (database, location, mapset) where mapset is "PERMANENT"
- by default or another mapset a user created and may want to switch to.
- """
- from location_wizard.wizard import LocationWizard
- gWizard = LocationWizard(parent=guiparent,
- grassdatabase=grassdb)
- if gWizard.location is None:
- gWizard_output = (None, None, None)
- # Returns Nones after Cancel
- return gWizard_output
- if gWizard.georeffile:
- message = _(
- "Do you want to import {}"
- "to the newly created location?"
- ).format(gWizard.georeffile)
- dlg = wx.MessageDialog(parent=guiparent,
- message=message,
- caption=_("Import data?"),
- style=wx.YES_NO | wx.YES_DEFAULT |
- wx.ICON_QUESTION)
- dlg.CenterOnParent()
- if dlg.ShowModal() == wx.ID_YES:
- import_file(guiparent, gWizard.georeffile)
- dlg.Destroy()
- if gWizard.default_region:
- defineRegion = RegionDef(guiparent, location=gWizard.location)
- defineRegion.CenterOnParent()
- defineRegion.ShowModal()
- defineRegion.Destroy()
- if gWizard.user_mapset:
- mapset = create_mapset_interactively(guiparent,
- gWizard.grassdatabase,
- gWizard.location)
- # Returns database and location created by user
- # and a mapset user may want to switch to
- gWizard_output = (gWizard.grassdatabase, gWizard.location,
- mapset)
- else:
- # Returns PERMANENT mapset when user mapset not defined
- gWizard_output = (gWizard.grassdatabase, gWizard.location,
- "PERMANENT")
- return gWizard_output
- def rename_mapset_interactively(guiparent, grassdb, location, mapset):
- """Rename mapset with user interaction.
- If PERMANENT or current mapset found, rename operation is not performed.
- Exceptions during renaming are handled in this function.
- Returns newmapset if there was a change or None if the mapset cannot be
- renamed (see above the possible reasons) or if another error was encountered.
- """
- genv = gisenv()
- # Check selected mapset and remember issue.
- # Each error is reported only once (using elif).
- mapset_path = os.path.join(grassdb, location, mapset)
- newmapset = None
- issue = None
- # Check for permanent mapsets
- if mapset == "PERMANENT":
- issue = _("<{}> is required for a valid location.").format(mapset_path)
- # Check for current mapset
- elif (
- grassdb == genv['GISDBASE'] and
- location == genv['LOCATION_NAME'] and
- mapset == genv['MAPSET']
- ):
- issue = _("<{}> is the current mapset.").format(mapset_path)
- # If an issue, display the warning message and do not rename mapset
- if issue:
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Cannot rename selected mapset for the following reason:\n\n"
- "{}\n\n"
- "No mapset will be renamed."
- ).format(issue),
- caption=_("Unable to rename selected mapset"),
- style=wx.OK | wx.ICON_WARNING
- )
- dlg.ShowModal()
- else:
- dlg = MapsetDialog(
- parent=guiparent,
- default=mapset,
- message=_("Current name: {}\n\nEnter new name:").format(mapset),
- caption=_("Rename selected mapset"),
- database=grassdb,
- location=location,
- )
- if dlg.ShowModal() == wx.ID_OK:
- newmapset = dlg.GetValue()
- try:
- rename_mapset(grassdb, location, mapset, newmapset)
- except OSError as err:
- newmapset = None
- wx.MessageBox(
- parent=guiparent,
- caption=_("Error"),
- message=_("Unable to rename mapset.\n\n{}").format(err),
- style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
- )
- dlg.Destroy()
- return newmapset
- def rename_location_interactively(guiparent, grassdb, location):
- """Rename location with user interaction.
- If current location found, rename operation is not performed.
- Exceptions during renaming are handled in this function.
- Returns newlocation if there was a change or None if the location cannot be
- renamed (see above the possible reasons) or if another error was encountered.
- """
- genv = gisenv()
- # Check selected location and remember issue.
- # Each error is reported only once (using elif).
- location_path = os.path.join(grassdb, location)
- newlocation = None
- issue = None
- # Check for current location
- if (
- grassdb == genv['GISDBASE'] and
- location == genv['LOCATION_NAME']
- ):
- issue = _("<{}> is the current location.").format(location_path)
- # If an issue, display the warning message and do not rename location
- if issue:
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Cannot rename selected location for the following reason:\n\n"
- "{}\n\n"
- "No location will be renamed."
- ).format(issue),
- caption=_("Unable to rename selected location"),
- style=wx.OK | wx.ICON_WARNING
- )
- dlg.ShowModal()
- else:
- dlg = LocationDialog(
- parent=guiparent,
- default=location,
- message=_("Current name: {}\n\nEnter new name:").format(location),
- caption=_("Rename selected location"),
- database=grassdb,
- )
- if dlg.ShowModal() == wx.ID_OK:
- newlocation = dlg.GetValue()
- try:
- rename_location(grassdb, location, newlocation)
- except OSError as err:
- newlocation = None
- wx.MessageBox(
- parent=guiparent,
- caption=_("Error"),
- message=_("Unable to rename location.\n\n{}").format(err),
- style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
- )
- dlg.Destroy()
- return newlocation
- def download_location_interactively(guiparent, grassdb):
- """
- Download new location using Location Wizard.
- Returns tuple (database, location, mapset) where mapset is "PERMANENT"
- by default or in future it could be the mapset the user may want to
- switch to.
- """
- from startup.locdownload import LocationDownloadDialog
- result = (None, None, None)
- loc_download = LocationDownloadDialog(parent=guiparent,
- database=grassdb)
- loc_download.Centre()
- loc_download.ShowModal()
- if loc_download.GetLocation() is not None:
- # Returns database and location created by user
- # and a mapset user may want to switch to
- result = (grassdb, loc_download.GetLocation(), "PERMANENT")
- loc_download.Destroy()
- return result
- def delete_mapset_interactively(guiparent, grassdb, location, mapset):
- """Delete one mapset with user interaction.
- This is currently just a convenience wrapper for delete_mapsets_interactively().
- """
- mapsets = [(grassdb, location, mapset)]
- return delete_mapsets_interactively(guiparent, mapsets)
- def delete_mapsets_interactively(guiparent, mapsets):
- """Delete multiple mapsets with user interaction.
- Parameter *mapsets* is a list of tuples (database, location, mapset).
- If PERMANENT or current mapset found, delete operation is not performed.
- Exceptions during deletation are handled in this function.
- Returns True if there was a change, i.e., all mapsets were successfuly deleted
- or at least one mapset was deleted. Returns False if one or more mapsets cannot be
- deleted (see above the possible reasons) or if an error was encountered when
- deleting the first mapset in the list.
- """
- genv = gisenv()
- issues = []
- deletes = []
- # Check selected mapsets and remember issue.
- # Each error is reported only once (using elif).
- for grassdb, location, mapset in mapsets:
- mapset_path = os.path.join(grassdb, location, mapset)
- # Check for permanent mapsets
- if mapset == "PERMANENT":
- issue = _("<{}> is required for a valid location.").format(mapset_path)
- issues.append(issue)
- # Check for current mapset
- elif (
- grassdb == genv['GISDBASE'] and
- location == genv['LOCATION_NAME'] and
- mapset == genv['MAPSET']
- ):
- issue = _("<{}> is the current mapset.").format(mapset_path)
- issues.append(issue)
- # No issue detected
- else:
- deletes.append(mapset_path)
- modified = False # True after first successful delete
- # If any issues, display the warning message and do not delete anything
- if issues:
- issues = "\n".join(issues)
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Cannot delete one or more mapsets for the following reasons:\n\n"
- "{}\n\n"
- "No mapsets will be deleted."
- ).format(issues),
- caption=_("Unable to delete selected mapsets"),
- style=wx.OK | wx.ICON_WARNING
- )
- dlg.ShowModal()
- else:
- deletes = "\n".join(deletes)
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Do you want to continue with deleting"
- " one or more of the following mapsets?\n\n"
- "{}\n\n"
- "All maps included in these mapsets will be permanently deleted!"
- ).format(deletes),
- caption=_("Delete selected mapsets"),
- style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
- )
- if dlg.ShowModal() == wx.ID_YES:
- try:
- for grassdb, location, mapset in mapsets:
- delete_mapset(grassdb, location, mapset)
- modified = True
- dlg.Destroy()
- return modified
- except OSError as error:
- wx.MessageBox(
- parent=guiparent,
- caption=_("Error when deleting mapsets"),
- message=_(
- "The following error occured when deleting mapset <{path}>:"
- "\n\n{error}\n\n"
- "Deleting of mapsets was interrupted."
- ).format(
- path=os.path.join(grassdb, location, mapset),
- error=error,
- ),
- style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
- )
- dlg.Destroy()
- return modified
- def delete_location_interactively(guiparent, grassdb, location):
- """Delete a location with user interaction.
- If current location found, delete operation is not performed.
- Exceptions during deletation are handled in this function.
- Returns True if there was a change, returns False if a location cannot be
- deleted (see above the possible reasons) or if another error was encountered.
- """
- genv = gisenv()
- issue = None
- # Check selected location and remember issue.
- # Each error is reported only once (using elif).
- location_path = os.path.join(grassdb, location)
- # Check for current location
- if (
- grassdb == genv['GISDBASE'] and
- location == genv['LOCATION_NAME']
- ):
- issue = _("<{}> is the current location.").format(location_path)
- modified = False # True after first successful delete
- # If any issues, display the warning message and do not delete anything
- if issue:
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Cannot delete selected location for the following reasons:\n\n"
- "{}\n\n"
- "No location will be deleted."
- ).format(issue),
- caption=_("Unable to delete selected location"),
- style=wx.OK | wx.ICON_WARNING
- )
- dlg.ShowModal()
- else:
- dlg = wx.MessageDialog(
- parent=guiparent,
- message=_(
- "Do you want to continue with deleting"
- "the following location?\n\n"
- "{}\n\n"
- "All mapsets included in this location will be permanently deleted!"
- ).format(location_path),
- caption=_("Delete selected location"),
- style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
- )
- if dlg.ShowModal() == wx.ID_YES:
- try:
- delete_location(grassdb, location)
- modified = True
- dlg.Destroy()
- return modified
- except OSError as error:
- wx.MessageBox(
- parent=guiparent,
- caption=_("Error when deleting location"),
- message=_(
- "The following error occured when deleting location <{path}>:"
- "\n\n{error}\n\n"
- "Deleting was interrupted."
- ).format(
- path=os.path.join(grassdb, location),
- error=error,
- ),
- style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
- )
- dlg.Destroy()
- return modified
- def import_file(guiparent, filePath):
- """Tries to import file as vector or raster.
- If successfull sets default region from imported map.
- """
- RunCommand('db.connect', flags='c')
- mapName = os.path.splitext(os.path.basename(filePath))[0]
- vectors = RunCommand('v.in.ogr', input=filePath, flags='l',
- read=True)
- wx.BeginBusyCursor()
- wx.GetApp().Yield()
- if vectors:
- # vector detected
- returncode, error = RunCommand(
- 'v.in.ogr', input=filePath, output=mapName, flags='e',
- getErrorMsg=True)
- else:
- returncode, error = RunCommand(
- 'r.in.gdal', input=filePath, output=mapName, flags='e',
- getErrorMsg=True)
- wx.EndBusyCursor()
- if returncode != 0:
- GError(
- parent=guiparent,
- message=_(
- "Import of <%(name)s> failed.\n"
- "Reason: %(msg)s") % ({
- 'name': filePath,
- 'msg': error}))
- else:
- GMessage(
- message=_(
- "Data file <%(name)s> imported successfully. "
- "The location's default region was set from "
- "this imported map.") % {
- 'name': filePath},
- parent=guiparent)
|