فهرست منبع

wxGUI: Add new mapset action to data catalog (#731)

* Created GenericMultiValidator to check for several conditions: 1) legal name 2) mapset 'ogr' should not be created as reserved word and 3) mapset already exists. All validations are done in NewMapsetDialog.
* Simplify code in gis_set.py as most functionality moved to NewMapsetDialog.
Linda Kladivova 4 سال پیش
والد
کامیت
eac40fa170
5فایلهای تغییر یافته به همراه179 افزوده شده و 77 حذف شده
  1. 48 3
      gui/wxpython/datacatalog/tree.py
  2. 18 53
      gui/wxpython/gis_set.py
  3. 45 0
      gui/wxpython/gui_core/widgets.py
  4. 55 20
      gui/wxpython/startup/guiutils.py
  5. 13 1
      gui/wxpython/startup/utils.py

+ 48 - 3
gui/wxpython/datacatalog/tree.py

@@ -15,8 +15,8 @@ for details.
 
 @author Tereza Fiedlerova
 @author Anna Petrasova (kratochanna gmail com)
+@author Linda Kladivova (l.kladivova@seznam.cz)
 """
-import os
 import re
 import copy
 from multiprocessing import Process, Queue, cpu_count
@@ -32,6 +32,8 @@ from core.treemodel import TreeModel, DictNode
 from gui_core.treeview import TreeView
 from gui_core.wrap import Menu
 from datacatalog.dialogs import CatalogReprojectionDialog
+from startup.utils import create_mapset, get_default_mapset_name
+from startup.guiutils import NewMapsetDialog
 
 from grass.pydispatch.signal import Signal
 
@@ -479,10 +481,12 @@ class LocationMapTree(TreeView):
             self._popupMenuEmpty()
         elif self.selected_layer[0]:
             self._popupMenuLayer()
-        elif self.selected_mapset[0] and not self.selected_type[0] and len(self.selected_mapset) == 1:
-            self._popupMenuMapset()
         elif self.selected_type[0] and len(self.selected_type) == 1:
             self._popupMenuElement()
+        elif self.selected_mapset[0] and not self.selected_type[0] and len(self.selected_mapset) == 1:
+            self._popupMenuMapset()
+        elif self.selected_location[0] and not self.selected_mapset[0] and len(self.selected_location) == 1:
+            self._popupMenuLocation()
         else:
             self._popupMenuEmpty()
 
@@ -803,6 +807,13 @@ class DataCatalogTree(LocationMapTree):
             self._model.SortChildren(found_element)
             self.RefreshNode(mapset_node, recursive=True)
 
+    def InsertMapset(self, name, location_node):
+        """Insert mapset into model and refresh tree"""
+        self._model.AppendNode(parent=location_node, label=name,
+                               data=dict(type="mapset", name=name))
+        self._model.SortChildren(location_node)
+        self.RefreshNode(location_node, recursive=True)
+
     def OnDeleteMap(self, event):
         """Delete layer or mapset"""
         names = [self.selected_layer[i].label + '@' + self.selected_mapset[i].label
@@ -895,6 +906,29 @@ class DataCatalogTree(LocationMapTree):
             self.changeLocation.emit(mapset=self.selected_mapset[0].label, location=self.selected_location[0].label)
         self.ExpandCurrentMapset()
 
+    def OnCreateMapset(self, event):
+        """Create new mapset"""
+        gisdbase = gisenv()['GISDBASE']
+
+        dlg = NewMapsetDialog(
+            parent=self,
+            default=get_default_mapset_name(),
+            database=gisdbase,
+            location=self.selected_location[0].label
+        )
+        if dlg.ShowModal() == wx.ID_OK:
+            mapset = dlg.GetValue()
+            try:
+                create_mapset(gisdbase,
+                              self.selected_location[0].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=self.selected_location[0])
+
     def OnMetadata(self, event):
         """Show metadata of any raster/vector/3draster"""
         def done(event):
@@ -1062,6 +1096,17 @@ class DataCatalogTree(LocationMapTree):
         self.PopupMenu(menu)
         menu.Destroy()
 
+    def _popupMenuLocation(self):
+        """Create popup menu for locations"""
+        menu = Menu()
+
+        item = wx.MenuItem(menu, wx.ID_ANY, _("&Create mapset"))
+        menu.AppendItem(item)
+        self.Bind(wx.EVT_MENU, self.OnCreateMapset, item)
+
+        self.PopupMenu(menu)
+        menu.Destroy()
+
     def _popupMenuElement(self):
         """Create popup menu for elements"""
         menu = Menu()

+ 18 - 53
gui/wxpython/gis_set.py

@@ -39,7 +39,7 @@ from core.gcmd import GMessage, GError, DecodeString, RunCommand
 from core.utils import GetListOfLocations, GetListOfMapsets
 from startup.utils import (
     get_lockfile_if_present, get_possible_database_path,
-    create_database_directory, create_mapset)
+    create_database_directory, create_mapset, get_default_mapset_name)
 import startup.utils as sutils
 from startup.guiutils import SetSessionMapset, NewMapsetDialog
 import startup.guiutils as sgui
@@ -944,55 +944,31 @@ class GRASSStartup(wx.Frame):
 
     def OnCreateMapset(self, event):
         """Create new mapset"""
+        gisdbase = self.tgisdbase.GetValue()
+        location = self.listOfLocations[self.lblocations.GetSelection()]
+
         dlg = NewMapsetDialog(
             parent=self,
-            default=self._getDefaultMapsetName(),
-            validation_failed_handler=self._nameValidationFailed,
-            help_hanlder=self.OnHelp,
+            default=get_default_mapset_name(),
+            database=gisdbase,
+            location=location
         )
         if dlg.ShowModal() == wx.ID_OK:
             mapset = dlg.GetValue()
-            return self.CreateNewMapset(mapset=mapset)
+            try:
+                create_mapset(gisdbase, location, 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
 
-    def CreateNewMapset(self, mapset):
-        if mapset in self.listOfMapsets:
-            GMessage(parent=self,
-                     message=_("Mapset <%s> already exists.") % mapset)
-            return False
-
-        if mapset.lower() == 'ogr':
-            dlg1 = wx.MessageDialog(
-                parent=self,
-                message=_(
-                    "Mapset <%s> is reserved for direct "
-                    "read access to OGR layers. Please consider to use "
-                    "another name for your mapset.\n\n"
-                    "Are you really sure that you want to create this mapset?") %
-                mapset,
-                caption=_("Reserved mapset name"),
-                style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-            ret = dlg1.ShowModal()
-            dlg1.Destroy()
-            if ret == wx.ID_NO:
-                dlg1.Destroy()
-                return False
-
-        try:
-            self.gisdbase = self.tgisdbase.GetValue()
-            location = self.listOfLocations[self.lblocations.GetSelection()]
-            create_mapset(self.gisdbase, location, 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
 
     def OnStart(self, event):
         """'Start GRASS' button clicked"""
@@ -1046,17 +1022,6 @@ class GRASSStartup(wx.Frame):
     def SetLocation(self, dbase, location, mapset):
         SetSessionMapset(dbase, location, mapset)
 
-    def _getDefaultMapsetName(self):
-        """Returns default name for mapset."""
-        try:
-            defaultName = getpass.getuser()
-            # raise error if not ascii (not valid mapset name)
-            defaultName.encode('ascii')
-        except:  # whatever might go wrong
-            defaultName = 'user'
-
-        return defaultName
-
     def ExitSuccessfully(self):
         self.Destroy()
         sys.exit(self.exit_success)

+ 45 - 0
gui/wxpython/gui_core/widgets.py

@@ -20,6 +20,7 @@ Classes:
  - widgets::NTCValidator
  - widgets::SimpleValidator
  - widgets::GenericValidator
+ - widgets::GenericMultiValidator
  - widgets::GListCtrl
  - widgets::SearchModuleWidget
  - widgets::ManageSettingsWidget
@@ -864,6 +865,50 @@ class MapValidator(GenericValidator):
                                   _mapNameValidationFailed)
 
 
+class GenericMultiValidator(Validator):
+    """This validator checks conditions and calls callbacks
+    in case the condition is not fulfilled.
+    """
+
+    def __init__(self, checks):
+        """Standard constructor.
+
+        :param checks: list of tuples consisting of conditions (list of
+        functions which accepts string value and returns T/F) and callbacks (
+        list of functions which is called when condition is not fulfilled)
+        """
+        Validator.__init__(self)
+        self._checks = checks
+
+    def Clone(self):
+        """Standard cloner.
+
+        Note that every validator must implement the Clone() method.
+        """
+        return GenericMultiValidator(self._checks)
+
+    def Validate(self, win):
+        """Validate the contents of the given text control.
+        """
+        ctrl = self.GetWindow()
+        text = ctrl.GetValue()
+        for condition, callback in self._checks:
+            if not condition(text):
+                callback(ctrl)
+                return False
+        return True
+
+    def TransferToWindow(self):
+        """Transfer data from validator to window.
+        """
+        return True  # Prevent wxDialog from complaining.
+
+    def TransferFromWindow(self):
+        """Transfer data from window to validator.
+        """
+        return True  # Prevent wxDialog from complaining.
+
+
 class SingleSymbolPanel(wx.Panel):
     """Panel for displaying one symbol.
 

