Source code for hyloa.data.session

# This file is part of HYLOA - HYsteresis LOop Analyzer.
# Copyright (C) 2024 Francesco Zeno Costanzo

# HYLOA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# HYLOA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with HYLOA. If not, see <https://www.gnu.org/licenses/>.


"""
Code to save a session (i.e. alla dta loaded all plot crated and so on) in .pkl files
"""
import os
import sys
import gzip
import pickle
import logging
from datetime import datetime


from PyQt5.QtCore import QTimer

from PyQt5.QtWidgets import (
    QMessageBox, QFileDialog
)

from hyloa.utils.logging_setup import setup_logging

from hyloa.gui.plot_window import PlotControlWidget
from hyloa.gui.worksheet import WorksheetWindow

[docs] def save_current_session(app_instance, parent_widget=None): ''' Save the current session to a HYLOA session file (.hyloa) Session files are intended to be opened only by HYLOA. Backward compatibility is guaranteed across minor versions. Parameters ---------- app_instance : MainApp Main application instance containing the session data. parent_widget : QWidget or None Optional parent for the dialog windows. ''' if app_instance.logger is None: QMessageBox.critical(parent_widget, "Error", "Cannot start analysis without starting log.") return file_path, _ = QFileDialog.getSaveFileName( parent_widget, "Save session", "", "HYLOA Session (*.hyloa)" ) if not file_path: QMessageBox.warning(parent_widget, "Warning", "No file selected for saving.") return if not file_path.lower().endswith(".hyloa"): file_path += ".hyloa" try: # Build the dictionary for saving data session_data = { "__hyloa__": True, "file_format_version": 1, "app_version": getattr(app_instance, "version", "unknown"), "created_on": datetime.now().isoformat(), "platform": sys.platform, "dataframes": app_instance.dataframes, "header_lines": app_instance.header_lines, "logger_path": app_instance.logger_path, "log_filename": os.path.basename(app_instance.logger_path), "fit_results": app_instance.fit_results, "number_plots": app_instance.number_plots, "plot_widgets": { idx: { "selected_pairs": [ ( f_combo.currentText(), x_combo.currentText(), y_combo.currentText() ) for f_combo, x_combo, y_combo in widget.selected_pairs ], "plot_customizations": widget.plot_customizations.copy() } for idx, widget in app_instance.plot_widgets.items() }, "plot_names": { idx : name for idx, name in app_instance.plot_names.items() }, "plot_control_pos": (app_instance.plot_control_subwindow.pos().x(), app_instance.plot_control_subwindow.pos().y() ), "tab_order": list(app_instance.plot_widgets.keys()), "plot_windows_geometry": { idx: { "x": fig_sub.x(), "y": fig_sub.y(), "width": fig_sub.width(), "height": fig_sub.height(), "minimized": fig_sub.isMinimized() } for idx, fig_sub in app_instance.figure_subwindows.items() }, "worksheets": { idx: { "name": app_instance.worksheet_names.get(idx, f"Worksheet {idx}"), "content": app_instance.worksheet_windows[idx].to_session_data() } for idx in app_instance.worksheet_windows }, } with gzip.open(file_path, "wb") as f: pickle.dump(session_data, f, protocol=pickle.HIGHEST_PROTOCOL) QMessageBox.information(parent_widget, "Session saved", f"Session saved in file:\n{file_path}") except Exception as e: QMessageBox.critical(parent_widget, "Error", f"Error while saving:\n{e}")
[docs] def load_previous_session(app_instance, parent_widget=None): ''' Load a previously saved session from a .hyloa file. Parameters ---------- app_instance : MainApp Main application instance to load the session into. parent_widget : QWidget or None Optional parent for the dialog windows. ''' options = QFileDialog.Options() file_path, _ = QFileDialog.getOpenFileName( parent_widget, "Load session", "", "HYLOA Session (*.hyloa);;Legacy Pickle (*.pkl)", options=options ) if not file_path: QMessageBox.warning(parent_widget, "Error", "No file selected for session loading.") return try: try: with gzip.open(file_path, "rb") as f: session_data = pickle.load(f) except OSError: # --- fallback to legacy pickle --- with open(file_path, "rb") as f: session_data = pickle.load(f) # Reload attributes of main app instance app_instance.dataframes = session_data.get("dataframes", []) app_instance.header_lines = session_data.get("header_lines", []) app_instance.fit_results = session_data.get("fit_results", {}) app_instance.number_plots = session_data.get("number_plots", 0) try : app_instance.logger_path = session_data.get("logger_path", None) # Recreate the logger setup_logging(app_instance.logger_path) except : log_filename = session_data.get("log_filename") # To ensure compatibility with different OS default_dir = os.path.dirname(file_path) reconstructed_path = os.path.join(default_dir, log_filename) app_instance.logger_path = reconstructed_path # Recreate the logger setup_logging(app_instance.logger_path) app_instance.logger = logging.getLogger(__name__) is_new_format = session_data.get("__hyloa__", False) file_version = session_data.get("file_format_version", 0) if is_new_format: app_instance.logger.info( f"Loaded HYLOA session (format v{file_version})" ) else: app_instance.logger.info( "Loaded legacy HYLOA session (no signature)" ) app_instance.logger.info("Logger restored from session file.") app_instance.open_default_panels() # Recreate all plot's control panels plot_control_pos = session_data.get("plot_control_pos") plot_widgets_data = session_data.get("plot_widgets", {}) plot_names_data = session_data.get("plot_names", {}) # Loop only over widget because widgets and names share the same keys for idx_str, plot_info in plot_widgets_data.items(): idx = int(idx_str) plot_name = plot_names_data.get(idx, f"Graph {idx}") widget = PlotControlWidget(app_instance, idx, plot_name) for i in reversed(range(widget.pair_layout.count())): widget.pair_layout.itemAt(i).widget().setParent(None) widget.selected_pairs.clear() app_instance.plot_widgets[idx] = widget app_instance.plot_names[idx] = plot_name # Retrieve selected pairs ... for file_str, x_str, y_str in plot_info.get("selected_pairs", []): widget.add_pair(file_text=file_str, x_col=x_str, y_col=y_str) # ... and customization widget.plot_customizations = plot_info.get("plot_customizations", {}) # Create sub window for panel app_instance.create_plot_tabs() app_instance.plot_tabs.addTab(widget, plot_name) app_instance.plot_widgets[idx] = widget app_instance.plot_names[idx] = plot_name if plot_control_pos and app_instance.plot_control_subwindow is not None: QTimer.singleShot(0, lambda: app_instance.plot_control_subwindow.move(plot_control_pos[0], plot_control_pos[1])) widget.plot() # Force the plot # ... and for plot windows fig_geom = session_data.get("plot_windows_geometry", {}).get(idx) fig_sub = app_instance.figure_subwindows.get(idx) if fig_geom and fig_sub: fig_sub.setGeometry(fig_geom["x"], fig_geom["y"], fig_geom["width"], fig_geom["height"]) if fig_geom.get("minimized"): fig_sub.showMinimized() # --- restore worksheets --- worksheet_data = session_data.get("worksheets", {}) for idx_key, ws_info in worksheet_data.items(): try: idx = int(idx_key) except: idx = idx_key ws_name = ws_info.get("name", f"Worksheet {idx}") # Create worksheet instance with name ws = WorksheetWindow(app_instance.mdi_area, parent=app_instance.mdi_area, name=ws_name, logger=app_instance.logger, app_instance=app_instance) # Add to mdi area BEFORE restoring content (important!) app_instance.mdi_area.addSubWindow(ws) # Restore content & plots; from_session_data will set geometry and schedule show ws.from_session_data(ws_info.get("content", {})) # Save references in app_instance app_instance.worksheet_windows[idx] = ws app_instance.worksheet_names[idx] = ws_name app_instance.worksheet_subwindows[idx] = ws if isinstance(idx, int) and idx > app_instance.number_worksheets: app_instance.number_worksheets = idx + 1 # Ensure it's visible (from_session_data schedules showNormal/showMinimized) QTimer.singleShot(0, lambda w=ws: w.show()) # Rename main window to reflect the loaded session base_name = os.path.basename(file_path) name, _ = os.path.splitext(base_name) app_instance.setWindowTitle(f"HYLOA - {name}") QMessageBox.information(parent_widget, "Session Loaded", f"Session loaded from file:\n{file_path}") except Exception as e: QMessageBox.critical(parent_widget, "Error", f"Error while loading session:\n{e}")