Преглед на файлове

wxGUI/datacatalog: Add rename and delete of Location and Mapset in Datacatalog (#771)

Deleting and renaming is disabled for current mapset or location. Deleting is not possible. Renaming creates invalid (intermediate) state which g.mapset can't deal with if the renamed mapset is the current one.

General functions for create, rename, and delete mapset or location in guiutils that uses function from utils. Created LocationDialog and edited MapsetDialog.

Closes #710.

Co-authored-by: Anna Petrasova <kratochanna@gmail.com>
Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
Linda Kladivova преди 4 години
родител
ревизия
6c125e9ba7
променени са 4 файла, в които са добавени 466 реда и са изтрити 252 реда
  1. 139 31
      gui/wxpython/datacatalog/tree.py
  2. 68 181
      gui/wxpython/gis_set.py
  3. 219 14
      gui/wxpython/startup/guiutils.py
  4. 40 26
      gui/wxpython/startup/utils.py

+ 139 - 31
gui/wxpython/datacatalog/tree.py

@@ -34,8 +34,11 @@ from gui_core.treeview import TreeView
 from gui_core.wrap import Menu
 from datacatalog.dialogs import CatalogReprojectionDialog
 from icons.icon import MetaIcon
-from startup.utils import create_mapset, get_default_mapset_name
-from startup.guiutils import NewMapsetDialog
+from startup.guiutils import (create_mapset_interactively,
+                              rename_mapset_interactively,
+                              rename_location_interactively,
+                              delete_mapset_interactively,
+                              delete_location_interactively)
 
 from grass.pydispatch.signal import Signal
 
@@ -662,6 +665,56 @@ class DataCatalogTree(TreeView):
         if new_name:
             self.Rename(old_name, new_name)
 
+    def OnCreateMapset(self, event):
+        """Create new mapset"""
+        gisdbase = self.selected_grassdb[0]
+        location = self.selected_location[0]
+        try:
+            mapset = create_mapset_interactively(self,
+                                        gisdbase.label,
+                                        location.label)
+            if mapset:
+                self.InsertMapset(name=mapset,
+                                  location_node=location)
+                self.ReloadTreeItems()
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to create new mapset: %s") % e,
+                   showTraceback=False)
+
+    def OnRenameMapset(self, event):
+        """
+        Rename selected mapset
+        """
+        try:
+            newmapset = rename_mapset_interactively(
+                    self,
+                    self.selected_grassdb[0].label,
+                    self.selected_location[0].label,
+                    self.selected_mapset[0].label)
+            if newmapset:
+                self.ReloadTreeItems()
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to rename mapset: %s") % e,
+                   showTraceback=False)
+
+    def OnRenameLocation(self, event):
+        """
+        Rename selected location
+        """
+        try:
+            newlocation = rename_location_interactively(
+                    self,
+                    self.selected_grassdb[0].label,
+                    self.selected_location[0].label)
+            if newlocation:
+                self.ReloadTreeItems()
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to rename location: %s") % e,
+                   showTraceback=False)
+
     def OnStartEditLabel(self, node, event):
         """Start label editing"""
         self.DefineItems([node])
@@ -884,6 +937,35 @@ class DataCatalogTree(TreeView):
             self.UnselectAll()
             self.showNotification.emit(message=_("g.remove completed"))
 
+    def OnDeleteMapset(self, event):
+        """
+        Delete selected mapset
+        """
+        try:
+            delete_mapset_interactively(self,
+                                        self.selected_grassdb[0].label,
+                                        self.selected_location[0].label,
+                                        self.selected_mapset[0].label)
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to delete mapset: %s") % e,
+                   showTraceback=False)
+        self.ReloadTreeItems()
+
+    def OnDeleteLocation(self, event):
+        """
+        Delete selected location
+        """
+        try:
+            delete_location_interactively(self,
+                                          self.selected_grassdb[0].label,
+                                          self.selected_location[0].label)
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to delete location: %s") % e,
+                   showTraceback=False)
+        self.ReloadTreeItems()
+
     def OnDisplayLayer(self, event):
         """Display layer in current graphics view"""
         self.DisplayLayer()
