import os import tempfile import shutil import atexit from PyQt5.QtCore import pyqtSignal, QSettings, QObject from PyQtAds import QtAds from dockindockmanager import DockInDockManager from dockindock import DockInDockWidget GROUP_PREFIX = "Group" def findWidget(name, managers: 'list[DockInDockManager]') -> QtAds.CDockWidget: for mgr in managers: widget = mgr.findDockWidget(name) if widget: return widget class PerspectiveInfo: # Partially bypass ADS perspective management, store list here # and then ADS will only have one perspective loaded # this is because all docking widgets must exist when a perspective is loaded # we will guarantee that! settings = QSettings() groups: 'dict[str, list[str]]' = {} class PerspectivesManager(QObject): perspectivesListChanged = pyqtSignal() openingPerspective = pyqtSignal() openedPerspective = pyqtSignal() def __init__(self, perspectives_folder): super().__init__() self.__perspectives_folder = perspectives_folder self.__perspectives = {} atexit.register(self.cleanup) def cleanup(self): for perspective in self.__perspectives.values(): filename = perspective.settings.fileName() try: os.remove(filename) except FileNotFoundError: pass def perspectiveNames(self) -> 'list[str]': return self.__perspectives.keys() def addPerspective(self, name: str, widget: DockInDockWidget) -> None: if self.__perspectives_folder: self.__perspectives[name] = perspective = PerspectiveInfo() perspective.settings = self.getSettingsObject(self.getSettingsFileName(name, True)) perspective.groups = widget.getManager().getGroupContents() # save perspective internally widget.getManager().addPerspectiveRec(name) # store it in QSettings object widget.getManager().savePerspectivesRec(perspective.settings) # remove internal perspectives widget.getManager().removePerspectives(widget.getManager().perspectiveNames()) self.perspectivesListChanged.emit() def openPerspective(name: str, widget: DockInDockWidget) -> None: assert widget.getTopLevelDockWidget() == widget if self.__perspectives_folder: if name in self.__perspectives: self.openingPerspective.emit() if widget.canCreateNewGroups(): cur_groups = widget.getManager().allManagers(True, True) for group in self.__perspectives[name].groups.keys(): found = False for curgroup in cur_groups: if curgroup.getPerspectiveGroupName() == group: found = True break if not found: group = DockInDockManager.getGroupNameFromPersistGroupName(group) # restore group in file but not in GUI yet widget.createGroup(group, None) cur_groups = widget.getManager().allManagers(False, True) for curgroup in cur_groups: if curgroup.getPersistGroupName() not in self.__perspectives[name].groups.keys(): widget.destroyGroup(curgroup.parent()) managers = widget.getManager().allManagers(True, True) for group in self.__perspectives[name].groups().keys(): for mgr in managers: if mgr.getPersistGroupName() == group: for widget_name in self.__perspectives[name].groups[group]: widget = findWidget(widget_name, [mgr]) if widget: pass # OK, widget is already in the good manager! else: widget = findWidget(widget_name, managers) if widget: # move dock widget in the same group as it used to be when perspective was saved # this guarantee load/open perspectives will work smartly MoveDockWidgetAction.move(widget, mgr) # internally load perspectives from QSettings widget.getManager().loadPerspectivesRec(self.__perspectives[name].settings) # load perspective (update GUI) widget.getManager().openPerspectiveRec(name) # remove internal perspectives widget.getManager().removePerspectives(widget.getManager().perspectiveNames()) self.openedPerspective().emit() else: assert False def removePerspectives(self) -> None: self.__perspectives.clear() self.perspectivesListChanged.emit() def removePerspective(self, name: str) -> None: del self.__perspectives[name] self.perspectivesListChanged.emit() def getSettingsFileName(self, perspective: str, temp: bool) -> str: name = "perspectives.ini" if not perspective else f"perspectives_{perspective + '.tmp' if temp else perspective + '.ini'}" return os.path.join(self.__perspectives_folder, name) def getSettingsObject(self, file_path: str) -> QSettings: return QSettings(file_path, QSettings.IniFormat) def loadPerspectives(self) -> None: if self.__perspectives_folder: tempfile.mktemp(dir=self.__perspectives_folder) self.__perspectives.clear() main_settings = self.getSettingsObject(self.getSettingsFileName("", False)) debug = main_settings.fileName() size = main_settings.beginReadArray("Perspectives") for i in range(0, size): main_settings.setArrayIndex(i) perspective = main_settings.value("Name") if perspective: to_load = self.getSettingsFileName(perspective, False) loaded = self.getSettingsFileName(perspective, True) try: os.remove(loaded) except FileNotFoundError: pass if not shutil.copy(to_load, loaded): assert False self.__perspectives[perspective] = PerspectiveInfo() self.__perspectives[perspective].settings = self.getSettingsObject(loaded) # load group info: main_settings.beginGroup(GROUP_PREFIX) for key in main_settings.allKeys(): self.__perspectives[perspective].groups[key] = main_settings.value(key) main_settings.endGroup() else: assert False main_settings.endArray() self.perspectivesListChanged.emit() def savePerspectives(self) -> None: if self.__perspectives_folder: main_settings = self.getSettingsObject(self.getSettingsFileName("", False)) # Save list of perspective and group organization main_settings.beginWriteArray("Perspectives", len(self.__perspectives)) for i, perspective in enumerate(self.__perspectives.keys()): main_settings.setArrayIndex(i) main_settings.setValue("Name", perspective) main_settings.beginGroup(GROUP_PREFIX) for group in self.__perspectives[perspective].groups.keys(): main_settings.setValue(group, list(self.__perspectives[perspective].groups[group])) main_settings.endGroup() main_settings.endArray() # Save perspectives themselves for perspective_name in self.__perspectives.keys(): to_save = self.getSettingsFileName(perspective_name, False) settings = self.__perspectives[perspective_name].settings settings.sync() try: os.remove(to_save) except FileNotFoundError: pass if not shutil.copy(settings.fileName(), to_save): assert False