+ 55 - 20
gui/wxpython/startup/guiutils.py

@@ -10,20 +10,18 @@ This program is free software under the GNU General Public License
 
 @author Vaclav Petras <wenzeslaus gmail com>
 
-This is for code which depend on something from GUI (wx or wxGUI). 
+This is for code which depend on something from GUI (wx or wxGUI).
 """
 
 
 import os
 
-import wx
-
 import grass.script as gs
 
 from core import globalvar
-from core.gcmd import DecodeString, RunCommand
+from core.gcmd import GError, DecodeString, RunCommand
 from gui_core.dialogs import TextEntryDialog
-from gui_core.widgets import GenericValidator
+from gui_core.widgets import GenericMultiValidator
 
 
 def SetSessionMapset(database, location, mapset):
@@ -33,30 +31,58 @@ def SetSessionMapset(database, location, mapset):
     RunCommand("g.gisenv", set="MAPSET=%s" % mapset)
 
 
-
 class NewMapsetDialog(TextEntryDialog):
     def __init__(self, parent=None, default=None,
-                 validation_failed_handler=None, help_hanlder=None):
-        if help_hanlder:
-            style = wx.OK | wx.CANCEL | wx.HELP
-        else:
-            style = wx.OK | wx.CANCEL
-        if validation_failed_handler:
-            validator=GenericValidator(
-                gs.legal_name, validation_failed_handler)
-        else:
-            validator = None
+                 database=None, location=None):
+        self.database = database
+        self.location = location
+
+        # list of tuples consisting of conditions and callbacks
+        checks = [(gs.legal_name, self._nameValidationFailed),
+                  (self._checkMapsetNotExists, self._mapsetAlreadyExists),
+                  (self._checkOGR, self._reservedMapsetName)]
+        validator = GenericMultiValidator(checks)
+
         TextEntryDialog.__init__(
             self, parent=parent,
             message=_("Name for the new mapset:"),
             caption=_("Create new mapset"),
             defaultValue=default,
             validator=validator,
-            style=style
         )
-        if help_hanlder:
-            help_button = self.FindWindowById(wx.ID_HELP)
-            help_button.Bind(wx.EVT_BUTTON, help_hanlder)
+
+    def _nameValidationFailed(self, ctrl):
+        message = _(
+            "Name '{}' is not a valid name for location or mapset. "
+            "Please use only ASCII characters excluding characters {} "
+            "and space.").format(ctrl.GetValue(), '/"\'@,=*~')
+        GError(parent=self, message=message, caption=_("Invalid name"))
+
+    def _checkOGR(self, text):
+        """Check user's input for reserved mapset name."""
+        if text.lower() == 'ogr':
+            return False
+        return True
+
+    def _reservedMapsetName(self, ctrl):
+        message = _(
+            "Name '{}' is reserved for direct "
+            "read access to OGR layers. Please use "
+            "another name for your mapset.").format(ctrl.GetValue())
+        GError(parent=self, message=message,
+               caption=_("Reserved mapset name"))
+
+    def _checkMapsetNotExists(self, text):
+        """Check whether user's input mapset exists or not."""
+        if mapset_exists(self.database, self.location, text):
+            return False
+        return True
+
+    def _mapsetAlreadyExists(self, ctrl):
+        message = _(
+            "Mapset '{}' already exists. Please consider to use "
+            "another name for your location.").format(ctrl.GetValue())
+        GError(parent=self, message=message, caption=_("Existing mapset path"))
 
 
 # TODO: similar to (but not the same as) read_gisrc function in grass.py
@@ -108,3 +134,12 @@ def GetVersion():
         grassVersion = versionLine
         grassRevisionStr = ''
     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

+ 13 - 1
gui/wxpython/startup/utils.py

@@ -93,6 +93,7 @@ def create_database_directory():
 
     return None
 
+
 def get_lockfile_if_present(database, location, mapset):
     """Return path to lock if present, None otherwise
 
@@ -136,7 +137,6 @@ def delete_location(database, location):
     shutil.rmtree(os.path.join(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)
@@ -148,3 +148,15 @@ 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 get_default_mapset_name():
+    """Returns default name for mapset."""
+    try:
+        defaultName = getpass.getuser()
+        defaultName.encode('ascii')
+    except UnicodeEncodeError:
+        # raise error if not ascii (not valid mapset name)
+        defaultName = 'user'
+
+    return defaultName