@@ -934,9 +1016,17 @@ class DataCatalogTree(TreeView):
             self.OnPasteMap(event)
 
     def OnSwitchDbLocationMapset(self, event):
+        """Switch to location and mapset"""
+        self._SwitchDbLocationMapset()
+
+    def _SwitchDbLocationMapset(self):
+        """Switch to location and mapset"""
         genv = gisenv()
-        if self.selected_grassdb[0].label == genv['GISDBASE'] and \
-           self.selected_location[0].label == genv['LOCATION_NAME']:
+        # Distinguish when only part of db/location/mapset is changed.
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+        ):
             self.changeMapset.emit(mapset=self.selected_mapset[0].label)
         elif self.selected_grassdb[0].label == genv['GISDBASE']:
             self.changeLocation.emit(mapset=self.selected_mapset[0].label,
@@ -950,30 +1040,6 @@ class DataCatalogTree(TreeView):
         self.ExpandCurrentMapset()
         self.RefreshItems()
 
-    def OnCreateMapset(self, event):
-        """Create new mapset"""
-        gisdbase = self.selected_grassdb[0]
-        location = self.selected_location[0]
-
-        dlg = NewMapsetDialog(
-            parent=self,
-            default=get_default_mapset_name(),
-            database=gisdbase.label,
-            location=location.label
-        )
-        if dlg.ShowModal() == wx.ID_OK:
-            mapset = dlg.GetValue()
-            try:
-                create_mapset(gisdbase.label,
-                              location.label,
-                              mapset)
-            except OSError as err:
-                GError(parent=self,
-                       message=_("Unable to create new mapset: %s") % err,
-                       showTraceback=False)
-            self.InsertMapset(name=mapset,
-                              location_node=location)
-
     def OnMetadata(self, event):
         """Show metadata of any raster/vector/3draster"""
         def done(event):
@@ -1144,21 +1210,63 @@ class DataCatalogTree(TreeView):
         item = wx.MenuItem(menu, wx.ID_ANY, _("&Switch mapset"))
         menu.AppendItem(item)
         self.Bind(wx.EVT_MENU, self.OnSwitchDbLocationMapset, item)
-        if (self.selected_grassdb[0].label == genv['GISDBASE'] and
-                self.selected_location[0].label == genv['LOCATION_NAME'] and
-                self.selected_mapset[0].label == genv['MAPSET']):
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+            and self.selected_mapset[0].label == genv['MAPSET']
+        ):
+            item.Enable(False)
+
+        item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete mapset"))
+        menu.AppendItem(item)
+        self.Bind(wx.EVT_MENU, self.OnDeleteMapset, item)
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+            and self.selected_mapset[0].label == genv['MAPSET']
+        ):
             item.Enable(False)
+
+        item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename mapset"))
+        menu.AppendItem(item)
+        self.Bind(wx.EVT_MENU, self.OnRenameMapset, item)
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+            and self.selected_mapset[0].label == genv['MAPSET']
+        ):
+            item.Enable(False)
+
         self.PopupMenu(menu)
         menu.Destroy()
 
     def _popupMenuLocation(self):
         """Create popup menu for locations"""
         menu = Menu()
+        genv = gisenv()
 
         item = wx.MenuItem(menu, wx.ID_ANY, _("&Create mapset"))
         menu.AppendItem(item)
         self.Bind(wx.EVT_MENU, self.OnCreateMapset, item)
 
+        item = wx.MenuItem(menu, wx.ID_ANY, _("&Delete location"))
+        menu.AppendItem(item)
+        self.Bind(wx.EVT_MENU, self.OnDeleteLocation, item)
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+        ):
+            item.Enable(False)
+
+        item = wx.MenuItem(menu, wx.ID_ANY, _("&Rename location"))
+        menu.AppendItem(item)
+        self.Bind(wx.EVT_MENU, self.OnRenameLocation, item)
+        if (
+            self.selected_grassdb[0].label == genv['GISDBASE']
+            and self.selected_location[0].label == genv['LOCATION_NAME']
+        ):
+            item.Enable(False)
+
         self.PopupMenu(menu)
         menu.Destroy()
 

+ 68 - 181
gui/wxpython/gis_set.py

@@ -24,28 +24,28 @@ import os
 import sys
 import copy
 import platform
