소스 검색

wxGUI: Display infobar focused on first-time user (#1078)

When in demolocation (as identified by its name), display first info bar informing a user about db/loc/mapset and CRS.
Assuming, first-time user, but no check done for that.

Shows two paragraphs with a single notification. Positioned on top of the data catalog area (much easier than map window, related to data, default tab, top seems to be a standard, more expected position. Colors a system highlight without alpha because info bar overlays its own widgets.

Create new location and learn more buttons (besides close). The two buttons don't close the info bar for user to be able to go back and review the message again (good for first-time user).

API is concentrated in a new InfoBar class. Data catalog has info bar code separated into a dedicated manager class.

Co-authored-by: Vaclav Petras <wenzeslaus@gmail.com>
Co-authored-by: Anna Petrasova <kratochanna@gmail.com>
Linda Kladivova 4 년 전
부모
커밋
37b39db0b9

+ 2 - 1
gui/wxpython/datacatalog/__init__.py

@@ -2,5 +2,6 @@ all = [
     'catalog',
     'frame',
     'tree',
-    'dialogs'
+    'dialogs',
+    'infomanager'
 ]

+ 22 - 6
gui/wxpython/datacatalog/catalog.py

@@ -21,9 +21,13 @@ import os
 from core.debug import Debug
 from datacatalog.tree import DataCatalogTree
 from datacatalog.toolbars import DataCatalogToolbar
+from gui_core.infobar import InfoBar
+from datacatalog.infomanager import DataCatalogInfoManager
 
 from grass.pydispatch.signal import Signal
 
+from grass.grassdb.checks import is_current_mapset_in_demolocation
+
 
 class DataCatalog(wx.Panel):
     """Data catalog panel"""
@@ -34,6 +38,7 @@ class DataCatalog(wx.Panel):
         self.showNotification = Signal('DataCatalog.showNotification')
         self.parent = parent
         self.baseTitle = title
+        self.giface = giface
         wx.Panel.__init__(self, parent=parent, id=id, **kwargs)
         self.SetName("DataCatalog")
 
@@ -46,24 +51,35 @@ class DataCatalog(wx.Panel):
         self.tree = DataCatalogTree(self, giface=giface)
         self.tree.showNotification.connect(self.showNotification)
 
+        # infobar for data catalog
+        self.infoBar = InfoBar(self)
+
+        # infobar manager for data catalog
+        self.infoManager = DataCatalogInfoManager(infobar=self.infoBar,
+                                                  giface=self.giface)
         # 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)
+
     def _layout(self):
         """Do layout"""
         sizer = wx.BoxSizer(wx.VERTICAL)
-
-        sizer.Add(self.toolbar, proportion=0,
-                  flag=wx.EXPAND)
-
-        sizer.Add(self.tree.GetControl(), proportion=1,
-                  flag=wx.EXPAND)
+        sizer.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
+        sizer.Add(self.infoBar, proportion=0, flag=wx.EXPAND)
+        sizer.Add(self.tree.GetControl(), proportion=1, flag=wx.EXPAND)
 
         self.SetAutoLayout(True)
         self.SetSizer(sizer)
+        self.Fit()
 
         self.Layout()
 
+    def showDataStructureInfo(self):
+        self.infoManager.ShowDataStructureInfo(self.OnCreateLocation)
+
     def LoadItems(self):
         self.tree.ReloadTreeItems()
 

+ 47 - 0
gui/wxpython/datacatalog/infomanager.py

@@ -0,0 +1,47 @@
+"""
+@package datacatalog.infomanager
+
+@brief Class for managing info messages
+in Data Catalog
+
+Classes:
+- infomanager::DataCatalogInfoManager
+
+(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.
+
+@author Linda Kladivova
+@author Anna Petrasova <kratochanna gmail.com>
+@author Vaclav Petras <wenzeslaus gmail.com>
+"""
+
+import wx
+
+from grass.script import gisenv
+
+
+class DataCatalogInfoManager:
+    """Manager for all things related to info bar in Data Catalog"""
+    def __init__(self, infobar, giface):
+        self.infoBar = infobar
+        self._giface = giface
+
+    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)]
+        message = _(
+            "GRASS GIS helps you organize your data using Locations (projects) "
+            "which contain Mapsets (subprojects). All data in one Location is "
+            "in the same coordinate reference system (CRS).\n\n"
+            "You are currently in Mapset PERMANENT in default Location {loc} "
+            "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'])
+        self.infoBar.ShowMessage(message, wx.ICON_INFORMATION, buttons)
+
+    def _onLearnMore(self, event):
+        self._giface.Help(entry="grass_database")

+ 1 - 0
gui/wxpython/gui_core/__init__.py

@@ -3,6 +3,7 @@ all = [
     'widgets',
     'preferences',
     'menu',
+    'infobar',
     'dialogs',
     'mapwindow',
     'mapdisp',

+ 121 - 0
gui/wxpython/gui_core/infobar.py

@@ -0,0 +1,121 @@
+"""
+@package gui_core.infobar
+
+@brief Wrapper around wx.InfoBar
+
+Classes:
+- gui_core::InfoBar
+
+(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.
+
+@author Linda Kladivova
+@author Anna Petrasova <kratochanna gmail.com>
+@author Vaclav Petras <wenzeslaus gmail.com>
+"""
+
+import wx
+import wx.aui
+try:
+    import wx.lib.agw.infobar as IB
+except ImportError:
+    import wx.lib.infobar as IB
+
+
+class InfoBar(IB.InfoBar):
+    """A customized and specialized info bar to used by default"""
+    def __init__(self, parent):
+        IB.InfoBar.__init__(self, parent)
+
+        self.button_ids = []
+
+        # some system themes have alpha, remove it
+        self._background_color = wx.SystemSettings.GetColour(
+            wx.SYS_COLOUR_HIGHLIGHT
+        ).Get(False)
+        self._foreground_color = wx.SystemSettings.GetColour(
+            wx.SYS_COLOUR_HIGHLIGHTTEXT
+        ).Get(False)
+        self.SetBackgroundColour(self._background_color)
+        self.SetForegroundColour(self._foreground_color)
+        self._text.SetBackgroundColour(self._background_color)
+        self._text.SetForegroundColour(self._foreground_color)
+        self._button.SetBackgroundColour(self._background_color)
+
+        # LAYOUT
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        self.subSizerText = wx.BoxSizer(wx.HORIZONTAL)
+        self.subSizerButtons = wx.BoxSizer(wx.HORIZONTAL)
+        self.subSizerText.Add(self._icon, wx.SizerFlags().Centre().Border())
+        self.subSizerText.Add(self._text, 1, wx.ALIGN_CENTER_VERTICAL)
+        self.subSizerButtons.AddStretchSpacer()
+        self.subSizerButtons.Add(self._button, wx.SizerFlags().Centre().Border())
+        sizer.Add(self.subSizerText, wx.SizerFlags().Expand())
+        sizer.Add(self.subSizerButtons, wx.SizerFlags().Expand())
+        self.SetSizer(sizer)
+
+    def ShowMessage(self, message, icon, buttons=None):
+        """Show message with buttons (optional).
+        Buttons are list of tuples (label, handler)"""
+        self.Hide()
+        self.RemoveButtons()
+        if buttons:
+            self.SetButtons(buttons)
+        super().ShowMessage(message, icon)
+
+    def AddButton(self, btnid, label):
+        """
+        Adds a button to be shown in the info bar.
+        """
+        sizer = self.GetSizer()
+
+        assert sizer is not None, "Sizer must be created first"
+
+        # user-added buttons replace the standard close button so remove it if we
+        # hadn't done it yet
+        if sizer.Detach(self._button):
+            self._button.Hide()
+
+        button = wx.Button(self, btnid, label)
+        button.SetBackgroundColour(self._background_color)
+        button.SetForegroundColour(self._foreground_color)
+
+        if wx.Platform == '__WXMAC__':
+            # smaller buttons look better in the(narrow)info bar under OS X
+            button.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
+
+        num_items = self.subSizerButtons.GetItemCount()
+        if num_items == 1:
+            self.subSizerButtons.Add(button, wx.SizerFlags().Centre().Border())
+            self.subSizerButtons.Add(self._button, wx.SizerFlags().Centre().Border())
+            self._button.Show()
+        else:
+            self.subSizerButtons.Insert(num_items - 1, button, wx.SizerFlags().Centre().Border())
+
+        if self.IsShown():
+            self.UpdateParent()
+
+    def SetButtons(self, buttons):
+        """
+        Sets buttons for notification.
+        Parameter *buttons* is a list of tuples (button_name, event)
+        """
+        for button_name, evt_handler in buttons:
+            button_id = wx.NewId()
+            self.button_ids.append(button_id)
+            self.AddButton(button_id, button_name)
+            self.Bind(wx.EVT_BUTTON, evt_handler, id=button_id)
+
+    def RemoveButtons(self):
+        """
+        Removes buttons from info bar.
+        """
+        items = self.subSizerButtons.GetChildren()
+        for item in reversed(items):
+            if not item.IsSpacer():
+                window = item.GetWindow()
+                if window.GetId() in self.button_ids:
+                    self.subSizerButtons.Detach(window)
+                    window.Destroy()