Browse Source

wxGUI: Replace startup screen by fallback session (#1400)

* Startup screen code removed. Used only when 
* Either demo/default world location is used (first time user) or an XY location temporary location is used (fallback).
* Last mapset path is now noted in gisrc of the current session. Used in GUI to offer switch from fallback to locked.
* non_standard_startup and first_time_user in grassdb/checks.py.
* grassdb/config for special strings such as `<UNKNOWN>`.
* Fallback tmp location only for GUI. Default currently for both GUI and CLI (`--text`).
* Call set_mapset in every branch (all paths lead to the same series of checks and setup).
* This increases the need for refactoring of `main()` and needs removal of `--gtext`.
Linda Kladivova 4 years ago
parent
commit
8fe13636b7

+ 1 - 1
gui/wxpython/Makefile

@@ -14,7 +14,7 @@ SRCFILES := $(wildcard icons/*.py scripts/*.py xml/*) \
 	mapswipe/*.py modules/*.py nviz/*.py psmap/*.py rdigit/*.py \
 	rlisetup/*.py startup/*.py timeline/*.py vdigit/*.py \
 	vnet/*.py web_services/*.py wxplot/*.py iscatt/*.py tplot/*.py photo2image/*.py image2target/*.py) \
-	gis_set.py gis_set_error.py wxgui.py README
+	wxgui.py README
 
 DSTFILES := $(patsubst %,$(DSTDIR)/%,$(SRCFILES)) \
 	$(patsubst %.py,$(DSTDIR)/%.pyc,$(filter %.py,$(SRCFILES)))

+ 39 - 4
gui/wxpython/datacatalog/catalog.py

@@ -26,10 +26,14 @@ from gui_core.infobar import InfoBar
 from datacatalog.infomanager import DataCatalogInfoManager
 from gui_core.wrap import Menu
 from gui_core.forms import GUI
+from grass.script import gisenv
 
 from grass.pydispatch.signal import Signal
 
-from grass.grassdb.checks import is_current_mapset_in_demolocation
+from grass.grassdb.manage import split_mapset_path
+from grass.grassdb.checks import (get_reason_id_mapset_not_usable,
+                          is_fallback_session,
+                          is_first_time_user)
 
 
 class DataCatalog(wx.Panel):
@@ -55,7 +59,9 @@ class DataCatalog(wx.Panel):
         self.tree.showNotification.connect(self.showNotification)
 
         # infobar for data catalog
+        delay = 2000
         self.infoBar = InfoBar(self)
+        self.giface.currentMapsetChanged.connect(self.dismissInfobar)
 
         # infobar manager for data catalog
         self.infoManager = DataCatalogInfoManager(infobar=self.infoBar,
@@ -65,9 +71,22 @@ class DataCatalog(wx.Panel):
         # some layout
         self._layout()
 
-        # show data structure infobar for first-time user with proper layout
-        if is_current_mapset_in_demolocation():
-            wx.CallLater(2000, self.showDataStructureInfo)
+        # show infobar for first-time user if applicable
+        if is_first_time_user():
+            # show data structure infobar for first-time user
+            wx.CallLater(delay, self.showDataStructureInfo)
+
+        # show infobar if last used mapset is not usable
+        if is_fallback_session():
+            # get reason why last used mapset is not usable
+            last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
+            self.reason_id = get_reason_id_mapset_not_usable(last_mapset_path)
+            if self.reason_id in ("non-existent", "invalid", "different-owner"):
+                # show non-standard situation info
+                wx.CallLater(delay, self.showFallbackSessionInfo)
+            elif self.reason_id == "locked":
+                # show info allowing to switch to locked mapset
+                wx.CallLater(delay, self.showLockedMapsetInfo)
 
     def _layout(self):
         """Do layout"""
@@ -85,12 +104,22 @@ class DataCatalog(wx.Panel):
     def showDataStructureInfo(self):
         self.infoManager.ShowDataStructureInfo(self.OnCreateLocation)
 
+    def showLockedMapsetInfo(self):
+        self.infoManager.ShowLockedMapsetInfo(self.OnSwitchToLastUsedMapset)
+
+    def showFallbackSessionInfo(self):
+        self.infoManager.ShowFallbackSessionInfo(self.reason_id)
+
     def showImportDataInfo(self):
         self.infoManager.ShowImportDataInfo(self.OnImportOgrLayers, self.OnImportGdalLayers)
 
     def LoadItems(self):
         self.tree.ReloadTreeItems()
 
+    def dismissInfobar(self):
+        if self.infoBar.IsShown():
+            self.infoBar.Dismiss()
+
     def OnReloadTree(self, event):
         """Reload whole tree"""
         self.LoadItems()
@@ -135,6 +164,12 @@ class DataCatalog(wx.Panel):
         db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
         self.tree.DownloadLocation(db_node)
 
+    def OnSwitchToLastUsedMapset(self, event):
+        """Switch to last used mapset"""
+        last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
+        grassdb, location, mapset = split_mapset_path(last_mapset_path)
+        self.tree.SwitchMapset(grassdb, location, mapset)
+
     def OnImportGdalLayers(self, event):
         """Convert multiple GDAL layers to GRASS raster map layers"""
         from modules.import_export import GdalImportDialog

+ 53 - 6
gui/wxpython/datacatalog/infomanager.py

@@ -20,6 +20,7 @@ This program is free software under the GNU General Public License
 import wx
 
 from grass.script import gisenv
+from grass.grassdb.checks import get_mapset_owner
 
 
 class DataCatalogInfoManager:
@@ -31,8 +32,10 @@ class DataCatalogInfoManager:
 
     def ShowDataStructureInfo(self, onCreateLocationHandler):
         """Show info about the data hierarchy focused on the first-time user"""
-        buttons = [("Create new Location", onCreateLocationHandler),
-                   ("Learn More", self._onLearnMore)]
+        buttons = [
+            (_("Create new Location"), onCreateLocationHandler),
+            (_("Learn more"), self._onLearnMore),
+        ]
         message = _(
             "GRASS GIS helps you organize your data using Locations (projects) "
             "which contain Mapsets (subprojects). All data in one Location is "
@@ -41,13 +44,15 @@ class DataCatalogInfoManager:
             "which uses WGS 84 (EPSG:4326). Consider creating a new Location with a CRS "
             "specific to your area. You can do it now or anytime later from "
             "the toolbar above."
-        ).format(loc=gisenv()['LOCATION_NAME'])
+        ).format(loc=gisenv()["LOCATION_NAME"])
         self.infoBar.ShowMessage(message, wx.ICON_INFORMATION, buttons)
 
     def ShowImportDataInfo(self, OnImportOgrLayersHandler, OnImportGdalLayersHandler):
         """Show info about the data import focused on the first-time user"""
-        buttons = [("Import vector data", OnImportOgrLayersHandler),
-                   ("Import raster data", OnImportGdalLayersHandler)]
+        buttons = [
+            (_("Import vector data"), OnImportOgrLayersHandler),
+            (_("Import raster data"), OnImportGdalLayersHandler),
+        ]
         message = _(
             "You have successfully created a new Location {loc}. "
             "Currently you are in its PERMANENT Mapset which is used for "
@@ -55,8 +60,50 @@ class DataCatalogInfoManager:
             "Mapsets. You can create new Mapsets for different tasks by right "
             "clicking on the Location name.\n\n"
             "To import data, go to the toolbar above or use the buttons below."
-        ).format(loc=gisenv()['LOCATION_NAME'])
+        ).format(loc=gisenv()["LOCATION_NAME"])
         self.infoBar.ShowMessage(message, wx.ICON_INFORMATION, buttons)
 
+    def ShowFallbackSessionInfo(self, reason_id):
+        """Show info when last used mapset is not usable"""
+        string = self._text_from_reason_id(reason_id)
+        message = _(
+            "{string} GRASS GIS has started in a temporary Location. "
+            "To continue, use Data Catalog below to switch to a different Location."
+        ).format(
+            string=string,
+        )
+        self.infoBar.ShowMessage(message, wx.ICON_INFORMATION)
+
+    def ShowLockedMapsetInfo(self, OnSwitchMapsetHandler):
+        """Show info when last used mapset is locked"""
+        last_used_mapset_path = gisenv()["LAST_MAPSET_PATH"]
+        buttons = [(_("Switch to last used mapset"), OnSwitchMapsetHandler)]
+        message = _(
+            "Last used mapset in path '{mapsetpath}' is currently in use. "
+            "GRASS GIS has started in a temporary Location. "
+            "To continue, use Data Catalog below to switch to a different Location "
+            "or remove lock file and switch to the last used mapset."
+        ).format(mapsetpath=last_used_mapset_path)
+        self.infoBar.ShowMessage(message, wx.ICON_INFORMATION, buttons)
+
+    def _text_from_reason_id(self, reason_id):
+        """ Get string for infobar message based on the reason."""
+        last_used_mapset_path = gisenv()["LAST_MAPSET_PATH"]
+        reason = None
+        if reason_id == "non-existent":
+            reason = _(
+                "Last used mapset in path '{mapsetpath}' does not exist."
+            ).format(mapsetpath=last_used_mapset_path)
+        elif reason_id == "invalid":
+            reason = _("Last used mapset in path '{mapsetpath}' is invalid.").format(
+                mapsetpath=last_used_mapset_path
+            )
+        elif reason_id == "different-owner":
+            owner = get_mapset_owner(last_used_mapset_path)
+            reason = _(
+                "Last used mapset in path '{mapsetpath}' has different owner {owner}."
+            ).format(owner=owner, mapsetpath=last_used_mapset_path)
+        return reason
+
     def _onLearnMore(self, event):
         self._giface.Help(entry="grass_database")

+ 11 - 7
gui/wxpython/datacatalog/tree.py

@@ -74,8 +74,7 @@ import grass.script as gscript
 from grass.script import gisenv
 from grass.grassdb.data import map_exists
 from grass.grassdb.checks import (get_mapset_owner, is_mapset_locked,
-                                  is_different_mapset_owner,
-                                  is_current_mapset_in_demolocation)
+                                  is_different_mapset_owner, is_first_time_user)
 from grass.exceptions import CalledModuleError
 
 
@@ -957,7 +956,7 @@ class DataCatalogTree(TreeView):
         """
         Creates new location interactively and adds it to the tree and switch
         to its new PERMANENT mapset.
-        If a user was in Demolocation, it shows data import infobar.
+        If a user is a first-time user, it shows data import infobar.
         """
         grassdatabase, location, mapset = (
             create_location_interactively(self, grassdb_node.data['name'])
@@ -968,14 +967,14 @@ class DataCatalogTree(TreeView):
                                              element='location',
                                              action='new')
 
-            # show data import infobar for first-time user with proper layout
-            if is_current_mapset_in_demolocation():
-                self.showImportDataInfo.emit()
-
             # switch to PERMANENT mapset in newly created location
             self.SwitchMapset(grassdatabase, location, mapset,
                               show_confirmation=True)
 
+            # show data import infobar for first-time user with proper layout
+            if is_first_time_user():
+                self.showImportDataInfo.emit()
+
     def OnCreateLocation(self, event):
         """Create new location"""
         self.CreateLocation(self.selected_grassdb[0])
@@ -1560,6 +1559,11 @@ class DataCatalogTree(TreeView):
                                       location=location)
                 if node:
                     self._renameNode(node, newname)
+        elif element == 'grassdb':
+            if action == 'delete':
+                node = self.GetDbNode(grassdb=grassdb)
+                if node:
+                    self.RemoveGrassDB(node)
         elif element in ('raster', 'vector', 'raster_3d'):
             # when watchdog is used, it watches current mapset,
             # so we don't process any signals here,

+ 0 - 955
gui/wxpython/gis_set.py

@@ -1,955 +0,0 @@
-"""
-@package gis_set
-
-GRASS start-up screen.
-
-Initialization module for wxPython GRASS GUI.
-Location/mapset management (selection, creation, etc.).
-
-Classes:
- - gis_set::GRASSStartup
- - gis_set::GListBox
- - gis_set::StartUp
-
-(C) 2006-2014 by 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 Michael Barton and Jachym Cepicky (original author)
-@author Martin Landa <landa.martin gmail.com> (various updates)
-"""
-
-import os
-import sys
-import copy
-import platform
-
-# i18n is taken care of in the grass library code.
-# So we need to import it before any of the GUI code.
-
-from core import globalvar
-import wx
-# import adv and html before wx.App is created, otherwise
-# we get annoying "Debug: Adding duplicate image handler for 'Windows bitmap file'"
-# during download location dialog start up, remove when not needed
-import wx.adv
-import wx.html
-import wx.lib.mixins.listctrl as listmix
-
-from grass.grassdb.checks import get_lockfile_if_present
-from grass.app import get_possible_database_path
-
-from core.gcmd import GError, RunCommand
-from core.utils import GetListOfLocations, GetListOfMapsets
-from startup.guiutils import (SetSessionMapset,
-                              create_mapset_interactively,
-                              create_location_interactively,
-                              rename_mapset_interactively,
-                              rename_location_interactively,
-                              delete_mapset_interactively,
-                              delete_location_interactively,
-                              download_location_interactively)
-import startup.guiutils as sgui
-from gui_core.widgets import StaticWrapText
-from gui_core.wrap import Button, ListCtrl, StaticText, StaticBox, \
-    TextCtrl, BitmapFromImage
-
-
-class GRASSStartup(wx.Frame):
-    exit_success = 0
-    # 2 is file not found from python interpreter
-    exit_user_requested = 5
-
-    """GRASS start-up screen"""
-
-    def __init__(self, parent=None, id=wx.ID_ANY,
-                 style=wx.DEFAULT_FRAME_STYLE):
-
-        #
-        # GRASS variables
-        #
-        self.gisbase = os.getenv("GISBASE")
-        self.grassrc = sgui.read_gisrc()
-        self.gisdbase = self.GetRCValue("GISDBASE")
-
-        #
-        # list of locations/mapsets
-        #
-        self.listOfLocations = []
-        self.listOfMapsets = []
-        self.listOfMapsetsSelectable = []
-
-        wx.Frame.__init__(self, parent=parent, id=id, style=style)
-
-        self.locale = wx.Locale(language=wx.LANGUAGE_DEFAULT)
-
-        # scroll panel was used here but not properly and is probably not need
-        # as long as it is not high too much
-        self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
-
-        # i18N
-
-        #
-        # graphical elements
-        #
-        # image
-        try:
-            if os.getenv('ISISROOT'):
-                name = os.path.join(
-                    globalvar.GUIDIR,
-                    "images",
-                    "startup_banner_isis.png")
-            else:
-                name = os.path.join(
-                    globalvar.GUIDIR, "images", "startup_banner.png")
-            self.hbitmap = wx.StaticBitmap(self.panel, wx.ID_ANY,
-                                           wx.Bitmap(name=name,
-                                                     type=wx.BITMAP_TYPE_PNG))
-        except:
-            self.hbitmap = wx.StaticBitmap(
-                self.panel, wx.ID_ANY, BitmapFromImage(
-                    wx.EmptyImage(530, 150)))
-
-        # labels
-        # crashes when LOCATION doesn't exist
-        # get version & revision
-        grassVersion, grassRevisionStr = sgui.GetVersion()
-
-        self.gisdbase_box = StaticBox(
-            parent=self.panel, id=wx.ID_ANY, label=" %s " %
-            _("1. Select GRASS GIS database directory"))
-        self.location_box = StaticBox(
-            parent=self.panel, id=wx.ID_ANY, label=" %s " %
-            _("2. Select GRASS Location"))
-        self.mapset_box = StaticBox(
-            parent=self.panel, id=wx.ID_ANY, label=" %s " %
-            _("3. Select GRASS Mapset"))
-
-        self.lmessage = StaticWrapText(parent=self.panel)
-        # It is not clear if all wx versions supports color, so try-except.
-        # The color itself may not be correct for all platforms/system settings
-        # but in http://xoomer.virgilio.it/infinity77/wxPython/Widgets/wx.SystemSettings.html
-        # there is no 'warning' color.
-        try:
-            self.lmessage.SetForegroundColour(wx.Colour(255, 0, 0))
-        except AttributeError:
-            pass
-
-        self.gisdbase_panel = wx.Panel(parent=self.panel)
-        self.location_panel = wx.Panel(parent=self.panel)
-        self.mapset_panel = wx.Panel(parent=self.panel)
-
-        self.ldbase = StaticText(
-            parent=self.gisdbase_panel, id=wx.ID_ANY,
-            label=_("GRASS GIS database directory contains Locations."))
-
-        self.llocation = StaticWrapText(
-            parent=self.location_panel, id=wx.ID_ANY,
-            label=_("All data in one Location is in the same "
-                    " coordinate reference system (projection)."
-                    " One Location can be one project."
-                    " Location contains Mapsets."),
-            style=wx.ALIGN_LEFT)
-
-        self.lmapset = StaticWrapText(
-            parent=self.mapset_panel, id=wx.ID_ANY,
-            label=_("Mapset contains GIS data related"
-                    " to one project, task within one project,"
-                    " subregion or user."),
-            style=wx.ALIGN_LEFT)
-
-        try:
-            for label in [self.ldbase, self.llocation, self.lmapset]:
-                label.SetForegroundColour(
-                    wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
-        except AttributeError:
-            # for explanation of try-except see above
-            pass
-
-        # buttons
-        self.bstart = Button(parent=self.panel, id=wx.ID_ANY,
-                             label=_("Start &GRASS session"))
-        self.bstart.SetDefault()
-        self.bexit = Button(parent=self.panel, id=wx.ID_EXIT)
-        self.bstart.SetMinSize((180, self.bexit.GetSize()[1]))
-        self.bhelp = Button(parent=self.panel, id=wx.ID_HELP)
-        self.bbrowse = Button(parent=self.gisdbase_panel, id=wx.ID_ANY,
-                              label=_("&Browse"))
-        self.bmapset = Button(parent=self.mapset_panel, id=wx.ID_ANY,
-                              # GTC New mapset
-                              label=_("&New"))
-        self.bmapset.SetToolTip(_("Create a new Mapset in selected Location"))
-        self.bwizard = Button(parent=self.location_panel, id=wx.ID_ANY,
-                              # GTC New location
-                              label=_("N&ew"))
-        self.bwizard.SetToolTip(
-            _(
-                "Create a new location using location wizard."
-                " After location is created successfully,"
-                " GRASS session is started."))
-        self.rename_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
-                                             # GTC Rename location
-                                             label=_("Ren&ame"))
-        self.rename_location_button.SetToolTip(_("Rename selected location"))
-        self.delete_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
-                                             # GTC Delete location
-                                             label=_("De&lete"))
-        self.delete_location_button.SetToolTip(_("Delete selected location"))
-        self.download_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
-                                             label=_("Do&wnload"))
-        self.download_location_button.SetToolTip(_("Download sample location"))
-
-        self.rename_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY,
-                                           # GTC Rename mapset
-                                           label=_("&Rename"))
-        self.rename_mapset_button.SetToolTip(_("Rename selected mapset"))
-        self.delete_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY,
-                                           # GTC Delete mapset
-                                           label=_("&Delete"))
-        self.delete_mapset_button.SetToolTip(_("Delete selected mapset"))
-
-        # textinputs
-        self.tgisdbase = TextCtrl(
-            parent=self.gisdbase_panel, id=wx.ID_ANY, value="", size=(
-                300, -1), style=wx.TE_PROCESS_ENTER)
-
-        # Locations
-        self.lblocations = GListBox(parent=self.location_panel,
-                                    id=wx.ID_ANY, size=(180, 200),
-                                    choices=self.listOfLocations)
-        self.lblocations.SetColumnWidth(0, 180)
-
-        # TODO: sort; but keep PERMANENT on top of list
-        # Mapsets
-        self.lbmapsets = GListBox(parent=self.mapset_panel,
-                                  id=wx.ID_ANY, size=(180, 200),
-                                  choices=self.listOfMapsets)
-        self.lbmapsets.SetColumnWidth(0, 180)
-
-        # layout & properties, first do layout so everything is created
-        self._do_layout()
-        self._set_properties(grassVersion, grassRevisionStr)
-
-        # events
-        self.bbrowse.Bind(wx.EVT_BUTTON, self.OnBrowse)
-        self.bstart.Bind(wx.EVT_BUTTON, self.OnStart)
-        self.bexit.Bind(wx.EVT_BUTTON, self.OnExit)
-        self.bhelp.Bind(wx.EVT_BUTTON, self.OnHelp)
-        self.bmapset.Bind(wx.EVT_BUTTON, self.OnCreateMapset)
-        self.bwizard.Bind(wx.EVT_BUTTON, self.OnCreateLocation)
-
-        self.rename_location_button.Bind(wx.EVT_BUTTON, self.OnRenameLocation)
-        self.delete_location_button.Bind(wx.EVT_BUTTON, self.OnDeleteLocation)
-        self.download_location_button.Bind(wx.EVT_BUTTON, self.OnDownloadLocation)
-        self.rename_mapset_button.Bind(wx.EVT_BUTTON, self.OnRenameMapset)
-        self.delete_mapset_button.Bind(wx.EVT_BUTTON, self.OnDeleteMapset)
-
-        self.lblocations.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectLocation)
-        self.lbmapsets.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectMapset)
-        self.lbmapsets.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnStart)
-        self.tgisdbase.Bind(wx.EVT_TEXT_ENTER, self.OnSetDatabase)
-        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
-
-    def _set_properties(self, version, revision):
-        """Set frame properties
-
-        :param version: Version in the form of X.Y.Z
-        :param revision: Version control revision with leading space
-
-        *revision* should be an empty string in case of release and
-        otherwise it needs a leading space to be separated from the rest
-        of the title.
-        """
-        self.SetTitle(_("GRASS GIS %s Startup%s") % (version, revision))
-        self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, "grass.ico"),
-                             wx.BITMAP_TYPE_ICO))
-
-        self.bstart.SetToolTip(_("Enter GRASS session"))
-        self.bstart.Enable(False)
-        self.bmapset.Enable(False)
-        # this all was originally a choice, perhaps just mapset needed
-        self.rename_location_button.Enable(False)
-        self.delete_location_button.Enable(False)
-        self.rename_mapset_button.Enable(False)
-        self.delete_mapset_button.Enable(False)
-
-        # set database
-        if not self.gisdbase:
-            # sets an initial path for gisdbase if nothing in GISRC
-            if os.path.isdir(os.getenv("HOME")):
-                self.gisdbase = os.getenv("HOME")
-            else:
-                self.gisdbase = os.getcwd()
-        try:
-            self.tgisdbase.SetValue(self.gisdbase)
-        except UnicodeDecodeError:
-            wx.MessageBox(parent=self, caption=_("Error"),
-                          message=_("Unable to set GRASS database. "
-                                    "Check your locale settings."),
-                          style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
-
-        self.OnSetDatabase(None)
-        location = self.GetRCValue("LOCATION_NAME")
-        if location == "<UNKNOWN>" or location is None:
-            return
-        if not os.path.isdir(os.path.join(self.gisdbase, location)):
-            location = None
-
-        # list of locations
-        self.UpdateLocations(self.gisdbase)
-        try:
-            self.lblocations.SetSelection(self.listOfLocations.index(location),
-                                          force=True)
-            self.lblocations.EnsureVisible(
-                self.listOfLocations.index(location))
-        except ValueError:
-            sys.stderr.write(
-                _("ERROR: Location <%s> not found\n") %
-                self.GetRCValue("LOCATION_NAME"))
-            if len(self.listOfLocations) > 0:
-                self.lblocations.SetSelection(0, force=True)
-                self.lblocations.EnsureVisible(0)
-                location = self.listOfLocations[0]
-            else:
-                return
-
-        # list of mapsets
-        self.UpdateMapsets(os.path.join(self.gisdbase, location))
-        mapset = self.GetRCValue("MAPSET")
-        if mapset:
-            try:
-                self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset),
-                                            force=True)
-                self.lbmapsets.EnsureVisible(self.listOfMapsets.index(mapset))
-            except ValueError:
-                sys.stderr.write(_("ERROR: Mapset <%s> not found\n") % mapset)
-                self.lbmapsets.SetSelection(0, force=True)
-                self.lbmapsets.EnsureVisible(0)
-
-    def _do_layout(self):
-        sizer = wx.BoxSizer(wx.VERTICAL)
-        self.sizer = sizer  # for the layout call after changing message
-        dbase_sizer = wx.BoxSizer(wx.HORIZONTAL)
-
-        location_mapset_sizer = wx.BoxSizer(wx.HORIZONTAL)
-
-        gisdbase_panel_sizer = wx.BoxSizer(wx.VERTICAL)
-        gisdbase_boxsizer = wx.StaticBoxSizer(self.gisdbase_box, wx.VERTICAL)
-
-        btns_sizer = wx.BoxSizer(wx.HORIZONTAL)
-
-        self.gisdbase_panel.SetSizer(gisdbase_panel_sizer)
-
-        # gis data directory
-
-        gisdbase_boxsizer.Add(self.gisdbase_panel, proportion=1,
-                              flag=wx.EXPAND | wx.ALL,
-                              border=1)
-
-        gisdbase_panel_sizer.Add(dbase_sizer, proportion=1,
-                                 flag=wx.EXPAND | wx.ALL,
-                                 border=1)
-        gisdbase_panel_sizer.Add(self.ldbase, proportion=0,
-                                 flag=wx.EXPAND | wx.ALL,
-                                 border=1)
-
-        dbase_sizer.Add(self.tgisdbase, proportion=1,
-                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL,
-                        border=1)
-        dbase_sizer.Add(self.bbrowse, proportion=0,
-                        flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL,
-                        border=1)
-
-        gisdbase_panel_sizer.Fit(self.gisdbase_panel)
-
-        # location and mapset lists
-
-        def layout_list_box(box, panel, list_box, buttons, description):
-            panel_sizer = wx.BoxSizer(wx.VERTICAL)
-            main_sizer = wx.BoxSizer(wx.HORIZONTAL)
-            box_sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
-            buttons_sizer = wx.BoxSizer(wx.VERTICAL)
-
-            panel.SetSizer(panel_sizer)
-            panel_sizer.Fit(panel)
-
-            main_sizer.Add(list_box, proportion=1,
-                           flag=wx.EXPAND | wx.ALL,
-                           border=1)
-            main_sizer.Add(buttons_sizer, proportion=0,
-                           flag=wx.ALL,
-                           border=1)
-            for button in buttons:
-                buttons_sizer.Add(button, proportion=0,
-                                  flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
-                                  border=3)
-            box_sizer.Add(panel, proportion=1,
-                          flag=wx.EXPAND | wx.ALL,
-                          border=1)
-            panel_sizer.Add(main_sizer, proportion=1,
-                            flag=wx.EXPAND | wx.ALL,
-                            border=1)
-            panel_sizer.Add(description, proportion=0,
-                            flag=wx.EXPAND | wx.ALL,
-                            border=1)
-            return box_sizer
-
-        location_boxsizer = layout_list_box(
-            box=self.location_box,
-            panel=self.location_panel,
-            list_box=self.lblocations,
-            buttons=[self.bwizard, self.rename_location_button,
-                     self.delete_location_button,
-                     self.download_location_button],
-            description=self.llocation)
-        mapset_boxsizer = layout_list_box(
-            box=self.mapset_box,
-            panel=self.mapset_panel,
-            list_box=self.lbmapsets,
-            buttons=[self.bmapset, self.rename_mapset_button,
-                     self.delete_mapset_button],
-            description=self.lmapset)
-
-        # location and mapset sizer
-        location_mapset_sizer.Add(location_boxsizer, proportion=1,
-                                  flag=wx.LEFT | wx.RIGHT | wx.EXPAND,
-                                  border=3)
-        location_mapset_sizer.Add(mapset_boxsizer, proportion=1,
-                                  flag=wx.RIGHT | wx.EXPAND,
-                                  border=3)
-
-        # buttons
-        btns_sizer.Add(self.bstart, proportion=0,
-                       flag=wx.ALIGN_CENTER_HORIZONTAL |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL,
-                       border=5)
-        btns_sizer.Add(self.bexit, proportion=0,
-                       flag=wx.ALIGN_CENTER_HORIZONTAL |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL,
-                       border=5)
-        btns_sizer.Add(self.bhelp, proportion=0,
-                       flag=wx.ALIGN_CENTER_HORIZONTAL |
-                       wx.ALIGN_CENTER_VERTICAL |
-                       wx.ALL,
-                       border=5)
-
-        # main sizer
-        sizer.Add(self.hbitmap,
-                  proportion=0,
-                  flag=wx.ALIGN_CENTER_VERTICAL |
-                  wx.ALIGN_CENTER_HORIZONTAL |
-                  wx.ALL,
-                  border=3)  # image
-        sizer.Add(gisdbase_boxsizer, proportion=0,
-                  flag=wx.RIGHT | wx.LEFT | wx.TOP | wx.EXPAND,
-                  border=3)  # GISDBASE setting
-
-        # warning/error message
-        sizer.Add(self.lmessage,
-                  proportion=0,
-                  flag=wx.ALIGN_LEFT | wx.ALL | wx.EXPAND, border=5)
-        sizer.Add(location_mapset_sizer, proportion=1,
-                  flag=wx.RIGHT | wx.LEFT | wx.EXPAND,
-                  border=1)
-        sizer.Add(btns_sizer, proportion=0,
-                  flag=wx.ALIGN_CENTER_VERTICAL |
-                  wx.ALIGN_CENTER_HORIZONTAL |
-                  wx.RIGHT | wx.LEFT,
-                  border=3)
-
-        self.panel.SetAutoLayout(True)
-        self.panel.SetSizer(sizer)
-        sizer.Fit(self.panel)
-        sizer.SetSizeHints(self)
-        self.Layout()
-
-    def _showWarning(self, text):
-        """Displays a warning, hint or info message to the user.
-
-        This function can be used for all kinds of messages except for
-        error messages.
-
-        .. note::
-            There is no cleaning procedure. You should call _hideMessage when
-            you know that there is everything correct now.
-        """
-        self.lmessage.SetLabel(text)
-        self.sizer.Layout()
-
-    def _showError(self, text):
-        """Displays a error message to the user.
-
-        This function should be used only when something serious and unexpected
-        happens, otherwise _showWarning should be used.
-
-        .. note::
-            There is no cleaning procedure. You should call _hideMessage when
-            you know that there is everything correct now.
-        """
-        self.lmessage.SetLabel(_("Error: {text}").format(text=text))
-        self.sizer.Layout()
-
-    def _hideMessage(self):
-        """Clears/hides the error message."""
-        # we do no hide widget
-        # because we do not want the dialog to change the size
-        self.lmessage.SetLabel("")
-        self.sizer.Layout()
-
-    def GetRCValue(self, value):
-        """Return GRASS variable (read from GISRC)
-        """
-        if value in self.grassrc:
-            return self.grassrc[value]
-        else:
-            return None
-
-    def SuggestDatabase(self):
-        """Suggest (set) possible GRASS Database value"""
-        # only if nothing is set (<UNKNOWN> comes from init script)
-        if self.GetRCValue("LOCATION_NAME") != "<UNKNOWN>":
-            return
-        path = get_possible_database_path()
-        if path:
-            try:
-                self.tgisdbase.SetValue(path)
-            except UnicodeDecodeError:
-                # restore previous state
-                # wizard gives error in this case, we just ignore
-                path = None
-                self.tgisdbase.SetValue(self.gisdbase)
-            # if we still have path
-            if path:
-                self.gisdbase = path
-                self.OnSetDatabase(None)
-        else:
-            # nothing found
-            # TODO: should it be warning, hint or message?
-            self._showWarning(_(
-                'GRASS needs a directory (GRASS database) '
-                'in which to store its data. '
-                'Create one now if you have not already done so. '
-                'A popular choice is "grassdata", located in '
-                'your home directory. '
-                'Press Browse button to select the directory.'))
-
-    def OnCreateLocation(self, event):
-        """Location wizard started"""
-        grassdatabase, location, mapset = (
-            create_location_interactively(self, self.gisdbase)
-        )
-        if location is not None:
-            self.OnSelectLocation(None)
-            self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset))
-            self.bstart.SetFocus()
-            self.tgisdbase.SetValue(grassdatabase)
-            self.OnSetDatabase(None)
-            self.UpdateMapsets(os.path.join(grassdatabase, location))
-            self.lblocations.SetSelection(
-                self.listOfLocations.index(location))
-            self.lbmapsets.SetSelection(0)
-            self.SetLocation(grassdatabase, location, mapset)
-
-    # the event can be refactored out by using lambda in bind
-    def OnRenameMapset(self, event):
-        """Rename selected mapset
-        """
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
-        try:
-            newmapset = rename_mapset_interactively(self, self.gisdbase,
-                                                    location, mapset)
-            if newmapset:
-                self.OnSelectLocation(None)
-                self.lbmapsets.SetSelection(
-                    self.listOfMapsets.index(newmapset))
-        except Exception as e:
-            GError(parent=self,
-                   message=_("Unable to rename mapset: %s") % e,
-                   showTraceback=False)
-
-    def OnRenameLocation(self, event):
-        """Rename selected location
-        """
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        try:
-            newlocation = rename_location_interactively(self, self.gisdbase,
-                                                        location)
-            if newlocation:
-                self.UpdateLocations(self.gisdbase)
-                self.lblocations.SetSelection(
-                    self.listOfLocations.index(newlocation))
-                self.UpdateMapsets(newlocation)
-        except Exception as e:
-            GError(parent=self,
-                   message=_("Unable to rename location: %s") % e,
-                   showTraceback=False)
-
-    def OnDeleteMapset(self, event):
-        """
-        Delete selected mapset
-        """
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
-        if (delete_mapset_interactively(self, self.gisdbase, location, mapset)):
-            self.OnSelectLocation(None)
-            self.lbmapsets.SetSelection(0)
-
-    def OnDeleteLocation(self, event):
-        """
-        Delete selected location
-        """
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        try:
-            if (delete_location_interactively(self, self.gisdbase, location)):
-                self.UpdateLocations(self.gisdbase)
-                self.lblocations.SetSelection(0)
-                self.OnSelectLocation(None)
-                self.lbmapsets.SetSelection(0)
-        except Exception as e:
-            GError(parent=self,
-                   message=_("Unable to delete location: %s") % e,
-                   showTraceback=False)
-
-    def OnDownloadLocation(self, event):
-        """
-        Download location online
-        """
-        grassdatabase, location, mapset = download_location_interactively(
-            self, self.gisdbase
-        )
-        if location:
-            # get the new location to the list
-            self.UpdateLocations(grassdatabase)
-            # seems to be used in similar context
-            self.UpdateMapsets(os.path.join(grassdatabase, location))
-            self.lblocations.SetSelection(
-                self.listOfLocations.index(location))
-            # wizard does this as well, not sure if needed
-            self.SetLocation(grassdatabase, location, mapset)
-            # seems to be used in similar context
-            self.OnSelectLocation(None)
-
-    def UpdateLocations(self, dbase):
-        """Update list of locations"""
-        try:
-            self.listOfLocations = GetListOfLocations(dbase)
-        except (UnicodeEncodeError, UnicodeDecodeError) as e:
-            GError(parent=self,
-                   message=_("Unicode error detected. "
-                             "Check your locale settings. Details: {0}").format(e),
-                   showTraceback=False)
-
-        self.lblocations.Clear()
-        self.lblocations.InsertItems(self.listOfLocations, 0)
-
-        if len(self.listOfLocations) > 0:
-            self._hideMessage()
-            self.lblocations.SetSelection(0)
-        else:
-            self.lblocations.SetSelection(wx.NOT_FOUND)
-            self._showWarning(_("No GRASS Location found in '%s'."
-                                " Create a new Location or choose different"
-                                " GRASS database directory.")
-                              % self.gisdbase)
-
-        return self.listOfLocations
-
-    def UpdateMapsets(self, location):
-        """Update list of mapsets"""
-        self.FormerMapsetSelection = wx.NOT_FOUND  # for non-selectable item
-
-        self.listOfMapsetsSelectable = list()
-        self.listOfMapsets = GetListOfMapsets(self.gisdbase, location)
-
-        self.lbmapsets.Clear()
-
-        # disable mapset with denied permission
-        locationName = os.path.basename(location)
-
-        ret = RunCommand('g.mapset',
-                         read=True,
-                         flags='l',
-                         location=locationName,
-                         gisdbase=self.gisdbase)
-
-        if ret:
-            for line in ret.splitlines():
-                self.listOfMapsetsSelectable += line.split(' ')
-        else:
-            self.SetLocation(self.gisdbase, locationName, "PERMANENT")
-            # first run only
-            self.listOfMapsetsSelectable = copy.copy(self.listOfMapsets)
-
-        disabled = []
-        idx = 0
-        for mapset in self.listOfMapsets:
-            if mapset not in self.listOfMapsetsSelectable or \
-                    get_lockfile_if_present(self.gisdbase,
-                                            locationName, mapset):
-                disabled.append(idx)
-            idx += 1
-
-        self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled)
-
-        return self.listOfMapsets
-
-    def OnSelectLocation(self, event):
-        """Location selected"""
-        if event:
-            self.lblocations.SetSelection(event.GetIndex())
-
-        if self.lblocations.GetSelection() != wx.NOT_FOUND:
-            self.UpdateMapsets(
-                os.path.join(
-                    self.gisdbase,
-                    self.listOfLocations[
-                        self.lblocations.GetSelection()]))
-        else:
-            self.listOfMapsets = []
-
-        disabled = []
-        idx = 0
-        try:
-            locationName = self.listOfLocations[
-                self.lblocations.GetSelection()]
-        except IndexError:
-            locationName = ''
-
-        for mapset in self.listOfMapsets:
-            if mapset not in self.listOfMapsetsSelectable or \
-                    get_lockfile_if_present(self.gisdbase,
-                                            locationName, mapset):
-                disabled.append(idx)
-            idx += 1
-
-        self.lbmapsets.Clear()
-        self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled)
-
-        if len(self.listOfMapsets) > 0:
-            self.lbmapsets.SetSelection(0)
-            if locationName:
-                # enable start button when location and mapset is selected
-                self.bstart.Enable()
-                self.bstart.SetFocus()
-                self.bmapset.Enable()
-                # replacing disabled choice, perhaps just mapset needed
-                self.rename_location_button.Enable()
-                self.delete_location_button.Enable()
-                self.rename_mapset_button.Enable()
-                self.delete_mapset_button.Enable()
-        else:
-            self.lbmapsets.SetSelection(wx.NOT_FOUND)
-            self.bstart.Enable(False)
-            self.bmapset.Enable(False)
-            # this all was originally a choice, perhaps just mapset needed
-            self.rename_location_button.Enable(False)
-            self.delete_location_button.Enable(False)
-            self.rename_mapset_button.Enable(False)
-            self.delete_mapset_button.Enable(False)
-
-    def OnSelectMapset(self, event):
-        """Mapset selected"""
-        self.lbmapsets.SetSelection(event.GetIndex())
-
-        if event.GetText() not in self.listOfMapsetsSelectable:
-            self.lbmapsets.SetSelection(self.FormerMapsetSelection)
-        else:
-            self.FormerMapsetSelection = event.GetIndex()
-            event.Skip()
-
-    def OnSetDatabase(self, event):
-        """Database set"""
-        gisdbase = self.tgisdbase.GetValue()
-        self._hideMessage()
-        if not os.path.exists(gisdbase):
-            self._showError(_("Path '%s' doesn't exist.") % gisdbase)
-            return
-
-        self.gisdbase = self.tgisdbase.GetValue()
-        self.UpdateLocations(self.gisdbase)
-
-        self.OnSelectLocation(None)
-
-    def OnBrowse(self, event):
-        """'Browse' button clicked"""
-        if not event:
-            defaultPath = os.getenv('HOME')
-        else:
-            defaultPath = ""
-
-        dlg = wx.DirDialog(parent=self, message=_("Choose GIS Data Directory"),
-                           defaultPath=defaultPath, style=wx.DD_DEFAULT_STYLE)
-
-        if dlg.ShowModal() == wx.ID_OK:
-            self.gisdbase = dlg.GetPath()
-            self.tgisdbase.SetValue(self.gisdbase)
-            self.OnSetDatabase(event)
-
-        dlg.Destroy()
-
-    def OnCreateMapset(self, event):
-        """Create new mapset"""
-        gisdbase = self.tgisdbase.GetValue()
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        try:
-            mapset = create_mapset_interactively(self, gisdbase, location)
-            if mapset:
-                self.OnSelectLocation(None)
-                self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset))
-                self.bstart.SetFocus()
-        except Exception as e:
-            GError(parent=self,
-                   message=_("Unable to create new mapset: %s") % e,
-                   showTraceback=False)
-
-    def OnStart(self, event):
-        """'Start GRASS' button clicked"""
-        dbase = self.tgisdbase.GetValue()
-        location = self.listOfLocations[self.lblocations.GetSelection()]
-        mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
-
-        lockfile = get_lockfile_if_present(dbase, location, mapset)
-        if lockfile:
-            dlg = wx.MessageDialog(
-                parent=self,
-                message=_(
-                    "GRASS is already running in selected mapset <%(mapset)s>\n"
-                    "(file %(lock)s found).\n\n"
-                    "Concurrent use not allowed.\n\n"
-                    "Do you want to try to remove .gislock (note that you "
-                    "need permission for this operation) and continue?") %
-                {'mapset': mapset, 'lock': lockfile},
-                caption=_("Lock file found"),
-                style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
-
-            ret = dlg.ShowModal()
-            dlg.Destroy()
-            if ret == wx.ID_YES:
-                dlg1 = wx.MessageDialog(
-                    parent=self,
-                    message=_(
-                        "ARE YOU REALLY SURE?\n\n"
-                        "If you really are running another GRASS session doing this "
-                        "could corrupt your data. Have another look in the processor "
-                        "manager just to be sure..."),
-                    caption=_("Lock file found"),
-                    style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
-
-                ret = dlg1.ShowModal()
-                dlg1.Destroy()
-
-                if ret == wx.ID_YES:
-                    try:
-                        os.remove(lockfile)
-                    except IOError as e:
-                        GError(_("Unable to remove '%(lock)s'.\n\n"
-                                 "Details: %(reason)s") % {'lock': lockfile, 'reason': e})
-                else:
-                    return
-            else:
-                return
-        self.SetLocation(dbase, location, mapset)
-        self.ExitSuccessfully()
-
-    def SetLocation(self, dbase, location, mapset):
-        SetSessionMapset(dbase, location, mapset)
-
-    def ExitSuccessfully(self):
-        self.Destroy()
-        sys.exit(self.exit_success)
-
-    def OnExit(self, event):
-        """'Exit' button clicked"""
-        self.Destroy()
-        sys.exit(self.exit_user_requested)
-
-    def OnHelp(self, event):
-        """'Help' button clicked"""
-
-        # help text in lib/init/helptext.html
-        RunCommand('g.manual', entry='helptext')
-
-    def OnCloseWindow(self, event):
-        """Close window event"""
-        event.Skip()
-        sys.exit(self.exit_user_requested)
-
-
-class GListBox(ListCtrl, listmix.ListCtrlAutoWidthMixin):
-    """Use wx.ListCtrl instead of wx.ListBox, different style for
-    non-selectable items (e.g. mapsets with denied permission)"""
-
-    def __init__(self, parent, id, size,
-                 choices, disabled=[]):
-        ListCtrl.__init__(
-            self, parent, id, size=size, style=wx.LC_REPORT | wx.LC_NO_HEADER |
-            wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN)
-
-        listmix.ListCtrlAutoWidthMixin.__init__(self)
-
-        self.InsertColumn(0, '')
-
-        self.selected = wx.NOT_FOUND
-
-        self._LoadData(choices, disabled)
-
-    def _LoadData(self, choices, disabled=[]):
-        """Load data into list
-
-        :param choices: list of item
-        :param disabled: list of indices of non-selectable items
-        """
-        idx = 0
-        count = self.GetItemCount()
-        for item in choices:
-            index = self.InsertItem(count + idx, item)
-            self.SetItem(index, 0, item)
-
-            if idx in disabled:
-                self.SetItemTextColour(idx, wx.Colour(150, 150, 150))
-            idx += 1
-
-    def Clear(self):
-        self.DeleteAllItems()
-
-    def InsertItems(self, choices, pos, disabled=[]):
-        self._LoadData(choices, disabled)
-
-    def SetSelection(self, item, force=False):
-        if item != wx.NOT_FOUND and \
-                (platform.system() != 'Windows' or force):
-            # Windows -> FIXME
-            self.SetItemState(
-                item,
-                wx.LIST_STATE_SELECTED,
-                wx.LIST_STATE_SELECTED)
-
-        self.selected = item
-
-    def GetSelection(self):
-        return self.selected
-
-
-class StartUp(wx.App):
-    """Start-up application"""
-
-    def OnInit(self):
-        StartUp = GRASSStartup()
-        StartUp.CenterOnScreen()
-        self.SetTopWindow(StartUp)
-        StartUp.Show()
-        StartUp.SuggestDatabase()
-
-        return 1
-
-
-if __name__ == "__main__":
-    if os.getenv("GISBASE") is None:
-        sys.exit("Failed to start GUI, GRASS GIS is not running.")
-
-    GRASSStartUp = StartUp(0)
-    GRASSStartUp.MainLoop()

+ 0 - 41
gui/wxpython/gis_set_error.py

@@ -1,41 +0,0 @@
-"""
-@package gis_set_error
-
-GRASS start-up screen error message.
-
-(C) 2010-2011 by 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 Martin Landa <landa.martin gmail.com>
-"""
-
-import sys
-
-# i18n is taken care of in the grass library code.
-# So we need to import it before any of the GUI code.
-# NOTE: in this particular case, we don't really need the grass library;
-# NOTE: we import it just for the side effects of gettext.install()
-
-import wx
-
-
-def main():
-    app = wx.App()
-
-    if len(sys.argv) == 1:
-        msg = "Unknown reason"
-    else:
-        msg = ''
-        for m in sys.argv[1:]:
-            msg += m
-
-    wx.MessageBox(caption="Error",
-                  message=msg,
-                  style=wx.OK | wx.ICON_ERROR)
-
-    app.MainLoop()
-
-if __name__ == "__main__":
-    main()

+ 3 - 5
gui/wxpython/lmgr/frame.py

@@ -37,9 +37,6 @@ if os.path.join(globalvar.ETCDIR, "python") not in sys.path:
 
 from grass.script import core as grass
 from grass.script.utils import decode
-from startup.guiutils import (
-    can_switch_mapset_interactive
-)
 
 from core.gcmd import RunCommand, GError, GMessage
 from core.settings import UserSettings, GetDisplayVectSettings
@@ -63,12 +60,13 @@ from lmgr.giface import LayerManagerGrassInterface
 from datacatalog.catalog import DataCatalog
 from gui_core.forms import GUI
 from gui_core.wrap import Menu, TextEntryDialog
-from grass.grassdb.checks import is_current_mapset_in_demolocation
 from startup.guiutils import (
+    can_switch_mapset_interactive,
     switch_mapset_interactively,
     create_mapset_interactively,
     create_location_interactively
 )
+from grass.grassdb.checks import is_first_time_user
 
 
 class GMFrame(wx.Frame):
@@ -446,7 +444,7 @@ class GMFrame(wx.Frame):
                 lchecked=True,
                 lcmd=["d.vect", "map={}".format(layer_name)],
             )
-        if is_current_mapset_in_demolocation():
+        if is_first_time_user():
             # Show only after everything is initialized for proper map alignment.
             wx.CallLater(1000, show_demo)
 

+ 22 - 63
gui/wxpython/startup/guiutils.py

@@ -16,7 +16,6 @@ This is for code which depend on something from GUI (wx or wxGUI).
 
 
 import os
-import sys
 import wx
 
 from grass.grassdb.checks import (
@@ -30,8 +29,10 @@ from grass.grassdb.checks import (
     get_reasons_mapsets_not_removable,
     get_reasons_location_not_removable,
     get_reasons_locations_not_removable,
-    get_reasons_grassdb_not_removable
+    get_reasons_grassdb_not_removable,
+    is_fallback_session
 )
+import grass.grassdb.config as cfg
 
 from grass.grassdb.create import create_mapset, get_default_mapset_name
 from grass.grassdb.manage import (
@@ -43,21 +44,14 @@ from grass.grassdb.manage import (
 )
 from grass.script.core import create_environment
 from grass.script.utils import try_remove
+from grass.script import gisenv
 
-from core import globalvar
-from core.gcmd import GError, GMessage, DecodeString, RunCommand
+from core.gcmd import GError, GMessage, RunCommand
 from gui_core.dialogs import TextEntryDialog
 from location_wizard.dialogs import RegionDef
 from gui_core.widgets import GenericValidator
 
 
-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):
@@ -112,57 +106,6 @@ class LocationDialog(TextEntryDialog):
         return is_location_name_valid(self.database, text)
 
 
-# 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
@@ -717,6 +660,9 @@ def import_file(guiparent, filePath, env):
 def switch_mapset_interactively(guiparent, giface, dbase, location, mapset,
                                 show_confirmation=False):
     """Switch current mapset. Emits giface.currentMapsetChanged signal."""
+    # Decide if a user is in a fallback session
+    fallback_session = is_fallback_session()
+
     if dbase:
         if RunCommand('g.mapset', parent=guiparent,
                       location=location,
@@ -751,4 +697,17 @@ def switch_mapset_interactively(guiparent, giface, dbase, location, mapset,
             if show_confirmation:
                 GMessage(parent=guiparent,
                          message=_("Current mapset is <%s>.") % mapset)
-            giface.currentMapsetChanged.emit(dbase=None, location=None, mapset=mapset)
+            giface.currentMapsetChanged.emit(dbase=None,
+                                             location=None,
+                                             mapset=mapset)
+
+    if fallback_session:
+        tmp_dbase = os.environ["TMPDIR"]
+        tmp_loc = cfg.temporary_location
+        if tmp_dbase != gisenv()["GISDBASE"]:
+            # Delete temporary location
+            delete_location(tmp_dbase, tmp_loc)
+            # Remove useless temporary grassdb node
+            giface.grassdbChanged.emit(
+                location=location, grassdb=tmp_dbase, action="delete", element="grassdb"
+            )

+ 119 - 129
lib/init/grass.py

@@ -586,6 +586,21 @@ def read_gisrc(filename):
     return kv
 
 
+def write_gisrcrc(gisrcrc, gisrc, skip_variable=None):
+    """Reads gisrc file and write to gisrcrc"""
+    debug("Reading %s" % gisrc)
+    number = 0
+    with open(gisrc, "r") as f:
+        lines = f.readlines()
+        for line in lines:
+            if skip_variable in line:
+                del lines[number]
+            number += 1
+    with open(gisrcrc, "w") as f:
+        for line in lines:
+            f.write(line)
+
+
 def read_env_file(path):
     kv = {}
     f = open(path, "r")
@@ -605,7 +620,7 @@ def write_gisrc(kv, filename, append=False):
     f.close()
 
 
-def set_mapset_to_gisrc(gisrc, grassdb, location, mapset):
+def add_mapset_to_gisrc(gisrc, grassdb, location, mapset):
     if os.access(gisrc, os.R_OK):
         kv = read_gisrc(gisrc)
     else:
@@ -616,6 +631,27 @@ def set_mapset_to_gisrc(gisrc, grassdb, location, mapset):
     write_gisrc(kv, gisrc)
 
 
+def add_last_mapset_to_gisrc(gisrc, last_mapset_path):
+    if os.access(gisrc, os.R_OK):
+        kv = read_gisrc(gisrc)
+    else:
+        kv = {}
+    kv["LAST_MAPSET_PATH"] = last_mapset_path
+    write_gisrc(kv, gisrc)
+
+
+def create_fallback_session(gisrc, tmpdir):
+    """Creates fallback temporary session"""
+    # Create temporary location
+    set_mapset(
+        gisrc=gisrc,
+        geofile="XY",
+        create_new=True,
+        tmp_location=True,
+        tmpdir=tmpdir,
+    )
+
+
 def read_gui(gisrc, default_gui):
     grass_gui = None
     # At this point the GRASS user interface variable has been set from the
@@ -1165,7 +1201,7 @@ def set_mapset(
                         )
                     )
                     writefile(os.path.join(path, "WIND"), s)
-        set_mapset_to_gisrc(gisrc, gisdbase, location_name, mapset)
+        add_mapset_to_gisrc(gisrc, gisdbase, location_name, mapset)
     else:
         fatal(
             _(
@@ -1176,77 +1212,6 @@ def set_mapset(
         )
 
 
-def set_mapset_interactive(grass_gui):
-    """User selects Location and Mapset in an interative way
-
-    The gisrc (GRASS environment file) is written at the end.
-    """
-    if not os.path.exists(wxpath("gis_set.py")) and grass_gui != "text":
-        debug("No GUI available, switching to text mode")
-        return False
-
-    # Check for text interface
-    if grass_gui == "text":
-        # TODO: maybe this should be removed and solved from outside
-        # this depends on what we expect from this function
-        # should gisrc be ok after running or is it allowed to be still not set
-        pass
-    # Check for GUI
-    elif grass_gui in ("gtext", "wxpython"):
-        gui_startup(grass_gui)
-    else:
-        # Shouldn't need this but you never know
-        fatal(
-            _(
-                "Invalid user interface specified - <%s>. "
-                "Use the --help option to see valid interface names."
-            )
-            % grass_gui
-        )
-
-    return True
-
-
-def gui_startup(grass_gui):
-    """Start GUI for startup (setting gisrc file)"""
-    if grass_gui in ("wxpython", "gtext"):
-        ret = call([os.getenv("GRASS_PYTHON"), wxpath("gis_set.py")])
-
-    # this if could be simplified to three branches (0, 5, rest)
-    # if there is no need to handle unknown code separately
-    if ret == 0:
-        pass
-    elif ret in [1, 2]:
-        # 1 probably error coming from gis_set.py
-        # 2 probably file not found from python interpreter
-        # formerly we were starting in text mode instead, now we just fail
-        # which is more straightforward for everybody
-        fatal(
-            _(
-                "Error in GUI startup. See messages above (if any)"
-                " and if necessary, please"
-                " report this error to the GRASS developers.\n"
-                "On systems with package manager, make sure you have the right"
-                " GUI package, probably named grass-gui, installed.\n"
-                "To run GRASS GIS in text mode use the --text flag.\n"
-                "Use '--help' for further options\n"
-                "     {cmd_name} --help\n"
-                "See also: https://grass.osgeo.org/{cmd_name}/manuals/helptext.html"
-            ).format(cmd_name=CMD_NAME)
-        )
-    elif ret == 5:  # defined in gui/wxpython/gis_set.py
-        # User wants to exit from GRASS
-        message(_("Exit was requested in GUI.\nGRASS GIS will not start. Bye."))
-        sys.exit(0)
-    else:
-        fatal(
-            _(
-                "Invalid return code from GUI startup script.\n"
-                "Please advise GRASS developers of this error."
-            )
-        )
-
-
 # we don't follow the LOCATION_NAME legacy naming here but we have to still
 # translate to it, so always double check
 class MapsetSettings(object):
@@ -1276,6 +1241,18 @@ class MapsetSettings(object):
         return self.gisdbase and self.location and self.mapset
 
 
+def get_mapset_settings(gisrc):
+    """Get the settings of Location and Mapset from the gisrc file"""
+    mapset_settings = MapsetSettings()
+    kv = read_gisrc(gisrc)
+    mapset_settings.gisdbase = kv.get("GISDBASE")
+    mapset_settings.location = kv.get("LOCATION_NAME")
+    mapset_settings.mapset = kv.get("MAPSET")
+    if not mapset_settings.is_valid():
+        return None
+    return mapset_settings
+
+
 # TODO: does it really makes sense to tell user about gisrcrc?
 # anything could have happened in between loading from gisrcrc and now
 # (we do e.g. GUI or creating loctation)
@@ -1284,12 +1261,8 @@ def load_gisrc(gisrc, gisrcrc):
 
     :returns: MapsetSettings object
     """
-    mapset_settings = MapsetSettings()
-    kv = read_gisrc(gisrc)
-    mapset_settings.gisdbase = kv.get("GISDBASE")
-    mapset_settings.location = kv.get("LOCATION_NAME")
-    mapset_settings.mapset = kv.get("MAPSET")
-    if not mapset_settings.is_valid():
+    mapset_settings = get_mapset_settings(gisrc)
+    if not mapset_settings:
         fatal(
             _(
                 "Error reading data path information from g.gisenv.\n"
@@ -1307,22 +1280,6 @@ def load_gisrc(gisrc, gisrcrc):
     return mapset_settings
 
 
-def can_start_in_gisrc_mapset(gisrc, ignore_lock=False):
-    """Check if a mapset from a gisrc file is usable for a new session"""
-    from grass.grassdb.checks import can_start_in_mapset
-
-    mapset_settings = MapsetSettings()
-    kv = read_gisrc(gisrc)
-    mapset_settings.gisdbase = kv.get("GISDBASE")
-    mapset_settings.location = kv.get("LOCATION_NAME")
-    mapset_settings.mapset = kv.get("MAPSET")
-    if not mapset_settings.is_valid():
-        return False
-    return can_start_in_mapset(
-        mapset_path=mapset_settings.full_mapset, ignore_lock=ignore_lock
-    )
-
-
 # load environmental variables from grass_env_file
 def load_env(grass_env_file):
     if not os.access(grass_env_file, os.R_OK):
@@ -1606,6 +1563,8 @@ def lock_mapset(mapset_path, force_gislock_removal, user):
     Behavior on error must be changed somehow; now it fatals but GUI case is
     unresolved.
     """
+    from grass.grassdb.checks import is_mapset_valid
+
     if not os.path.exists(mapset_path):
         fatal(_("Path '%s' doesn't exist") % mapset_path)
     if not os.access(mapset_path, os.W_OK):
@@ -2555,39 +2514,71 @@ def main():
         save_gui(gisrc, grass_gui)
 
     # Parsing argument to get LOCATION
+    # Mapset is not specified in command line arguments
     if not params.mapset and not params.tmp_location:
-        # Mapset is not specified in command line arguments.
-        last_mapset_usable = can_start_in_gisrc_mapset(
-            gisrc=gisrc, ignore_lock=params.force_gislock_removal
+        # Get mapset parameters from gisrc file
+        mapset_settings = get_mapset_settings(gisrc)
+        last_mapset_path = mapset_settings.full_mapset
+        # Check if mapset from gisrc is usable
+        from grass.grassdb.checks import can_start_in_mapset
+
+        last_mapset_usable = can_start_in_mapset(
+            mapset_path=last_mapset_path,
+            ignore_lock=params.force_gislock_removal,
         )
         debug(f"last_mapset_usable: {last_mapset_usable}")
         if not last_mapset_usable:
-            import grass.app as ga
-            from grass.grassdb.checks import can_start_in_mapset
-
-            # Try to use demolocation
-            grassdb, location, mapset = ga.ensure_demolocation()
-            demo_mapset_usable = can_start_in_mapset(
-                mapset_path=os.path.join(grassdb, location, mapset),
-                ignore_lock=params.force_gislock_removal,
-            )
-            debug(f"demo_mapset_usable: {demo_mapset_usable}")
-            if demo_mapset_usable:
-                set_mapset_to_gisrc(
-                    gisrc=gisrc, grassdb=grassdb, location=location, mapset=mapset
-                )
-            else:
-                # Try interactive startup
-                # User selects LOCATION and MAPSET if not set
-                if not set_mapset_interactive(grass_gui):
-                    # No GUI available, update gisrc file
+            from grass.app import ensure_default_data_hierarchy
+            from grass.grassdb.checks import is_first_time_user
+
+            fallback_session = False
+
+            # Add last used mapset to gisrc
+            add_last_mapset_to_gisrc(gisrc, last_mapset_path)
+
+            if is_first_time_user():
+                # Ensure default data hierarchy
+                (
+                    default_gisdbase,
+                    default_location,
+                    unused_default_mapset,
+                    default_mapset_path,
+                ) = ensure_default_data_hierarchy()
+
+                if not default_gisdbase:
                     fatal(
                         _(
-                            "<{0}> requested, but not available. Run GRASS in text "
-                            "mode (--text) or install missing package (usually "
-                            "'grass-gui')."
-                        ).format(grass_gui)
+                            "Failed to start GRASS GIS, grassdata directory could not be found or created."
+                        )
                     )
+                elif not default_location:
+                    fatal(
+                        _(
+                            "Failed to start GRASS GIS, no default location to copy in the installation or copying failed."
+                        )
+                    )
+                if can_start_in_mapset(
+                    mapset_path=default_mapset_path, ignore_lock=False
+                ):
+                    # Use the default location/mapset.
+                    set_mapset(gisrc=gisrc, arg=default_mapset_path)
+                else:
+                    fallback_session = True
+                    add_last_mapset_to_gisrc(gisrc, default_mapset_path)
+            else:
+                fallback_session = True
+
+            if fallback_session:
+                if grass_gui == "text":
+                    # Fallback in command line is just failing in a standard way.
+                    set_mapset(gisrc=gisrc, arg=last_mapset_path)
+                else:
+                    # Create fallback temporary session
+                    create_fallback_session(gisrc, tmpdir)
+                    params.tmp_location = True
+        else:
+            # Use the last used mapset.
+            set_mapset(gisrc=gisrc, arg=last_mapset_path)
     else:
         # Mapset was specified in command line parameters.
         if params.tmp_location:
@@ -2627,12 +2618,8 @@ def main():
             force_gislock_removal=params.force_gislock_removal,
         )
     except Exception as e:
-        msg = e.args[0]
-        if grass_gui == "wxpython":
-            call([os.getenv("GRASS_PYTHON"), wxpath("gis_set_error.py"), msg])
-            sys.exit(_("Exiting..."))
-        else:
-            fatal(msg)
+        fatal(e.args[0])
+        sys.exit(_("Exiting..."))
 
     # unlock the mapset which is current at the time of turning off
     # in case mapset was changed
@@ -2714,8 +2701,11 @@ def main():
 
         # here we are at the end of grass session
         clean_all()
-        if not params.tmp_location:
-            writefile(gisrcrc, readfile(gisrc))
+        mapset_settings = load_gisrc(gisrc, gisrcrc=gisrcrc)
+        if not params.tmp_location or (
+            params.tmp_location and mapset_settings.gisdbase != os.environ["TMPDIR"]
+        ):
+            write_gisrcrc(gisrcrc, gisrc, skip_variable="LAST_MAPSET_PATH")
         # After this point no more grass modules may be called
         # done message at last: no atexit.register()
         # or register done_message()

+ 21 - 13
python/grass/app/data.py

@@ -17,6 +17,7 @@ import tempfile
 import getpass
 import sys
 from shutil import copytree, ignore_patterns
+import grass.grassdb.config as cfg
 
 from grass.grassdb.checks import is_location_valid
 
@@ -141,18 +142,25 @@ def create_startup_location_in_grassdb(grassdatabase, startup_location_name):
     return False
 
 
-def ensure_demolocation():
-    """Ensure that demolocation exists
+def ensure_default_data_hierarchy():
+    """Ensure that default gisdbase, location and mapset exist.
+    Creates database directory based on the default path determined
+    according to OS if needed. Creates location if needed.
 
-    Creates both database directory and location if needed.
+    Returns the db, loc, mapset, mapset_path"""
 
-    Returns the db, location name, and preferred mapset of the demolocation.
-    """
-    grassdb = get_possible_database_path()
-    # If nothing found, try to create GRASS directory and copy startup loc
-    if grassdb is None:
-        grassdb = create_database_directory()
-    location = "world_latlong_wgs84"
-    if not is_location_valid(grassdb, location):
-        create_startup_location_in_grassdb(grassdb, location)
-    return (grassdb, location, "PERMANENT")
+    gisdbase = get_possible_database_path()
+    location = cfg.default_location
+    mapset = cfg.permanent_mapset
+
+    # If nothing found, try to create GRASS directory
+    if not gisdbase:
+        gisdbase = create_database_directory()
+
+    if not is_location_valid(gisdbase, location):
+        # If not valid, copy startup loc
+        create_startup_location_in_grassdb(gisdbase, location)
+
+    mapset_path = os.path.join(gisdbase, location, mapset)
+
+    return gisdbase, location, mapset, mapset_path

+ 1 - 1
python/grass/grassdb/Makefile

@@ -5,7 +5,7 @@ include $(MODULE_TOPDIR)/include/Make/Python.make
 
 DSTDIR = $(ETC)/python/grass/grassdb
 
-MODULES = checks create data manage
+MODULES = checks create data manage config
 
 PYFILES := $(patsubst %,$(DSTDIR)/%.py,$(MODULES) __init__)
 PYCFILES := $(patsubst %,$(DSTDIR)/%.pyc,$(MODULES) __init__)

+ 50 - 3
python/grass/grassdb/checks.py

@@ -9,7 +9,6 @@ for details.
 .. sectionauthor:: Vaclav Petras <wenzeslaus gmail com>
 """
 
-
 import os
 import sys
 import datetime
@@ -18,6 +17,8 @@ from grass.script import gisenv
 import grass.script as gs
 import glob
 
+import grass.grassdb.config as cfg
+
 
 def mapset_exists(database, location, mapset):
     """Returns True whether mapset path exists."""
@@ -115,8 +116,33 @@ def get_mapset_owner(mapset_path):
         return None
 
 
-def is_current_mapset_in_demolocation():
-    return gisenv()["LOCATION_NAME"] == "world_latlong_wgs84"
+def is_fallback_session():
+    """Checks if a user encounters a fallback GRASS session.
+
+    Returns True if a user encounters a fallback session.
+    It occurs when a last mapset is not usable and at the same time
+    a user is in a temporary location.
+    """
+    if "LAST_MAPSET_PATH" in gisenv().keys():
+        return is_mapset_current(
+            os.environ["TMPDIR"], cfg.temporary_location, cfg.permanent_mapset
+        )
+    return False
+
+
+def is_first_time_user():
+    """Check if a user is a first-time user.
+
+    Returns True if a user is a first-time user.
+    It occurs when a gisrc file has initial settings either in last used mapset
+    or in current mapset settings.
+    """
+    genv = gisenv()
+    if "LAST_MAPSET_PATH" in genv.keys():
+        return genv["LAST_MAPSET_PATH"] == os.path.join(
+            os.getcwd(), cfg.unknown_location, cfg.unknown_mapset
+        )
+    return False
 
 
 def is_mapset_locked(mapset_path):
@@ -169,6 +195,27 @@ def can_start_in_mapset(mapset_path, ignore_lock=False):
     return True
 
 
+def get_reason_id_mapset_not_usable(mapset_path):
+    """It finds a reason why mapset is not usable.
+
+    Returns a reason id as a string.
+    If mapset path is None or no reason found, returns None.
+    """
+    # Check whether mapset exists
+    if not os.path.exists(mapset_path):
+        return "non-existent"
+    # Check whether mapset is valid
+    elif not is_mapset_valid(mapset_path):
+        return "invalid"
+    # Check whether mapset is owned by current user
+    elif not is_current_user_mapset_owner(mapset_path):
+        return "different-owner"
+    # Check whether mapset is locked
+    elif is_mapset_locked(mapset_path):
+        return "locked"
+    return None
+
+
 def dir_contains_location(path):
     """Return True if directory *path* contains a valid location"""
     if not os.path.isdir(path):

+ 17 - 0
python/grass/grassdb/config.py

@@ -0,0 +1,17 @@
+"""
+Set global variables for objects in a GRASS GIS Spatial Database
+
+(C) 2020 by 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.
+
+.. sectionauthor:: Linda Kladivova <linda.kladivova seznam cz>
+"""
+
+# global variables
+default_location = "world_latlong_wgs84"
+temporary_location = "tmploc"
+unknown_location = "<UNKNOWN>"
+unknown_mapset = "<UNKNOWN>"
+permanent_mapset = "PERMANENT"

+ 7 - 0
python/grass/grassdb/manage.py

@@ -46,3 +46,10 @@ def rename_mapset(database, location, old_name, new_name):
 def rename_location(database, old_name, new_name):
     """Rename location from *old_name* to *new_name*"""
     os.rename(os.path.join(database, old_name), os.path.join(database, new_name))
+
+
+def split_mapset_path(mapset_path):
+    """Split mapset path to three parts - grassdb, location, mapset"""
+    path, mapset = os.path.split(mapset_path.rstrip(os.sep))
+    grassdb, location = os.path.split(path)
+    return grassdb, location, mapset