LCOV - code coverage report
Current view: top level - src/pairinteraction_gui - settings.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 69 81 85.2 %
Date: 2026-04-17 09:29:39 Functions: 7 9 77.8 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2026 PairInteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3           1 : from __future__ import annotations
       4             : 
       5           1 : import logging
       6           1 : from typing import TYPE_CHECKING, ClassVar
       7             : 
       8           1 : from PySide6.QtCore import QObject, QSettings
       9           1 : from PySide6.QtWidgets import (
      10             :     QCheckBox,
      11             :     QComboBox,
      12             :     QDoubleSpinBox,
      13             :     QRadioButton,
      14             :     QSpinBox,
      15             :     QStackedWidget,
      16             :     QWidget,
      17             : )
      18             : 
      19           1 : from pairinteraction import _backend
      20           1 : from pairinteraction_gui.config.base_config import BaseConfig
      21             : 
      22             : if TYPE_CHECKING:
      23             :     from pathlib import Path
      24             : 
      25             : 
      26           1 : logger = logging.getLogger(__name__)
      27             : 
      28             : 
      29           1 : class SettingsManager(QObject):
      30             :     """Settings manager."""
      31             : 
      32           1 :     widget_mappers: ClassVar[dict[type, tuple[str, str, type]]] = {
      33             :         QCheckBox: ("isChecked", "setChecked", bool),
      34             :         QSpinBox: ("value", "setValue", int),
      35             :         QDoubleSpinBox: ("value", "setValue", float),
      36             :         QRadioButton: ("isChecked", "setChecked", bool),
      37             :         QComboBox: ("currentText", "setCurrentText", str),
      38             :     }
      39             : 
      40           1 :     def __init__(self, cache_dir: Path | None = None) -> None:
      41           1 :         super().__init__()
      42           1 :         if cache_dir is None:
      43           0 :             cache_dir = _backend.get_cache_directory()
      44           1 :         path = cache_dir / "gui_settings.ini"
      45           1 :         path.parent.mkdir(parents=True, exist_ok=True)
      46           1 :         self.settings = QSettings(str(path), QSettings.Format.IniFormat)
      47             : 
      48           1 :     def value(self, key: str, default: object = None, value_type: type | None = None) -> object:
      49           0 :         if value_type is not None:
      50           0 :             return self.settings.value(key, defaultValue=default, type=value_type)
      51           0 :         return self.settings.value(key, defaultValue=default)
      52             : 
      53           1 :     def set_value(self, key: str, value: object) -> None:
      54           0 :         self.settings.setValue(key, value)
      55             : 
      56           1 :     def _get_mapper(self, widget: QWidget) -> tuple[str, str, type] | tuple[None, None, None]:
      57           1 :         return next((m for c, m in self.widget_mappers.items() if isinstance(widget, c)), (None, None, None))
      58             : 
      59           1 :     def update_widgets_from_settings(self, widget_map: dict[str, QWidget], *, combos_only: bool = False) -> None:
      60             :         """Set widget states from stored settings values."""
      61           1 :         for name, widget in widget_map.items():
      62           1 :             if combos_only and not isinstance(widget, QComboBox):
      63           1 :                 continue
      64             : 
      65           1 :             getter, setter, dtype = self._get_mapper(widget)
      66           1 :             if not getter:
      67           0 :                 continue
      68             : 
      69           1 :             value = getattr(widget, getter)()
      70           1 :             stored = self.settings.value(name, value, type=dtype)
      71           1 :             if stored is None:
      72           0 :                 continue
      73             : 
      74           1 :             if setter:
      75           1 :                 try:
      76           1 :                     getattr(widget, setter)(stored)
      77           0 :                 except Exception as e:
      78           0 :                     logger.warning("Failed to restore setting '%s' with value '%s': %s", name, stored, e)
      79             : 
      80           1 :     def update_settings_from_widgets(self, widget_map: dict[str, QWidget]) -> None:
      81             :         """Save widget states into settings."""
      82           1 :         for name, widget in widget_map.items():
      83           1 :             getter, _setter, _dtype = self._get_mapper(widget)
      84           1 :             if getter:
      85           1 :                 value = getattr(widget, getter)()
      86           1 :                 if value is not None:
      87           1 :                     self.settings.setValue(name, value)
      88             : 
      89           1 :     def save_widget_state(self, root: QWidget, group: str) -> None:
      90             :         """Write the current state of all named input widgets under `group`."""
      91           1 :         if not isinstance(root, BaseConfig):
      92           0 :             return
      93           1 :         widget_map = self.collect_widgets(root)
      94           1 :         self.settings.beginGroup(group)
      95           1 :         self.update_settings_from_widgets(widget_map)
      96           1 :         self.settings.endGroup()
      97             : 
      98           1 :     def restore_widget_state(self, root: QWidget, group: str) -> None:
      99             :         """Restore widget state (two-pass: combos first, then others)."""
     100           1 :         if not isinstance(root, BaseConfig):
     101           0 :             return
     102           1 :         widget_map = self.collect_widgets(root)
     103           1 :         self.settings.beginGroup(group)
     104           1 :         self.update_widgets_from_settings(widget_map, combos_only=True)
     105           1 :         widget_map = self.collect_widgets(root)
     106           1 :         self.update_widgets_from_settings(widget_map, combos_only=False)
     107           1 :         self.settings.endGroup()
     108             : 
     109           1 :     def collect_widgets(self, root: QWidget, widget_map: dict[str, QWidget] | None = None) -> dict[str, QWidget]:
     110           1 :         if widget_map is None:
     111           1 :             widget_map = {}
     112           1 :         for child in root.children():
     113           1 :             if not isinstance(child, QWidget):
     114           1 :                 continue
     115           1 :             name = child.objectName()
     116           1 :             if name and any(isinstance(child, c) for c in self.widget_mappers):
     117           1 :                 if name in widget_map:
     118           0 :                     logger.warning("Duplicate widget name '%s' found. Only the last one will be saved/restored.", name)
     119           1 :                 widget_map[name] = child
     120             : 
     121             :             # For stacked widgets, only recurse into the currently shown page
     122           1 :             if isinstance(child, QStackedWidget):
     123           1 :                 current = child.currentWidget()
     124           1 :                 if current is not None:
     125           1 :                     self.collect_widgets(current, widget_map)
     126             :             else:
     127           1 :                 self.collect_widgets(child, widget_map)
     128             : 
     129           1 :         return widget_map

Generated by: LCOV version 1.16