ソースを参照

wxGUI refactoring: New WorkspaceManager class (#1437)

Linda Kladivova 4 年 前
コミット
a6d76822cc

+ 3 - 0
gui/wxpython/core/giface.py

@@ -239,6 +239,9 @@ class StandaloneGrassInterface(GrassInterface):
         # Signal emitted to request updating of map
         # Signal emitted to request updating of map
         self.updateMap = Signal('StandaloneGrassInterface.updateMap')
         self.updateMap = Signal('StandaloneGrassInterface.updateMap')
 
 
+        # Signal emitted when workspace is changed
+        self.workspaceChanged = Signal('StandaloneGrassInterface.workspaceChanged')
+
         # workaround, standalone grass interface should be moved to sep. file
         # workaround, standalone grass interface should be moved to sep. file
         from core.gconsole import GConsole, \
         from core.gconsole import GConsole, \
             EVT_CMD_OUTPUT, EVT_CMD_PROGRESS
             EVT_CMD_OUTPUT, EVT_CMD_PROGRESS

+ 24 - 486
gui/wxpython/lmgr/frame.py

@@ -20,14 +20,9 @@ This program is free software under the GNU General Public License
 
 
 import sys
 import sys
 import os
 import os
-import tempfile
 import stat
 import stat
 import platform
 import platform
 import re
 import re
-try:
-    import xml.etree.ElementTree as etree
-except ImportError:
-    import elementtree.ElementTree as etree  # Python <= 2.4
 
 
 from core import globalvar
 from core import globalvar
 import wx
 import wx
@@ -53,7 +48,6 @@ from gui_core.preferences import MapsetAccess, PreferencesDialog
 from lmgr.layertree import LayerTree, LMIcons
 from lmgr.layertree import LayerTree, LMIcons
 from lmgr.menudata import LayerManagerMenuData, LayerManagerModuleTree
 from lmgr.menudata import LayerManagerMenuData, LayerManagerModuleTree
 from gui_core.widgets import GNotebook, FormNotebook
 from gui_core.widgets import GNotebook, FormNotebook
-from core.workspace import ProcessWorkspaceFile, ProcessGrcFile, WriteWorkspaceFile
 from core.gconsole import GConsole, EVT_IGNORED_CMD_RUN
 from core.gconsole import GConsole, EVT_IGNORED_CMD_RUN
 from core.giface import Notification
 from core.giface import Notification
 from gui_core.goutput import GConsoleWindow, GC_PROMPT
 from gui_core.goutput import GConsoleWindow, GC_PROMPT
@@ -63,6 +57,7 @@ from gui_core.menu import Menu as GMenu
 from core.debug import Debug
 from core.debug import Debug
 from lmgr.toolbars import LMWorkspaceToolbar, LMToolsToolbar
 from lmgr.toolbars import LMWorkspaceToolbar, LMToolsToolbar
 from lmgr.toolbars import LMMiscToolbar, LMNvizToolbar, DisplayPanelToolbar
 from lmgr.toolbars import LMMiscToolbar, LMNvizToolbar, DisplayPanelToolbar
+from lmgr.workspace import WorkspaceManager
 from lmgr.pyshell import PyShellWindow
 from lmgr.pyshell import PyShellWindow
 from lmgr.giface import LayerManagerGrassInterface
 from lmgr.giface import LayerManagerGrassInterface
 from datacatalog.catalog import DataCatalog
 from datacatalog.catalog import DataCatalog
@@ -97,14 +92,16 @@ class GMFrame(wx.Frame):
         self.displayIndex = 0          # index value for map displays and layer trees
         self.displayIndex = 0          # index value for map displays and layer trees
         self.currentPage = None       # currently selected page for layer tree notebook
         self.currentPage = None       # currently selected page for layer tree notebook
         self.currentPageNum = None       # currently selected page number for layer tree notebook
         self.currentPageNum = None       # currently selected page number for layer tree notebook
-        self.workspaceFile = workspace    # workspace file
-        self.workspaceChanged = False     # track changes in workspace
-        # if we are currently loading workspace to ignore some events
-        self.loadingWorkspace = False
         self.cwdPath = None               # current working directory
         self.cwdPath = None               # current working directory
 
 
         wx.Frame.__init__(self, parent=parent, id=id, size=size,
         wx.Frame.__init__(self, parent=parent, id=id, size=size,
                           style=style, **kwargs)
                           style=style, **kwargs)
+
+        self._giface = LayerManagerGrassInterface(self)
+
+        # workspace manager
+        self.workspace_manager = WorkspaceManager(lmgr=self,
+                                                  giface=self._giface)
         self._setTitle()
         self._setTitle()
         self.SetName("LayerManager")
         self.SetName("LayerManager")
 
 
@@ -115,8 +112,6 @@ class GMFrame(wx.Frame):
                     'grass.ico'),
                     'grass.ico'),
                 wx.BITMAP_TYPE_ICO))
                 wx.BITMAP_TYPE_ICO))
 
 
-        self._giface = LayerManagerGrassInterface(self)
-
         menu_errors = []
         menu_errors = []
 
 
         def add_menu_error(message):
         def add_menu_error(message):
@@ -218,12 +213,9 @@ class GMFrame(wx.Frame):
         self.Show()
         self.Show()
 
 
         # load workspace file if requested
         # load workspace file if requested
-        if self.workspaceFile:
-            # load given workspace file
-            if self.LoadWorkspaceFile(self.workspaceFile):
+        if workspace:
+            if self.workspace_manager.Load(workspace):
                 self._setTitle()
                 self._setTitle()
-            else:
-                self.workspaceFile = None
         else:
         else:
             # start default initial display
             # start default initial display
             self.NewDisplay(show=False)
             self.NewDisplay(show=False)
@@ -239,8 +231,6 @@ class GMFrame(wx.Frame):
         # fix goutput's pane size (required for Mac OSX)`
         # fix goutput's pane size (required for Mac OSX)`
         self.goutput.SetSashPosition(int(self.GetSize()[1] * .8))
         self.goutput.SetSashPosition(int(self.GetSize()[1] * .8))
 
 
-        self.workspaceChanged = False
-
         show_menu_errors(menu_errors)
         show_menu_errors(menu_errors)
 
 
         # start with layer manager on top
         # start with layer manager on top
