From fd624b0bf519b29e7f0dd4a6507e2a92ba8d5b8f Mon Sep 17 00:00:00 2001 From: David Meyer Date: Tue, 18 Nov 2025 16:05:43 -0500 Subject: [PATCH 1/2] Add ability for plot windows to remember their geometry between runs of lyse. Uses the `QSettings` functionality of Qt itself, with ini files created in lyse's `app_saved_config` directory. A new file for each analysis script is created. --- lyse/analysis_subprocess.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lyse/analysis_subprocess.py b/lyse/analysis_subprocess.py index 0a0f100..650db37 100644 --- a/lyse/analysis_subprocess.py +++ b/lyse/analysis_subprocess.py @@ -27,6 +27,7 @@ from qtutils.qt import QtCore, QtGui, QtWidgets from qtutils.qt.QtCore import pyqtSignal as Signal +from qtutils.qt.QtCore import QSettings, QByteArray from qtutils import inmain, inmain_decorator, UiLoader import qtutils.icons @@ -56,6 +57,9 @@ ): multiprocessing.set_start_method('spawn') +# read labconfig once for plot window locations +autoload_config_file = lyse.utils.LABCONFIG.get('lyse', 'autoload_config_file') +config_dir = os.path.dirname(autoload_config_file) class PlotWindowCloseEvent(QtGui.QCloseEvent): def __init__(self, force, *args, **kwargs): @@ -68,22 +72,43 @@ class PlotWindow(QtWidgets.QWidget): def __init__(self, plot, *args, **kwargs): self.__plot = plot + filepath = kwargs.pop('analysis_filepath') QtWidgets.QWidget.__init__(self, *args, **kwargs) + # configure plot window persistence + filename = os.path.basename(os.path.splitext(filepath)[0]) + settings_path = os.path.join(config_dir, 'lyse-'+filename+'.ini') + self.settings = QSettings(settings_path, QSettings.IniFormat) + + self.restore_geometry() + def closeEvent(self, event): self.hide() if isinstance(event, PlotWindowCloseEvent) and event.force: + # if closing, save window geometry to QSettings + self.settings.setValue("windowGeometry", self.saveGeometry()) + self.settings.sync() self.__plot.on_close() event.accept() else: event.ignore() + + def restore_geometry(self): + """Restores window geometry from local QSettings config. + + Will do nothing if config not present. + """ + geometry = self.settings.value("windowGeometry", QByteArray()) + if isinstance(geometry, QByteArray) and not geometry.isEmpty(): + self.restoreGeometry(geometry) class Plot(object): def __init__(self, figure, identifier, filepath): self.identifier = identifier loader = UiLoader() - self.ui = loader.load(os.path.join(lyse.utils.LYSE_DIR, 'user_interface/plot_window.ui'), PlotWindow(self)) + self.ui = loader.load(os.path.join(lyse.utils.LYSE_DIR, 'user_interface/plot_window.ui'), + PlotWindow(self, analysis_filepath=filepath)) self.set_window_title(identifier, filepath) @@ -284,6 +309,7 @@ def mainloop(self): task, data = self.from_parent.get() with kill_lock: if task == 'quit': + self.close_plots() inmain(qapplication.quit) elif task == 'analyse': path = data @@ -296,6 +322,13 @@ def mainloop(self): self.to_parent.put(['error', lyse.utils.worker._updated_data]) else: self.to_parent.put(['error','invalid task %s'%str(task)]) + + @inmain_decorator() + def close_plots(self): + """Ensures analysis plots get the force close event and save geometry when lyse closes""" + for plot in self.plots.values(): + event = PlotWindowCloseEvent(True) + QtCore.QCoreApplication.instance().postEvent(plot.ui, event) @inmain_decorator() def do_analysis(self, path): From 08c608427ddcc34624939b5c70333c70b1a58c6e Mon Sep 17 00:00:00 2001 From: David Meyer Date: Fri, 21 Nov 2025 11:03:06 -0500 Subject: [PATCH 2/2] Allow for more than one plot window per analysis process. --- lyse/analysis_subprocess.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lyse/analysis_subprocess.py b/lyse/analysis_subprocess.py index 650db37..5f88e3e 100644 --- a/lyse/analysis_subprocess.py +++ b/lyse/analysis_subprocess.py @@ -73,6 +73,7 @@ class PlotWindow(QtWidgets.QWidget): def __init__(self, plot, *args, **kwargs): self.__plot = plot filepath = kwargs.pop('analysis_filepath') + self.identifier = kwargs.pop('analysis_identifier') QtWidgets.QWidget.__init__(self, *args, **kwargs) # configure plot window persistence @@ -86,7 +87,7 @@ def closeEvent(self, event): self.hide() if isinstance(event, PlotWindowCloseEvent) and event.force: # if closing, save window geometry to QSettings - self.settings.setValue("windowGeometry", self.saveGeometry()) + self.settings.setValue(f"windowGeometry-{self.identifier:d}", self.saveGeometry()) self.settings.sync() self.__plot.on_close() event.accept() @@ -98,7 +99,7 @@ def restore_geometry(self): Will do nothing if config not present. """ - geometry = self.settings.value("windowGeometry", QByteArray()) + geometry = self.settings.value(f"windowGeometry-{self.identifier}", QByteArray()) if isinstance(geometry, QByteArray) and not geometry.isEmpty(): self.restoreGeometry(geometry) @@ -108,7 +109,8 @@ def __init__(self, figure, identifier, filepath): self.identifier = identifier loader = UiLoader() self.ui = loader.load(os.path.join(lyse.utils.LYSE_DIR, 'user_interface/plot_window.ui'), - PlotWindow(self, analysis_filepath=filepath)) + PlotWindow(self, analysis_filepath=filepath, + analysis_identifier=identifier)) self.set_window_title(identifier, filepath)