-import codecs
-import getpass
 
 # i18n is taken care of in the grass library code.
 # So we need to import it before any of the GUI code.
-from grass.script import core as grass
 
 from core import globalvar
 import wx
 import wx.lib.mixins.listctrl as listmix
 
-from core.gcmd import GMessage, GError, DecodeString, RunCommand
+from core.gcmd import GMessage, GError, RunCommand
 from core.utils import GetListOfLocations, GetListOfMapsets
 from startup.utils import (
     get_lockfile_if_present, get_possible_database_path,
-    create_database_directory, create_mapset, get_default_mapset_name)
-import startup.utils as sutils
-from startup.guiutils import SetSessionMapset, NewMapsetDialog
+    create_database_directory)
+from startup.guiutils import (SetSessionMapset,
+                              create_mapset_interactively,
+                              rename_mapset_interactively,
+                              rename_location_interactively,
+                              delete_mapset_interactively,
+                              delete_location_interactively)
 import startup.guiutils as sgui
 from location_wizard.dialogs import RegionDef
-from gui_core.dialogs import TextEntryDialog
-from gui_core.widgets import GenericValidator, StaticWrapText
+from gui_core.widgets import StaticWrapText
 from gui_core.wrap import Button, ListCtrl, StaticText, StaticBox, \
     TextCtrl, BitmapFromImage
 
@@ -233,11 +233,11 @@ class GRASSStartup(wx.Frame):
         self.bmapset.Bind(wx.EVT_BUTTON, self.OnCreateMapset)
         self.bwizard.Bind(wx.EVT_BUTTON, self.OnWizard)
 
-        self.rename_location_button.Bind(wx.EVT_BUTTON, self.RenameLocation)
-        self.delete_location_button.Bind(wx.EVT_BUTTON, self.DeleteLocation)
-        self.download_location_button.Bind(wx.EVT_BUTTON, self.DownloadLocation)
-        self.rename_mapset_button.Bind(wx.EVT_BUTTON, self.RenameMapset)
-        self.delete_mapset_button.Bind(wx.EVT_BUTTON, self.DeleteMapset)
+        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)
@@ -606,165 +606,74 @@ class GRASSStartup(wx.Frame):
                     'name': filePath},
                 parent=self)
 
+
     # the event can be refactored out by using lambda in bind
-    def RenameMapset(self, event):
+    def OnRenameMapset(self, event):
         """Rename selected mapset
         """
         location = self.listOfLocations[self.lblocations.GetSelection()]
         mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
