123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- """
- @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 list(display.decorations):
- 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.ShowStatusbar(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(self.workspaceFile)
- 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
|