@@ -255,8 +245,8 @@ class GMFrame(wx.Frame):
         gisenv = grass.gisenv()
         gisenv = grass.gisenv()
         location = gisenv["LOCATION_NAME"]
         location = gisenv["LOCATION_NAME"]
         mapset = gisenv["MAPSET"]
         mapset = gisenv["MAPSET"]
-        if self.workspaceFile:
-            filename = os.path.splitext(os.path.basename(self.workspaceFile))[0]
+        if self.workspace_manager.workspaceFile:
+            filename = os.path.splitext(os.path.basename(self.workspace_manager.workspaceFile))[0]
             self.SetTitle(
             self.SetTitle(
                 "{workspace} - {location}/{mapset} - {program}".format(
                 "{workspace} - {location}/{mapset} - {program}".format(
                     location=location,
                     location=location,
@@ -514,14 +504,6 @@ class GMFrame(wx.Frame):
             self._auimgr.GetPane(toolbar).Row(1).Position(pos)
             self._auimgr.GetPane(toolbar).Row(1).Position(pos)
         self._auimgr.Update()
         self._auimgr.Update()
 
 
-    def WorkspaceChanged(self):
-        """Update window title"""
-        if not self.workspaceChanged:
-            self.workspaceChanged = True
-
-        if self.workspaceFile:
-            self._setTitle()
-
     def OnLocationWizard(self, event):
     def OnLocationWizard(self, event):
         """Launch location wizard"""
         """Launch location wizard"""
         gisenv = grass.gisenv()
         gisenv = grass.gisenv()
@@ -682,7 +664,7 @@ class GMFrame(wx.Frame):
         # save changes in the workspace
         # save changes in the workspace
         name = self.notebookLayers.GetPageText(event.GetSelection())
         name = self.notebookLayers.GetPageText(event.GetSelection())
         caption = _("Close Map Display {}").format(name)
         caption = _("Close Map Display {}").format(name)
-        if not self.CanClosePage(caption):
+        if not self.workspace_manager.CanClosePage(caption):
             event.Veto()
             event.Veto()
             return
             return
 
 
@@ -702,37 +684,6 @@ class GMFrame(wx.Frame):
         self.notebookLayers.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CLOSING,
         self.notebookLayers.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CLOSING,
                                  self.OnCBPageClosing)
                                  self.OnCBPageClosing)
 
 
-    def CanClosePage(self, caption):
-        """Ask if page with map display(s) can be closed
-        """
-        # save changes in the workspace
-        maptree = self.GetLayerTree()
-        if  self.workspaceChanged and UserSettings.Get(
-                group='manager', key='askOnQuit', subkey='enabled'):
-            if self.workspaceFile:
-                message = _("Do you want to save changes in the workspace?")
-            else:
-                message = _("Do you want to store current settings "
-                            "to workspace file?")
-
-            # ask user to save current settings
-            if maptree.GetCount() > 0:
-                dlg = wx.MessageDialog(self,
-                                       message=message,
-                                       caption=caption,
-                                       style=wx.YES_NO | wx.YES_DEFAULT |
-                                       wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
-                ret = dlg.ShowModal()
-                dlg.Destroy()
-                if ret == wx.ID_YES:
-                    if not self.workspaceFile:
-                        self.OnWorkspaceSaveAs()
-                    else:
-                        self.SaveToWorkspaceFile(self.workspaceFile)
-                elif ret == wx.ID_CANCEL:
-                    return False
-        return True
-
     def _switchPageHandler(self, event, notification):
     def _switchPageHandler(self, event, notification):
         self._switchPage(notification=notification)
         self._switchPage(notification=notification)
         event.Skip()
         event.Skip()
@@ -1315,437 +1266,24 @@ class GMFrame(wx.Frame):
         menu.Destroy()
         menu.Destroy()
 
 
     def OnWorkspaceNew(self, event=None):
     def OnWorkspaceNew(self, event=None):
-        """Create new workspace file
-
-        Erase current workspace settings first
-        """
-        Debug.msg(4, "GMFrame.OnWorkspaceNew():")
-
-        # start new map display if no display is available
-        if not self.currentPage:
-            self.NewDisplay()
-
-        maptrees = [self.notebookLayers.GetPage(i).maptree for i in range(self.notebookLayers.GetPageCount())]
-
-        # ask user to save current settings
-        if self.workspaceFile and self.workspaceChanged:
-            self.OnWorkspaceSave()
-        elif self.workspaceFile is None and any(tree.GetCount() for tree in maptrees):
-            dlg = wx.MessageDialog(
-                self,
-                message=_(
-                    "Current workspace is not empty. "
-                    "Do you want to store current settings "
-                    "to workspace file?"),
-                caption=_("Create new workspace?"),
-                style=wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION)
-            ret = dlg.ShowModal()
-            if ret == wx.ID_YES:
-                self.OnWorkspaceSaveAs()
-            elif ret == wx.ID_CANCEL:
-                dlg.Destroy()
-                return
-
-            dlg.Destroy()
-
-        # delete all layers in map displays
-        for maptree in maptrees:
-            maptree.DeleteAllLayers()
-
-        # delete all decorations
-        for display in self.GetAllMapDisplays():
-            for overlayId in display.decorations.keys():
-                display.RemoveOverlay(overlayId)
-
-        # no workspace file loaded
-        self.workspaceFile = None
-        self.workspaceChanged = False
-        self._setTitle()
+        """Create new workspace file"""
+        self.workspace_manager.New()
 
 
     def OnWorkspaceOpen(self, event=None):
     def OnWorkspaceOpen(self, event=None):
         """Open file with workspace definition"""
         """Open file with workspace definition"""
-        dlg = wx.FileDialog(
-            parent=self,
-            message=_("Choose workspace file"),
-            defaultDir=os.getcwd(),
-            wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"))
-
-        filename = ''
-        if dlg.ShowModal() == wx.ID_OK:
-            filename = dlg.GetPath()
-
-        if filename == '':
-            return
-
-        Debug.msg(4, "GMFrame.OnWorkspaceOpen(): filename=%s" % filename)
-
-        # delete current layer tree content
-        self.OnWorkspaceClose()
-        self.loadingWorkspace = True
-        self.LoadWorkspaceFile(filename)
-        self.loadingWorkspace = False
-
-        self.workspaceFile = filename
-        self._setTitle()
-
-    def _tryToSwitchMapsetFromWorkspaceFile(self, gxwXml):
-        returncode, errors = RunCommand('g.mapset',
-                      dbase=gxwXml.database,
-                      location=gxwXml.location,
-                      mapset=gxwXml.mapset,
-                      getErrorMsg=True,
-                                        )
-        if returncode != 0:
-            # TODO: use the function from grass.py
-            reason = _("Most likely the database, location or mapset"
-                       " does not exist")
-            details = errors
-            message = _("Unable to change to location and mapset"
-                        " specified in the workspace.\n"
-                        "Reason: {reason}\nDetails: {details}\n\n"
-                        "Do you want to proceed with opening"
-                        " the workspace anyway?"
-                        ).format(**locals())
-            dlg = wx.MessageDialog(
-                parent=self, message=message, caption=_(
-                    "Proceed with opening of the workspace?"),
-                style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-            dlg.CenterOnParent()
-            if dlg.ShowModal() in [wx.ID_NO, wx.ID_CANCEL]:
-                return False
-        else:
-            # TODO: copy from ChangeLocation function
-            GMessage(
-                parent=self,
-                message=_("Current location is <%(loc)s>.\n"
-                          "Current mapset is <%(mapset)s>.") %
-                {'loc': gxwXml.location,
-                           'mapset': gxwXml.mapset})
-        return True
-
-    def LoadWorkspaceFile(self, filename):
-        """Load layer tree definition stored in GRASS Workspace XML file (gxw)
-
-        .. todo::
-            Validate against DTD
-
-        :return: True on success
-        :return: False on error
-        """
-        # parse workspace file
-        try:
-            gxwXml = ProcessWorkspaceFile(etree.parse(filename))
-        except Exception as e:
-            GError(
-                parent=self, message=_(
-                    "Reading workspace file <%s> failed.\n"
-                    "Invalid file, unable to parse XML document.") %
-                filename)
-            return False
-
-        if gxwXml.database and gxwXml.location and gxwXml.mapset:
-            if not self._tryToSwitchMapsetFromWorkspaceFile(gxwXml):
-                return False
-
-        # the really busy part starts here (mapset change is fast)
-        busy = wx.BusyInfo(_("Please wait, loading workspace..."),
-                           parent=self)
-        wx.GetApp().Yield()
-
-        #
-        # load layer manager window properties
-        #
-        if UserSettings.Get(group='general', key='workspace', subkey=[
-                            'posManager', 'enabled']) is False:
-            if gxwXml.layerManager['pos']:
-                self.SetPosition(gxwXml.layerManager['pos'])
-            if gxwXml.layerManager['size']:
-                self.SetSize(gxwXml.layerManager['size'])
-            if gxwXml.layerManager['cwd']:
-                self.cwdPath = gxwXml.layerManager['cwd']
-                if os.path.isdir(self.cwdPath):
-                    os.chdir(self.cwdPath)
-
-        #
-        # start map displays first (list of layers can be empty)
-        #
-        displayId = 0
-        mapdisplay = list()
-        for display in gxwXml.displays:
-            mapdisp = self.NewDisplay(name=display['name'], show=False)
-            mapdisplay.append(mapdisp)
-            maptree = self.notebookLayers.GetPage(displayId).maptree
-
-            # set windows properties
-            mapdisp.SetProperties(render=display['render'],
-                                  mode=display['mode'],
-                                  showCompExtent=display['showCompExtent'],
-                                  alignExtent=display['alignExtent'],
-                                  constrainRes=display['constrainRes'],
-                                  projection=display['projection']['enabled'])
-
-            if display['projection']['enabled']:
-                if display['projection']['epsg']:
-                    UserSettings.Set(
-                        group='display',
-                        key='projection',
-                        subkey='epsg',
-                        value=display['projection']['epsg'])
-                    if display['projection']['proj']:
-                        UserSettings.Set(
-                            group='display',
-                            key='projection',
-                            subkey='proj4',
-                            value=display['projection']['proj'])
-
-            # set position and size of map display
-            if not UserSettings.Get(
-                    group='general', key='workspace',
-                    subkey=['posDisplay', 'enabled']):
-                if display['pos']:
-                    mapdisp.SetPosition(display['pos'])
-                if display['size']:
-                    mapdisp.SetSize(display['size'])
-
-            # set extent if defined
-            if display['extent']:
-                w, s, e, n, b, t = display['extent']
-                region = maptree.Map.region = maptree.Map.GetRegion(
-                    w=w, s=s, e=e, n=n)
-                mapdisp.GetWindow().ResetZoomHistory()
-                mapdisp.GetWindow().ZoomHistory(region['n'],
-                                                region['s'],
-                                                region['e'],
-                                                region['w'])
-            if 'showStatusbar' in display and not display['showStatusbar']:
-                mapdisp.statusbarManager.Show(False)
-            if 'showToolbars' in display and not display['showToolbars']:
-                for toolbar in mapdisp.GetToolbarNames():
-                    mapdisp.RemoveToolbar(toolbar)
-
-            displayId += 1
-            mapdisp.Show()  # show mapdisplay
-            # set render property to False to speed up loading layers
-            mapdisp.mapWindowProperties.autoRender = False
-
-        maptree = None
-        selectList = []  # list of selected layers
-        #
-        # load list of map layers
-        #
-        for layer in gxwXml.layers:
-            display = layer['display']
-            maptree = self.notebookLayers.GetPage(display).maptree
-            newItem = maptree.AddLayer(ltype=layer['type'],
-                                       lname=layer['name'],
-                                       lchecked=layer['checked'],
-                                       lopacity=layer['opacity'],
-                                       lcmd=layer['cmd'],
-                                       lgroup=layer['group'],
-                                       lnviz=layer['nviz'],
-                                       lvdigit=layer['vdigit'],
-                                       loadWorkspace=True)
-
-            if 'selected' in layer:
-                selectList.append((maptree, newItem, layer['selected']))
-
-        for maptree, layer, selected in selectList:
-            if selected:
-                if not layer.IsSelected():
-                    maptree.SelectItem(layer, select=True)
-            else:
-                maptree.SelectItem(layer, select=False)
-
-        del busy
-
-        # set render property again when all layers are loaded
-        for i, display in enumerate(gxwXml.displays):
-            mapdisplay[i].mapWindowProperties.autoRender = display['render']
-
-            for overlay in gxwXml.overlays:
-                # overlay["cmd"][0] name of command e.g. d.barscale, d.legend
-                # overlay["cmd"][1:] parameters and flags
-                if overlay['display'] == i:
-                    if overlay['cmd'][0] == "d.legend.vect":
-                        mapdisplay[i].AddLegendVect(overlay['cmd'])
-                    if overlay['cmd'][0] == "d.legend":
-                        mapdisplay[i].AddLegendRast(overlay['cmd'])
-                    if overlay['cmd'][0] == "d.barscale":
-                        mapdisplay[i].AddBarscale(overlay['cmd'])
-                    if overlay['cmd'][0] == "d.northarrow":
-                        mapdisplay[i].AddArrow(overlay['cmd'])
-                    if overlay['cmd'][0] == "d.text":
-                        mapdisplay[i].AddDtext(overlay['cmd'])
-
-            # avoid double-rendering when loading workspace
-            # mdisp.MapWindow2D.UpdateMap()
-            # nviz
-            if gxwXml.displays[i]['viewMode'] == '3d':
-                mapdisplay[i].AddNviz()
-                self.nviz.UpdateState(view=gxwXml.nviz_state['view'],
-                                      iview=gxwXml.nviz_state['iview'],
-                                      light=gxwXml.nviz_state['light'])
-                mapdisplay[i].MapWindow3D.constants = gxwXml.nviz_state['constants']
-                for idx, constant in enumerate(mapdisplay[i].MapWindow3D.constants):
-                    mapdisplay[i].MapWindow3D.AddConstant(constant, i + 1)
-                for page in ('view', 'light', 'fringe', 'constant', 'cplane'):
-                    self.nviz.UpdatePage(page)
-                self.nviz.UpdateSettings()
-                mapdisplay[i].toolbars['map'].combo.SetSelection(1)
-
-        return True
-
-    def OnWorkspaceLoadGrcFile(self, event):
-        """Load map layers from GRC file (Tcl/Tk GUI) into map layer tree"""
-        dlg = wx.FileDialog(
-            parent=self,
-            message=_("Choose GRC file to load"),
-            defaultDir=os.getcwd(),
-            wildcard=_("Old GRASS Workspace File (*.grc)|*.grc"))
-
-        filename = ''
-        if dlg.ShowModal() == wx.ID_OK:
-            filename = dlg.GetPath()
-
-        if filename == '':
-            return
-
-        Debug.msg(
-            4,
-            "GMFrame.OnWorkspaceLoadGrcFile(): filename=%s" %
-            filename)
-
-        # start new map display if no display is available
-        if not self.currentPage:
-            self.NewDisplay()
-
-        busy = wx.BusyInfo(_("Please wait, loading workspace..."),
-                           parent=self)
-        wx.GetApp().Yield()
-
-        maptree = None
-        for layer in ProcessGrcFile(filename).read(self):
-            maptree = self.notebookLayers.GetPage(layer['display']).maptree
-            newItem = maptree.AddLayer(ltype=layer['type'],
-                                       lname=layer['name'],
-                                       lchecked=layer['checked'],
-                                       lopacity=layer['opacity'],
-                                       lcmd=layer['cmd'],
-                                       lgroup=layer['group'])
-
-        del busy
-
-        if maptree:
-            # reverse list of map layers
-            maptree.Map.ReverseListOfLayers()
-
-    def OnWorkspaceSaveAs(self, event=None):
-        """Save workspace definition to selected file"""
-        dlg = wx.FileDialog(
-            parent=self,
-            message=_("Choose file to save current workspace"),
-            defaultDir=os.getcwd(),
-            wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"),
-            style=wx.FD_SAVE)
-
-        filename = ''
-        if dlg.ShowModal() == wx.ID_OK:
-            filename = dlg.GetPath()
-
-        if filename == '':
-            return False
-
-        # check for extension
-        if filename[-4:] != ".gxw":
-            filename += ".gxw"
-
-        if os.path.exists(filename):
-            dlg = wx.MessageDialog(
-                self,
-                message=_(
-                    "Workspace file <%s> already exists. "
-                    "Do you want to overwrite this file?") %
-                filename,
-                caption=_("Save workspace"),
-                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
-            if dlg.ShowModal() != wx.ID_YES:
-                dlg.Destroy()
-                return False
-
-        Debug.msg(4, "GMFrame.OnWorkspaceSaveAs(): filename=%s" % filename)
-
-        self.SaveToWorkspaceFile(filename)
-        self.workspaceFile = filename
-        self._setTitle()
+        self.workspace_manager.Open()
 
 
     def OnWorkspaceSave(self, event=None):
     def OnWorkspaceSave(self, event=None):
         """Save file with workspace definition"""
         """Save file with workspace definition"""
-        if self.workspaceFile:
-            dlg = wx.MessageDialog(
-                self,
-                message=_(
-                    "Workspace file <%s> already exists. "
-                    "Do you want to overwrite this file?") %
-                self.workspaceFile,
-                caption=_("Save workspace"),
-                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
-            if dlg.ShowModal() == wx.ID_NO:
-                dlg.Destroy()
-            else:
-                Debug.msg(
-                    4, "GMFrame.OnWorkspaceSave(): filename=%s" %
-                    self.workspaceFile)
-                self.SaveToWorkspaceFile(self.workspaceFile)
-                self._setTitle()
-                self.workspaceChanged = False
-        else:
-            self.OnWorkspaceSaveAs()
-
-    def SaveToWorkspaceFile(self, filename):
-        """Save layer tree layout to workspace file
-
-        :return: True on success, False on error
-        """
-        tmpfile = tempfile.TemporaryFile(mode='w+b')
-        try:
-            WriteWorkspaceFile(lmgr=self, file=tmpfile)
-        except Exception as e:
-            GError(parent=self,
-                   message=_("Writing current settings to workspace file "
-                             "failed."))
-            return False
-
-        try:
-            mfile = open(filename, "wb")
-            tmpfile.seek(0)
-            for line in tmpfile.readlines():
-                mfile.write(line)
-        except IOError:
-            GError(
-                parent=self,
-                message=_("Unable to open file <%s> for writing.") %
-                filename)
-            return False
-
-        mfile.close()
+        self.workspace_manager.Save()
 
 
-        return True
+    def OnWorkspaceSaveAs(self, event=None):
+        """Save workspace definition to selected file"""
+        self.workspace_manager.SaveAs()
 
 
     def OnWorkspaceClose(self, event=None):
     def OnWorkspaceClose(self, event=None):
-        """Close file with workspace definition
-
-        If workspace has been modified ask user to save the changes.
-        """
-        Debug.msg(
-            4, "GMFrame.OnWorkspaceClose(): file=%s" %
-            self.workspaceFile)
-
-        self.DisplayCloseAll()
-        self.workspaceFile = None
-        self.workspaceChanged = False
-        self._setTitle()
-        self.displayIndex = 0
-        self.currentPage = None
+        """Close file with workspace definition"""
+        self.workspace_manager.Close()
 
 
     def OnDisplayClose(self, event=None):
     def OnDisplayClose(self, event=None):
         """Close current map display window
         """Close current map display window
@@ -1756,7 +1294,7 @@ class GMFrame(wx.Frame):
     def OnDisplayCloseAll(self, event):
     def OnDisplayCloseAll(self, event):
         """Close all open map display windows (from menu)
         """Close all open map display windows (from menu)
         """
         """
-        if not self.CanClosePage(caption=_("Close all Map Displays")):
+        if not self.workspace_manager.CanClosePage(caption=_("Close all Map Displays")):
             return
             return
         self.DisplayCloseAll()
         self.DisplayCloseAll()
 
 
@@ -2167,7 +1705,7 @@ class GMFrame(wx.Frame):
         # moved from mapdisp/frame.py
         # moved from mapdisp/frame.py
         # TODO: why it is called 3 times when getting focus?
         # TODO: why it is called 3 times when getting focus?
         # and one times when loosing focus?
         # and one times when loosing focus?
-        if self.loadingWorkspace:
+        if self.workspace_manager.loadingWorkspace:
             return
             return
         pgnum = self.notebookLayers.GetPageIndex(notebookLayerPage)
         pgnum = self.notebookLayers.GetPageIndex(notebookLayerPage)
         if pgnum > -1:
         if pgnum > -1:
@@ -2565,7 +2103,7 @@ class GMFrame(wx.Frame):
             self._auimgr.UnInit()
             self._auimgr.UnInit()
             self.Destroy()
             self.Destroy()
             return
             return
-        if not self.CanClosePage(caption=_("Quit GRASS GUI")):
+        if not self.workspace_manager.CanClosePage(caption=_("Quit GRASS GUI")):
             # when called from menu, it gets CommandEvent and not
             # when called from menu, it gets CommandEvent and not
             # CloseEvent
             # CloseEvent
             if hasattr(event, 'Veto'):
             if hasattr(event, 'Veto'):

+ 3 - 0
gui/wxpython/lmgr/giface.py

@@ -204,6 +204,9 @@ class LayerManagerGrassInterface(object):
         # Signal emitted to request updating of map
         # Signal emitted to request updating of map
         self.updateMap = Signal('LayerManagerGrassInterface.updateMap')
         self.updateMap = Signal('LayerManagerGrassInterface.updateMap')
 
 
+        # Signal emitted when workspace is changed
+        self.workspaceChanged = Signal('LayerManagerGrassInterface.workspaceChanged')
+
     def RunCmd(self, *args, **kwargs):
     def RunCmd(self, *args, **kwargs):
         self.lmgr._gconsole.RunCmd(*args, **kwargs)
         self.lmgr._gconsole.RunCmd(*args, **kwargs)
 
 

+ 7 - 5
gui/wxpython/lmgr/layertree.py

@@ -1382,6 +1382,8 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         :param bool multiple: True to allow multiple map layers in layer tree
         :param bool multiple: True to allow multiple map layers in layer tree
         :param bool loadWorkspace: True if called when loading workspace
         :param bool loadWorkspace: True if called when loading workspace
         """
         """
+        self._giface.workspaceChanged.emit()
+
         if lname and not multiple:
         if lname and not multiple:
             # check for duplicates
             # check for duplicates
             item = self.GetFirstChild(self.root)[0]
             item = self.GetFirstChild(self.root)[0]
@@ -1629,7 +1631,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
         """Double click on the layer item.
         """Double click on the layer item.
         Launch property dialog, or expand/collapse group of items, etc.
         Launch property dialog, or expand/collapse group of items, etc.
         """
         """
-        self.lmgr.WorkspaceChanged()
+        self._giface.workspaceChanged.emit()
         layer = event.GetItem()
         layer = event.GetItem()
 
 
         if self.GetLayerInfo(layer, key='type') == 'group':
         if self.GetLayerInfo(layer, key='type') == 'group':
@@ -1643,7 +1645,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
 
 
     def OnDeleteLayer(self, event):
     def OnDeleteLayer(self, event):
         """Remove selected layer item from the layer tree"""
         """Remove selected layer item from the layer tree"""
-        self.lmgr.WorkspaceChanged()
+        self._giface.workspaceChanged.emit()
         item = event.GetItem()
         item = event.GetItem()
 
 
         try:
         try:
@@ -1719,7 +1721,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
                         if mapLayer:
                         if mapLayer:
                             # ignore when map layer is edited
                             # ignore when map layer is edited
                             self.Map.ChangeLayerActive(mapLayer, checked)
                             self.Map.ChangeLayerActive(mapLayer, checked)
-                        self.lmgr.WorkspaceChanged()
+                        self._giface.workspaceChanged.emit()
                     child = self.GetNextSibling(child)
                     child = self.GetNextSibling(child)
             else:
             else:
                 mapLayer = self.GetLayerInfo(item, key='maplayer')
                 mapLayer = self.GetLayerInfo(item, key='maplayer')
@@ -1727,7 +1729,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
                         digitToolbar and digitToolbar.GetLayer() != mapLayer)):
                         digitToolbar and digitToolbar.GetLayer() != mapLayer)):
                     # ignore when map layer is edited
                     # ignore when map layer is edited
                     self.Map.ChangeLayerActive(mapLayer, checked)
                     self.Map.ChangeLayerActive(mapLayer, checked)
-                    self.lmgr.WorkspaceChanged()
+                    self._giface.workspaceChanged.emit()
 
 
         # nviz
         # nviz
         if self.mapdisplay.IsPaneShown('3d') and \
         if self.mapdisplay.IsPaneShown('3d') and \
@@ -1739,7 +1741,7 @@ class LayerTree(treemixin.DragAndDrop, CT.CustomTreeCtrl):
 
 
             self.mapdisplay.SetStatusText(
             self.mapdisplay.SetStatusText(
                 _("Please wait, updating data..."), 0)
                 _("Please wait, updating data..."), 0)
-            self.lmgr.WorkspaceChanged()
+            self._giface.workspaceChanged.emit()
 
 
             if checked:  # enable
             if checked:  # enable
                 if mapLayer.type == 'raster':
                 if mapLayer.type == 'raster':

+ 486 - 0
gui/wxpython/lmgr/workspace.py

@@ -0,0 +1,486 @@
+"""
+@package lmgr::workspace
+
+@brief Workspace manager class for creating, loading and saving workspaces
+
+Class:
+ - lmgr::WorkspaceManager
+
+(C) 2021 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.
+"""
+
+import os
+import tempfile
+
+import xml.etree.ElementTree as etree
+
+import wx
+import wx.aui
+
+from core.settings import UserSettings
+from core.gcmd import RunCommand, GError, GMessage
+from core.workspace import ProcessWorkspaceFile, WriteWorkspaceFile
+from core.debug import Debug
+
+
+class WorkspaceManager:
+    """Workspace Manager for creating, loading and saving workspaces."""
+
+    def __init__(self, lmgr, giface):
+
+        self.lmgr = lmgr
+        self.workspaceFile = None
+        self._giface = giface
+        self.workspaceChanged = False  # track changes in workspace
+        self.loadingWorkspace = False
+
+        Debug.msg(1, "WorkspaceManager.__init__()")
+
+        self._giface.workspaceChanged.connect(self.WorkspaceChanged)
+
+    def WorkspaceChanged(self):
+        "Update window title"
+        self.workspaceChanged = True
+
+    def New(self):
+        """Create new workspace file
+        Erase current workspace settings first
+        """
+        Debug.msg(4, "WorkspaceManager.New():")
+
+        # start new map display if no display is available
+        if not self.lmgr.currentPage:
+            self.lmgr.NewDisplay()
+
+        maptrees = [
+            self.lmgr.notebookLayers.GetPage(i).maptree
+            for i in range(self.lmgr.notebookLayers.GetPageCount())
+        ]
+
+        # ask user to save current settings
+        if self.workspaceFile and self.workspaceChanged:
+            self.Save()
+        elif self.workspaceFile is None and any(tree.GetCount() for tree in maptrees):
+            dlg = wx.MessageDialog(
+                self.lmgr,
+                message=_(
+                    "Current workspace is not empty. "
+                    "Do you want to store current settings "
+                    "to workspace file?"
+                ),
+                caption=_("Create new workspace?"),
+                style=wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION,
+            )
+            ret = dlg.ShowModal()
+            if ret == wx.ID_YES:
+                self.SaveAs()
+            elif ret == wx.ID_CANCEL:
+                dlg.Destroy()
+                return
+
+            dlg.Destroy()
+
+        # delete all layers in map displays
+        for maptree in maptrees:
+            maptree.DeleteAllLayers()
+
+        # delete all decorations
+        for display in self.lmgr.GetAllMapDisplays():
+            for overlayId in display.decorations.keys():
+                display.RemoveOverlay(overlayId)
+
+        self.workspaceFile = None
+        self.workspaceChanged = False
+        self.lmgr._setTitle()
+
+    def Open(self):
+        """Open file with workspace definition"""
+        dlg = wx.FileDialog(
+            parent=self.lmgr,
+            message=_("Choose workspace file"),
+            defaultDir=os.getcwd(),
+            wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"),
+        )
+
+        filename = ""
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+
+        if filename == "":
+            return
+
+        Debug.msg(4, "WorkspaceManager.Open(): filename=%s" % filename)
+
+        # delete current layer tree content
+        self.Close()
+        self.loadingWorkspace = True
+        self.Load(filename)
+        self.loadingWorkspace = False
+        self.lmgr._setTitle()
+
+    def _tryToSwitchMapsetFromWorkspaceFile(self, gxwXml):
+        returncode, errors = RunCommand(
+            "g.mapset",
+            dbase=gxwXml.database,
+            location=gxwXml.location,
+            mapset=gxwXml.mapset,
+            getErrorMsg=True,
+        )
+        if returncode != 0:
+            # TODO: use the function from grass.py
+            reason = _("Most likely the database, location or mapset" " does not exist")
+            details = errors
+            message = _(
+                "Unable to change to location and mapset"
+                " specified in the workspace.\n"
+                "Reason: {reason}\nDetails: {details}\n\n"
+                "Do you want to proceed with opening"
+                " the workspace anyway?"
+            ).format(**locals())
+            dlg = wx.MessageDialog(
+                parent=self.lmgr,
+                message=message,
+                caption=_("Proceed with opening of the workspace?"),
+                style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
+            )
+            dlg.CenterOnParent()
+            if dlg.ShowModal() in [wx.ID_NO, wx.ID_CANCEL]:
+                return False
+        else:
+            # TODO: copy from ChangeLocation function
+            GMessage(
+                parent=self.lmgr,
+                message=_(
+                    "Current location is <%(loc)s>.\n" "Current mapset is <%(mapset)s>."
+                )
+                % {"loc": gxwXml.location, "mapset": gxwXml.mapset},
+            )
+        return True
+
+    def Load(self, filename):
+        """Load layer tree definition stored in GRASS Workspace XML file (gxw)
+        .. todo::
+            Validate against DTD
+        :return: True on success
+        :return: False on error
+        """
+        # parse workspace file
+        try:
+            gxwXml = ProcessWorkspaceFile(etree.parse(filename))
+        except Exception as e:
+            GError(
+                parent=self.lmgr,
+                message=_(
+                    "Reading workspace file <%s> failed.\n"
+                    "Invalid file, unable to parse XML document."
+                )
+                % filename,
+            )
+            return False
+
+        if gxwXml.database and gxwXml.location and gxwXml.mapset:
+            if not self._tryToSwitchMapsetFromWorkspaceFile(gxwXml):
+                return False
+
+        # the really busy part starts here (mapset change is fast)
+        busy = wx.BusyInfo(_("Please wait, loading workspace..."), parent=self.lmgr)
+        wx.GetApp().Yield()
+
+        #
+        # load layer manager window properties
+        #
+        if (
+            UserSettings.Get(
+                group="general", key="workspace", subkey=["posManager", "enabled"]
+            )
+            is False
+        ):
+            if gxwXml.layerManager["pos"]:
+                self.lmgr.SetPosition(gxwXml.layerManager["pos"])
+            if gxwXml.layerManager["size"]:
+                self.lmgr.SetSize(gxwXml.layerManager["size"])
+            if gxwXml.layerManager["cwd"]:
+                self.lmgr.cwdPath = gxwXml.layerManager["cwd"]
+                if os.path.isdir(self.lmgr.cwdPath):
+                    os.chdir(self.lmgr.cwdPath)
+
+        #
+        # start map displays first (list of layers can be empty)
+        #
+        displayId = 0
+        mapdisplay = list()
+        for display in gxwXml.displays:
+            mapdisp = self.lmgr.NewDisplay(name=display["name"], show=False)
+            mapdisplay.append(mapdisp)
+            maptree = self.lmgr.notebookLayers.GetPage(displayId).maptree
+
+            # set windows properties
+            mapdisp.SetProperties(
+                render=display["render"],
+                mode=display["mode"],
+                showCompExtent=display["showCompExtent"],
+                alignExtent=display["alignExtent"],
+                constrainRes=display["constrainRes"],
+                projection=display["projection"]["enabled"],
+            )
+
+            if display["projection"]["enabled"]:
+                if display["projection"]["epsg"]:
+                    UserSettings.Set(
+                        group="display",
+                        key="projection",
+                        subkey="epsg",
+                        value=display["projection"]["epsg"],
+                    )
+                    if display["projection"]["proj"]:
+                        UserSettings.Set(
+                            group="display",
+                            key="projection",
+                            subkey="proj4",
+                            value=display["projection"]["proj"],
+                        )
+
+            # set position and size of map display
+            if not UserSettings.Get(
+                group="general", key="workspace", subkey=["posDisplay", "enabled"]
+            ):
+                if display["pos"]:
+                    mapdisp.SetPosition(display["pos"])
+                if display["size"]:
+                    mapdisp.SetSize(display["size"])
+
+            # set extent if defined
+            if display["extent"]:
+                w, s, e, n, b, t = display["extent"]
+                region = maptree.Map.region = maptree.Map.GetRegion(w=w, s=s, e=e, n=n)
+                mapdisp.GetWindow().ResetZoomHistory()
+                mapdisp.GetWindow().ZoomHistory(
+                    region["n"], region["s"], region["e"], region["w"]
+                )
+            if "showStatusbar" in display and not display["showStatusbar"]:
+                mapdisp.statusbarManager.Show(False)
+            if "showToolbars" in display and not display["showToolbars"]:
+                for toolbar in mapdisp.GetToolbarNames():
+                    mapdisp.RemoveToolbar(toolbar)
+
+            displayId += 1
+            mapdisp.Show()  # show mapdisplay
+            # set render property to False to speed up loading layers
+            mapdisp.mapWindowProperties.autoRender = False
+
+        maptree = None
+        selectList = []  # list of selected layers
+        #
+        # load list of map layers
+        #
+        for layer in gxwXml.layers:
+            display = layer["display"]
+            maptree = self.lmgr.notebookLayers.GetPage(display).maptree
+            newItem = maptree.AddLayer(
+                ltype=layer["type"],
+                lname=layer["name"],
+                lchecked=layer["checked"],
+                lopacity=layer["opacity"],
+                lcmd=layer["cmd"],
+                lgroup=layer["group"],
+                lnviz=layer["nviz"],
+                lvdigit=layer["vdigit"],
+                loadWorkspace=True,
+            )
+
+            if "selected" in layer:
+                selectList.append((maptree, newItem, layer["selected"]))
+
+        for maptree, layer, selected in selectList:
+            if selected:
+                if not layer.IsSelected():
+                    maptree.SelectItem(layer, select=True)
+            else:
+                maptree.SelectItem(layer, select=False)
+
+        del busy
+
+        # set render property again when all layers are loaded
+        for i, display in enumerate(gxwXml.displays):
+            mapdisplay[i].mapWindowProperties.autoRender = display["render"]
+
+            for overlay in gxwXml.overlays:
+                # overlay["cmd"][0] name of command e.g. d.barscale, d.legend
+                # overlay["cmd"][1:] parameters and flags
+                if overlay["display"] == i:
+                    if overlay["cmd"][0] == "d.legend.vect":
+                        mapdisplay[i].AddLegendVect(overlay["cmd"])
+                    if overlay["cmd"][0] == "d.legend":
+                        mapdisplay[i].AddLegendRast(overlay["cmd"])
+                    if overlay["cmd"][0] == "d.barscale":
+                        mapdisplay[i].AddBarscale(overlay["cmd"])
+                    if overlay["cmd"][0] == "d.northarrow":
+                        mapdisplay[i].AddArrow(overlay["cmd"])
+                    if overlay["cmd"][0] == "d.text":
+                        mapdisplay[i].AddDtext(overlay["cmd"])
+
+            # avoid double-rendering when loading workspace
+            # mdisp.MapWindow2D.UpdateMap()
+            # nviz
+            if gxwXml.displays[i]["viewMode"] == "3d":
+                mapdisplay[i].AddNviz()
+                self.lmgr.nvizUpdateState(
+                    view=gxwXml.nviz_state["view"],
+                    iview=gxwXml.nviz_state["iview"],
+                    light=gxwXml.nviz_state["light"],
+                )
+                mapdisplay[i].MapWindow3D.constants = gxwXml.nviz_state["constants"]
+                for idx, constant in enumerate(mapdisplay[i].MapWindow3D.constants):
+                    mapdisplay[i].MapWindow3D.AddConstant(constant, i + 1)
+                for page in ("view", "light", "fringe", "constant", "cplane"):
+                    self.lmgr.nvizUpdatePage(page)
+                self.lmgr.nvizUpdateSettings()
+                mapdisplay[i].toolbars["map"].combo.SetSelection(1)
+
+        self.workspaceFile = filename
+        return True
+
+    def SaveAs(self):
+        """Save workspace definition to selected file"""
+        dlg = wx.FileDialog(
+            parent=self.lmgr,
+            message=_("Choose file to save current workspace"),
+            defaultDir=os.getcwd(),
+            wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"),
+            style=wx.FD_SAVE,
+        )
+
+        filename = ""
+        if dlg.ShowModal() == wx.ID_OK:
+            filename = dlg.GetPath()
+
+        if filename == "":
+            return False
+
+        # check for extension
+        if filename[-4:] != ".gxw":
+            filename += ".gxw"
+
+        if os.path.exists(filename):
+            dlg = wx.MessageDialog(
+                self.lmgr,
+                message=_(
+                    "Workspace file <%s> already exists. "
+                    "Do you want to overwrite this file?"
+                )
+                % filename,
+                caption=_("Save workspace"),
+                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
+            )
+            if dlg.ShowModal() != wx.ID_YES:
+                dlg.Destroy()
+                return False
+
+        Debug.msg(4, "WorkspaceManager.SaveAs(): filename=%s" % filename)
+
+        self.SaveToFile(filename)
+        self.workspaceFile = filename
+        self.lmgr._setTitle()
+
+    def Save(self):
+        """Save file with workspace definition"""
+        if self.workspaceFile:
+            dlg = wx.MessageDialog(
+                self.lmgr,
+                message=_(
+                    "Workspace file <%s> already exists. "
+                    "Do you want to overwrite this file?"
+                )
+                % self.workspaceFile,
+                caption=_("Save workspace"),
+                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
+            )
+            if dlg.ShowModal() == wx.ID_NO:
+                dlg.Destroy()
+            else:
+                Debug.msg(
+                    4, "WorkspaceManager.Save(): filename=%s" % self.workspaceFile
+                )
+                self.SaveToFile(self.workspaceFile)
+                self.lmgr._setTitle()
+                self.workspaceChanged = False
+        else:
+            self.SaveAs()
+
+    def SaveToFile(self, filename):
+        """Save layer tree layout to workspace file
+        :return: True on success, False on error
+        """
+        tmpfile = tempfile.TemporaryFile(mode="w+b")
+        try:
+            WriteWorkspaceFile(lmgr=self.lmgr, file=tmpfile)
+        except Exception as e:
+            GError(
+                parent=self.lmgr,
+                message=_("Writing current settings to workspace file " "failed."),
+            )
+            return False
+
+        try:
+            mfile = open(filename, "wb")
+            tmpfile.seek(0)
+            for line in tmpfile.readlines():
+                mfile.write(line)
+        except IOError:
+            GError(
+                parent=self.lmgr,
+                message=_("Unable to open file <%s> for writing.") % filename,
+            )
+            return False
+
+        mfile.close()
+
+        return True
+
+    def CanClosePage(self, caption):
+        """Ask if page with map display(s) can be closed
+        """
+        # save changes in the workspace
+        maptree = self._giface.GetLayerTree()
+        if self.workspaceChanged and UserSettings.Get(
+                group='manager', key='askOnQuit', subkey='enabled'):
+            if self.workspaceFile:
+                message = _("Do you want to save changes in the workspace?")
+            else:
+                message = _("Do you want to store current settings "
+                            "to workspace file?")
+
+            # ask user to save current settings
+            if maptree.GetCount() > 0:
+                dlg = wx.MessageDialog(self.lmgr,
+                                       message=message,
+                                       caption=caption,
+                                       style=wx.YES_NO | wx.YES_DEFAULT |
+                                       wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
+                ret = dlg.ShowModal()
+                dlg.Destroy()
+                if ret == wx.ID_YES:
+                    if not self.workspaceFile:
+                        self.SaveAs()
+                    else:
+                        self.SaveToFile()
+                elif ret == wx.ID_CANCEL:
+                    return False
+        return True
+
+    def Close(self):
+        """Close file with workspace definition
+        If workspace has been modified ask user to save the changes.
+        """
+        Debug.msg(4, "WorkspaceManager.Close(): file=%s" % self.workspaceFile)
+
+        self.lmgr.DisplayCloseAll()
+        self.workspaceFile = None
+        self.workspaceChanged = False
+        self.lmgr._setTitle()
+        self.lmgr.displayIndex = 0
+        self.lmgr.currentPage = None

+ 1 - 1
gui/wxpython/mapdisp/frame.py

@@ -856,7 +856,7 @@ class MapFrame(SingleMapFrame):
             name = self.layerbook.GetPageText(pgnum)
             name = self.layerbook.GetPageText(pgnum)
             caption = _("Close Map Display {}").format(name)
             caption = _("Close Map Display {}").format(name)
             if not askIfSaveWorkspace or \
             if not askIfSaveWorkspace or \
-               (askIfSaveWorkspace and self._layerManager.CanClosePage(caption)):
+               (askIfSaveWorkspace and self._layerManager.workspace_manager.CanClosePage(caption)):
                 self.CleanUp()
                 self.CleanUp()
                 if pgnum > -1:
                 if pgnum > -1:
                     self.closingDisplay.emit(page_index=pgnum)
                     self.closingDisplay.emit(page_index=pgnum)

+ 1 - 3
gui/wxpython/vdigit/preferences.py

@@ -979,10 +979,8 @@ class VDigitSettingsDialog(wx.Dialog):
         .. todo::
         .. todo::
             Needs refactoring
             Needs refactoring
         """
         """
-        # TODO: it seems that it needs to be replaced by signal
-        # but if it makes sense generally for wxGUI it can be added to giface
         if self.parent.GetLayerManager():
         if self.parent.GetLayerManager():
-            self.parent.GetLayerManager().WorkspaceChanged()  # geometry attributes
+            self._giface.workspaceChanged.emit()
         # symbology
         # symbology
         for key, (enabled, color) in six.iteritems(self.symbology):
         for key, (enabled, color) in six.iteritems(self.symbology):
             if enabled:
             if enabled:

+ 0 - 2
gui/wxpython/xml/toolboxes.xml

@@ -227,8 +227,6 @@
       <wxgui-item name="Save"/>
       <wxgui-item name="Save"/>
       <wxgui-item name="SaveAs"/>
       <wxgui-item name="SaveAs"/>
       <wxgui-item name="Close"/>
       <wxgui-item name="Close"/>
-      <separator/>
-      <wxgui-item name="LoadGRCFileTclTkGUI"/>
     </items>
     </items>
   </toolbox>
   </toolbox>
   <toolbox name="MapDisplay">
   <toolbox name="MapDisplay">

+ 0 - 5
gui/wxpython/xml/wxgui_items.xml

@@ -154,11 +154,6 @@
     <description>Close workspace file</description>
     <description>Close workspace file</description>
     <wx-id>ID_CLOSE</wx-id>
     <wx-id>ID_CLOSE</wx-id>
   </wxgui-item>
   </wxgui-item>
-  <wxgui-item name="LoadGRCFileTclTkGUI">
-    <label>Load GRC file (Tcl/Tk GUI)</label>
-    <handler>OnWorkspaceLoadGrcFile</handler>
-    <description>Load map layers from GRC file to layer tree</description>
-  </wxgui-item>
   <wxgui-item name="AddRaster">
   <wxgui-item name="AddRaster">
     <label>Add raster</label>
     <label>Add raster</label>
     <handler>OnAddRaster</handler>
     <handler>OnAddRaster</handler>