-        if mapset == 'PERMANENT':
-            GMessage(
-                parent=self, message=_(
-                    'Mapset <PERMANENT> is required for valid GRASS location.\n\n'
-                    'This mapset cannot be renamed.'))
-            return
-
-        dlg = TextEntryDialog(
-            parent=self,
-            message=_('Current name: %s\n\nEnter new name:') %
-            mapset,
-            caption=_('Rename selected mapset'),
-            validator=GenericValidator(
-                grass.legal_name,
-                self._nameValidationFailed))
-
-        if dlg.ShowModal() == wx.ID_OK:
-            newmapset = dlg.GetValue()
-            if newmapset == mapset:
-                dlg.Destroy()
-                return
-
-            if newmapset in self.listOfMapsets:
-                wx.MessageBox(
-                    parent=self, caption=_('Message'), message=_(
-                        'Unable to rename mapset.\n\n'
-                        'Mapset <%s> already exists in location.') %
-                    newmapset, style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
-            else:
-                try:
-                    sutils.rename_mapset(self.gisdbase, location,
-                                         mapset, newmapset)
-                    self.OnSelectLocation(None)
-                    self.lbmapsets.SetSelection(
+        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:
-                    wx.MessageBox(
-                        parent=self,
-                        caption=_('Error'),
-                        message=_('Unable to rename mapset.\n\n%s') %
-                        e,
-                        style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
-
-        dlg.Destroy()
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to rename mapset: %s") % e,
+                   showTraceback=False)
 
-    def RenameLocation(self, event):
+    def OnRenameLocation(self, event):
         """Rename selected location
         """
         location = self.listOfLocations[self.lblocations.GetSelection()]
-
-        dlg = TextEntryDialog(
-            parent=self,
-            message=_('Current name: %s\n\nEnter new name:') %
-            location,
-            caption=_('Rename selected location'),
-            validator=GenericValidator(
-                grass.legal_name,
-                self._nameValidationFailed))
-
-        if dlg.ShowModal() == wx.ID_OK:
-            newlocation = dlg.GetValue()
-            if newlocation == location:
-                dlg.Destroy()
-                return
-
-            if newlocation in self.listOfLocations:
-                wx.MessageBox(
-                    parent=self, caption=_('Message'), message=_(
-                        'Unable to rename location.\n\n'
-                        'Location <%s> already exists in GRASS database.') %
-                    newlocation, style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
-            else:
-                try:
-                    sutils.rename_location(self.gisdbase,
-                                           location, newlocation)
-                    self.UpdateLocations(self.gisdbase)
-                    self.lblocations.SetSelection(
+        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:
-                    wx.MessageBox(
-                        parent=self,
-                        caption=_('Error'),
-                        message=_('Unable to rename location.\n\n%s') %
-                        e,
-                        style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
-
-        dlg.Destroy()
+                self.UpdateMapsets(newlocation)
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to rename location: %s") % e,
+                   showTraceback=False)
 
-    def DeleteMapset(self, event):
-        """Delete selected mapset
+    def OnDeleteMapset(self, event):
+        """
+        Delete selected mapset
         """
         location = self.listOfLocations[self.lblocations.GetSelection()]
         mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
-        if mapset == 'PERMANENT':
-            GMessage(
-                parent=self, message=_(
-                    'Mapset <PERMANENT> is required for valid GRASS location.\n\n'
-                    'This mapset cannot be deleted.'))
-            return
-
-        dlg = wx.MessageDialog(
-            parent=self,
-            message=_(
-                "Do you want to continue with deleting mapset <%(mapset)s> "
-                "from location <%(location)s>?\n\n"
-                "ALL MAPS included in this mapset will be "
-                "PERMANENTLY DELETED!") %
-            {'mapset': mapset, 'location': location},
-            caption=_("Delete selected mapset"),
-            style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-
-        if dlg.ShowModal() == wx.ID_YES:
-            try:
-                sutils.delete_mapset(self.gisdbase, location, mapset)
-                self.OnSelectLocation(None)
-                self.lbmapsets.SetSelection(0)
-            except:
-                wx.MessageBox(message=_('Unable to delete mapset'))
-
-        dlg.Destroy()
+        try:
+            delete_mapset_interactively(self, self.gisdbase, location, mapset)
+            self.OnSelectLocation(None)
+            self.lbmapsets.SetSelection(0)
+        except Exception as e:
+            GError(parent=self,
+                   message=_("Unable to delete mapset: %s") % e,
+                   showTraceback=False)
 
-    def DeleteLocation(self, event):
+    def OnDeleteLocation(self, event):
         """
         Delete selected location
         """
-
         location = self.listOfLocations[self.lblocations.GetSelection()]
+        try:
+            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)
 
-        dlg = wx.MessageDialog(
-            parent=self,
-            message=_(
-                "Do you want to continue with deleting "
-                "location <%s>?\n\n"
-                "ALL MAPS included in this location will be "
-                "PERMANENTLY DELETED!") %
-            (location),
-            caption=_("Delete selected location"),
-            style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-
-        if dlg.ShowModal() == wx.ID_YES:
-            try:
-                sutils.delete_location(self.gisdbase, location)
-                self.UpdateLocations(self.gisdbase)
-                self.lblocations.SetSelection(0)
-                self.OnSelectLocation(None)
-                self.lbmapsets.SetSelection(0)
-            except:
-                wx.MessageBox(message=_('Unable to delete location'))
-
-        dlg.Destroy()
-
-    def DownloadLocation(self, event):
+    def OnDownloadLocation(self, event):
         """Download location online"""
         from startup.locdownload import LocationDownloadDialog
 
@@ -946,29 +855,16 @@ class GRASSStartup(wx.Frame):
         """Create new mapset"""
         gisdbase = self.tgisdbase.GetValue()
         location = self.listOfLocations[self.lblocations.GetSelection()]
-
-        dlg = NewMapsetDialog(
-            parent=self,
-            default=get_default_mapset_name(),
-            database=gisdbase,
-            location=location
-        )
-        if dlg.ShowModal() == wx.ID_OK:
-            mapset = dlg.GetValue()
-            try:
-                create_mapset(gisdbase, location, mapset)
+        try:
+            mapset = create_mapset_interactively(self, gisdbase, location)
+            if mapset:
                 self.OnSelectLocation(None)
                 self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset))
                 self.bstart.SetFocus()
-                return True
-            except Exception as e:
-                GError(parent=self,
-                       message=_("Unable to create new mapset: %s") % e,
-                       showTraceback=False)
-                return False
-        else:
-            return False
-
+        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"""
@@ -1042,15 +938,6 @@ class GRASSStartup(wx.Frame):
         event.Skip()
         sys.exit(self.exit_user_requested)
 
-    def _nameValidationFailed(self, ctrl):
-        message = _(
-            "Name <%(name)s> is not a valid name for location or mapset. "
-            "Please use only ASCII characters excluding %(chars)s "
-            "and space.") % {
-            'name': ctrl.GetValue(),
-            'chars': '/"\'@,=*~'}
-        GError(parent=self, message=message, caption=_("Invalid name"))
-
 
 class GListBox(ListCtrl, listmix.ListCtrlAutoWidthMixin):
     """Use wx.ListCtrl instead of wx.ListBox, different style for

+ 219 - 14
gui/wxpython/startup/guiutils.py

@@ -16,13 +16,18 @@ 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 core import globalvar
-from core.gcmd import GError, DecodeString, RunCommand
+from core.gcmd import GError, GMessage, DecodeString, RunCommand
 from gui_core.dialogs import TextEntryDialog
 from gui_core.widgets import GenericMultiValidator
+from startup.utils import (create_mapset, delete_mapset, delete_location,
+                           rename_mapset, rename_location, mapset_exists,
+                           location_exists, get_default_mapset_name)
 
 
 def SetSessionMapset(database, location, mapset):
@@ -32,8 +37,8 @@ def SetSessionMapset(database, location, mapset):
     RunCommand("g.gisenv", set="MAPSET=%s" % mapset)
 
 
-class NewMapsetDialog(TextEntryDialog):
-    def __init__(self, parent=None, default=None,
+class MapsetDialog(TextEntryDialog):
+    def __init__(self, parent=None, default=None, message=None, caption=None,
                  database=None, location=None):
         self.database = database
         self.location = location
@@ -46,8 +51,8 @@ class NewMapsetDialog(TextEntryDialog):
 
         TextEntryDialog.__init__(
             self, parent=parent,
-            message=_("Name for the new mapset:"),
-            caption=_("Create new mapset"),
+            message=message,
+            caption=caption,
             defaultValue=default,
             validator=validator,
         )
@@ -81,9 +86,49 @@ class NewMapsetDialog(TextEntryDialog):
 
     def _mapsetAlreadyExists(self, ctrl):
         message = _(
-            "Mapset '{}' already exists. Please consider to use "
+            "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 mapset path"))
+        GError(parent=self, message=message,
+               caption=_("Existing location path"))
 
 
 # TODO: similar to (but not the same as) read_gisrc function in grass.py
@@ -137,10 +182,170 @@ def GetVersion():
     return (grassVersion, grassRevisionStr)
 
 
-def mapset_exists(database, location, mapset):
-    """Returns True whether mapset path exists."""
-    location_path = os.path.join(database, location)
-    mapset_path = os.path.join(location_path, mapset)
-    if os.path.exists(mapset_path):
-        return True
-    return False
+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,
+    )
+
+    if dlg.ShowModal() == wx.ID_OK:
+        mapset = dlg.GetValue()
+        try:
+            create_mapset(grassdb, location, mapset)
+        except OSError as err:
+            GError(
+                parent=guiparent,
+                message=_("Unable to create new mapset: %s") % err,
+                showTraceback=False,
+            )
+    else:
+        mapset = None
+    dlg.Destroy()
+    return mapset
+
+
+def rename_mapset_interactively(guiparent, grassdb, location, mapset):
+    """
+    Rename selected mapset
+    """
+    if mapset == "PERMANENT":
+        GMessage(
+            parent=guiparent,
+            message=_(
+                "Mapset <PERMANENT> is required for valid GRASS location.\n\n"
+                "This mapset cannot be renamed."
+            ),
+        )
+        return
+    dlg = MapsetDialog(
+        parent=guiparent,
+        default=mapset,
+        message=_("Current name: %s\n\nEnter new name:") % 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:
+            wx.MessageBox(
+                parent=guiparent,
+                caption=_("Error"),
+                message=_("Unable to rename mapset.\n\n%s") % err,
+                style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
+            )
+    else:
+        newmapset = None
+    dlg.Destroy()
+    return newmapset
+
+
+def rename_location_interactively(guiparent, grassdb, location):
+    """
+    Rename selected location
+    """
+    dlg = LocationDialog(
+        parent=guiparent,
+        default=location,
+        message=_("Current name: %s\n\nEnter new name:") % 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:
+            wx.MessageBox(
+                parent=guiparent,
+                caption=_("Error"),
+                message=_("Unable to rename location.\n\n%s") % err,
+                style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
+            )
+    else:
+        newlocation = None
+    dlg.Destroy()
+    return newlocation
+
+
+def delete_mapset_interactively(guiparent, grassdb, location, mapset):
+    """
+    Delete selected mapset
+    """
+    if mapset == "PERMANENT":
+        GMessage(
+            parent=guiparent,
+            message=_(
+                "Mapset <PERMANENT> is required for valid GRASS location.\n\n"
+                "This mapset cannot be deleted."
+            ),
+        )
+        return
+
+    dlg = wx.MessageDialog(
+        parent=guiparent,
+        message=_(
+            "Do you want to continue with deleting mapset <%(mapset)s> "
+            "from location <%(location)s>?\n\n"
+            "ALL MAPS included in this mapset will be "
+            "PERMANENTLY DELETED!"
+        )
+        % {"mapset": mapset, "location": location},
+        caption=_("Delete selected mapset"),
+        style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
+    )
+
+    if dlg.ShowModal() == wx.ID_YES:
+        try:
+            delete_mapset(grassdb, location, mapset)
+        except OSError as err:
+            wx.MessageBox(
+                parent=guiparent,
+                caption=_("Error"),
+                message=_("Unable to delete mapset.\n\n%s") % err,
+                style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
+            )
+
+    dlg.Destroy()
+
+
+def delete_location_interactively(guiparent, grassdb, location):
+    """
+    Delete selected location
+    """
+    dlg = wx.MessageDialog(
+        parent=guiparent,
+        message=_(
+            "Do you want to continue with deleting "
+            "location <%s>?\n\n"
+            "ALL MAPS included in this location will be "
+            "PERMANENTLY DELETED!"
+        )
+        % (location),
+        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)
+        except OSError as err:
+            wx.MessageBox(
+                parent=guiparent,
+                caption=_("Error"),
+                message=_("Unable to delete location.\n\n%s") % err,
+                style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
+            )
+
+    dlg.Destroy()

+ 40 - 26
gui/wxpython/startup/utils.py

@@ -24,12 +24,12 @@ import sys
 
 
 def get_possible_database_path():
-    """Looks for directory 'grassdata' (case-insensitive) in standard 
+    """Looks for directory 'grassdata' (case-insensitive) in standard
     locations to detect existing GRASS Database.
-    
+
     Returns the path as a string or None if nothing was found.
     """
-    home = os.path.expanduser('~')
+    home = os.path.expanduser("~")
 
     # try some common directories for grassdata
     candidates = [
@@ -41,23 +41,22 @@ def get_possible_database_path():
     for candidate in candidates:
         if os.path.exists(candidate):
             for subdir in next(os.walk(candidate))[1]:
-                if 'grassdata' in subdir.lower():
-                    return os.path.join(candidate,subdir)
+                if "grassdata" in subdir.lower():
+                    return os.path.join(candidate, subdir)
     return None
 
 
 def create_database_directory():
     """Creates the standard GRASS GIS directory.
-
-    Creates database directory named grassdata in the standard location 
+    Creates database directory named grassdata in the standard location
     according to the platform.
 
     Returns the new path as a string or None if nothing was found or created.
     """
-    home = os.path.expanduser('~')
+    home = os.path.expanduser("~")
 
     # Determine the standard path according to the platform
-    if sys.platform == 'win32':
+    if sys.platform == "win32":
         path = os.path.join(home, "Documents", "grassdata")
     else:
         path = os.path.join(home, "grassdata")
@@ -73,10 +72,7 @@ def create_database_directory():
     # in some special environment and the standard directories
     # cannot be created which might be the case in some "try out GRASS"
     # use cases.
-    path = os.path.join(
-        tempfile.gettempdir(),
-        "grassdata_{}".format(getpass.getuser())
-    )
+    path = os.path.join(tempfile.gettempdir(), "grassdata_{}".format(getpass.getuser()))
 
     # The created tmp is not cleaned by GRASS, so we are relying on
     # the system to do it at some point. The positive outcome is that
@@ -100,7 +96,7 @@ def get_lockfile_if_present(database, location, mapset):
     Returns the path as a string or None if nothing was found, so the
     return value can be used to test if the lock is present.
     """
-    lock_name = '.gislock'
+    lock_name = ".gislock"
     lockfile = os.path.join(database, location, mapset, lock_name)
     if os.path.isfile(lockfile):
         return lockfile
@@ -114,10 +110,10 @@ def create_mapset(database, location, mapset):
     mapset_path = os.path.join(location_path, mapset)
     # create an empty directory
     os.mkdir(mapset_path)
-    # copy DEFAULT_WIND file and its permissions from PERMANENT 
+    # copy DEFAULT_WIND file and its permissions from PERMANENT
     # to WIND in the new mapset
-    region_path1 = os.path.join(location_path, 'PERMANENT', 'DEFAULT_WIND')
-    region_path2 = os.path.join(location_path, mapset, 'WIND')
+    region_path1 = os.path.join(location_path, "PERMANENT", "DEFAULT_WIND")
+    region_path2 = os.path.join(location_path, mapset, "WIND")
     shutil.copy(region_path1, region_path2)
     # set permissions to u+rw,go+r (disabled; why?)
     # os.chmod(os.path.join(database,location,mapset,'WIND'), 0644)
@@ -125,10 +121,11 @@ def create_mapset(database, location, mapset):
 
 def delete_mapset(database, location, mapset):
     """Deletes a specified mapset"""
-    if mapset == 'PERMANENT':
+    if mapset == "PERMANENT":
         # TODO: translatable or not?
-        raise ValueError("Mapset PERMANENT cannot be deleted"
-                         " (whole location can be)")
+        raise ValueError(
+            "Mapset PERMANENT cannot be deleted" " (whole location can be)"
+        )
     shutil.rmtree(os.path.join(database, location, mapset))
 
 
@@ -140,23 +137,40 @@ def delete_location(database, location):
 def rename_mapset(database, location, old_name, new_name):
     """Rename mapset from *old_name* to *new_name*"""
     location_path = os.path.join(database, location)
-    os.rename(os.path.join(location_path, old_name),
-              os.path.join(location_path, new_name))
+    os.rename(
+        os.path.join(location_path, old_name), os.path.join(location_path, 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))
+    os.rename(os.path.join(database, old_name), os.path.join(database, new_name))
+
+
+def mapset_exists(database, location, mapset):
+    """Returns True whether mapset path exists."""
+    location_path = os.path.join(database, location)
+    mapset_path = os.path.join(location_path, mapset)
+    if os.path.exists(mapset_path):
+        return True
+    return False
+
+
+def location_exists(database, location):
+    """Returns True whether location path exists."""
+    location_path = os.path.join(database, location)
+    if os.path.exists(location_path):
+        return True
+    return False
 
 
 def get_default_mapset_name():
     """Returns default name for mapset."""
     try:
         defaultName = getpass.getuser()
-        defaultName.encode('ascii')
+        defaultName.encode("ascii")
     except UnicodeEncodeError:
         # raise error if not ascii (not valid mapset name)
-        defaultName = 'user'
+        defaultName = "user"
 
     return defaultName