LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/qobjects - item.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 140 153 91.5 %
Date: 2026-04-17 09:29:39 Functions: 23 26 88.5 %

          Line data    Source code
       1             : # SPDX-FileCopyrightText: 2025 PairInteraction Developers
       2             : # SPDX-License-Identifier: LGPL-3.0-or-later
       3           1 : from __future__ import annotations
       4             : 
       5           1 : import typing as t
       6           1 : from typing import TYPE_CHECKING, Generic, TypeVar
       7             : 
       8           1 : import numpy as np
       9           1 : from PySide6.QtWidgets import QCheckBox, QLabel
      10             : 
      11           1 : from pairinteraction_gui.qobjects.spin_boxes import DoubleSpinBox, HalfIntSpinBox, IntSpinBox
      12           1 : from pairinteraction_gui.qobjects.widget import WidgetH
      13           1 : from pairinteraction_gui.utils import label_to_object_name
      14             : 
      15             : if TYPE_CHECKING:
      16             :     from collections.abc import Callable
      17             : 
      18             :     from PySide6.QtWidgets import QWidget
      19             : 
      20             :     P = TypeVar("P")
      21             : 
      22           1 : ValueType = TypeVar("ValueType", int, float, complex)
      23             : 
      24             : 
      25           1 : @t.runtime_checkable
      26           1 : class NotSet(t.Protocol):
      27             :     """Singleton for a not set value and type at the same time.
      28             : 
      29             :     See Also:
      30             :     https://stackoverflow.com/questions/77571796/how-to-create-singleton-object-which-could-be-used-both-as-type-and-value-simi
      31             : 
      32             :     """
      33             : 
      34             :     @staticmethod
      35             :     def __not_set() -> None: ...
      36             : 
      37             : 
      38           1 : class Item(WidgetH):
      39           1 :     margin = (20, 0, 20, 0)
      40           1 :     spacing = 10
      41             : 
      42           1 :     def __init__(
      43             :         self,
      44             :         parent: QWidget,
      45             :         label: str,
      46             :         checked: bool = True,
      47             :     ) -> None:
      48           1 :         self.checkbox = QCheckBox()
      49           1 :         self.checkbox.setObjectName(f"{label_to_object_name(label)}_checkbox")
      50           1 :         self.checkbox.setChecked(checked)
      51             : 
      52           1 :         self.label = label
      53           1 :         self._label = QLabel(label)
      54           1 :         self._label.setMinimumWidth(25)
      55             : 
      56           1 :         super().__init__(parent)
      57             : 
      58           1 :     def setupWidget(self) -> None:
      59           1 :         self.layout().addWidget(self.checkbox)
      60           1 :         self.layout().addWidget(self._label)
      61             : 
      62           1 :     def postSetupWidget(self) -> None:
      63           1 :         self.layout().addStretch(1)
      64             : 
      65           1 :     def isChecked(self) -> bool:
      66             :         """Return the state of the checkbox."""
      67           1 :         return self.checkbox.isChecked()
      68             : 
      69           1 :     def setChecked(self, checked: bool) -> None:
      70             :         """Set the state of the checkbox."""
      71           1 :         self.checkbox.setChecked(checked)
      72             : 
      73           1 :     def connectAll(self, func: Callable[[], None]) -> None:
      74             :         """Connect the function to the spinbox.valueChanged signal."""
      75           0 :         self.checkbox.stateChanged.connect(lambda state: func())
      76             : 
      77             : 
      78           1 : class _QnItem(WidgetH, Generic[ValueType]):
      79             :     """Widget for displaying a range with min and max spinboxes."""
      80             : 
      81           1 :     margin = (20, 0, 20, 0)
      82           1 :     spacing = 10
      83           1 :     _spinbox_class: type[IntSpinBox | HalfIntSpinBox | DoubleSpinBox]
      84             : 
      85           1 :     def __init__(
      86             :         self,
      87             :         parent: QWidget,
      88             :         label: str,
      89             :         vmin: ValueType = 0,
      90             :         vmax: ValueType = 999,
      91             :         vdefault: ValueType = 0,
      92             :         vstep: ValueType | None = None,
      93             :         unit: str = "",
      94             :         tooltip: str | None = None,
      95             :         checkable: bool = True,
      96             :         checked: bool = True,
      97             :     ) -> None:
      98           1 :         tooltip = tooltip if tooltip is not None else f"{label} in {unit}"
      99             : 
     100           1 :         self.checkbox: QCheckBox | None
     101           1 :         if checkable:
     102           1 :             self.checkbox = QCheckBox()
     103           1 :             self.checkbox.setObjectName(f"{label_to_object_name(label)}_checkbox")
     104           1 :             self.checkbox.setChecked(checked)
     105           1 :             self.checkbox.stateChanged.connect(self._on_checkbox_changed)
     106             :         else:
     107           1 :             self.checkbox = None
     108             : 
     109           1 :         self.label = QLabel(label)
     110           1 :         self.label.setMinimumWidth(25)
     111             : 
     112           1 :         self.spinbox = self._spinbox_class(parent, vmin, vmax, vdefault, vstep, tooltip=tooltip)  # type: ignore [arg-type]
     113           1 :         self.spinbox.setObjectName(label_to_object_name(label))
     114           1 :         self.spinbox.setMinimumWidth(100)
     115             : 
     116           1 :         self.unit = QLabel(unit)
     117             : 
     118           1 :         super().__init__(parent)
     119             : 
     120           1 :     def setupWidget(self) -> None:
     121           1 :         if isinstance(self.checkbox, QCheckBox):
     122           1 :             self.layout().addWidget(self.checkbox)
     123           1 :         self.layout().addWidget(self.label)
     124           1 :         self.layout().addWidget(self.spinbox)
     125           1 :         self.layout().addWidget(self.unit)
     126             : 
     127           1 :     def postSetupWidget(self) -> None:
     128           1 :         self.layout().addStretch(1)
     129           1 :         self._on_checkbox_changed(self.isChecked())
     130             : 
     131           1 :     def connectAll(self, func: Callable[[], None]) -> None:
     132             :         """Connect the function to the spinbox.valueChanged signal."""
     133           1 :         if isinstance(self.checkbox, QCheckBox):
     134           1 :             self.checkbox.stateChanged.connect(lambda state: func())
     135           1 :         self.spinbox.valueChanged.connect(lambda value: func())
     136             : 
     137           1 :     def _on_checkbox_changed(self, state: bool) -> None:
     138             :         """Update the enabled state of widget when checkbox changes."""
     139           1 :         self.spinbox.setEnabled(state)
     140             : 
     141           1 :     def isChecked(self) -> bool:
     142             :         """Return the state of the checkbox."""
     143           1 :         if isinstance(self.checkbox, QCheckBox):
     144           1 :             return self.checkbox.isChecked()
     145           1 :         return True
     146             : 
     147           1 :     def setChecked(self, checked: bool) -> None:
     148             :         """Set the state of the checkbox."""
     149           1 :         if isinstance(self.checkbox, QCheckBox):
     150           1 :             return self.checkbox.setChecked(checked)
     151           0 :         raise ValueError("Cannot (un)check a non-checkable item.")
     152             : 
     153           1 :     def value(
     154             :         self,
     155             :         default: P | NotSet = NotSet,
     156             :     ) -> ValueType | P:
     157             :         """Return the value of the spinbox."""
     158           1 :         if not self.isChecked():
     159           0 :             if isinstance(default, NotSet):
     160           0 :                 raise ValueError("Checkbox is not checked and no default value is provided.")
     161           0 :             return default
     162           1 :         return self.spinbox.value()  # type: ignore [return-value]
     163             : 
     164           1 :     def setValue(self, value: ValueType) -> None:
     165             :         """Set the value of the spinbox and set the checkbox state to checked if applicable."""
     166           1 :         if isinstance(self.checkbox, QCheckBox):
     167           1 :             self.checkbox.setChecked(True)
     168           1 :         self.spinbox.setValue(value)  # type: ignore [arg-type]
     169             : 
     170             : 
     171           1 : class QnItemInt(_QnItem[int]):
     172           1 :     _spinbox_class = IntSpinBox
     173             : 
     174             : 
     175           1 : class QnItemHalfInt(_QnItem[float]):
     176           1 :     _spinbox_class = HalfIntSpinBox
     177             : 
     178             : 
     179           1 : class QnItemDouble(_QnItem[float]):
     180           1 :     _spinbox_class = DoubleSpinBox
     181             : 
     182             : 
     183           1 : class RangeItem(WidgetH):
     184             :     """Widget for displaying a range with min and max spinboxes."""
     185             : 
     186           1 :     margin = (20, 0, 20, 0)
     187           1 :     spacing = 10
     188             : 
     189           1 :     def __init__(
     190             :         self,
     191             :         parent: QWidget,
     192             :         label: str,
     193             :         vdefaults: tuple[float, float] = (0, 0),
     194             :         vrange: tuple[float, float] = (-np.inf, np.inf),
     195             :         unit: str = "",
     196             :         tooltip_label: str | None = None,
     197             :         checkable: bool = True,
     198             :         checked: bool = True,
     199             :     ) -> None:
     200           1 :         tooltip_label = tooltip_label if tooltip_label is not None else label
     201             : 
     202           1 :         self.checkbox: QCheckBox | None
     203           1 :         if checkable:
     204           1 :             self.checkbox = QCheckBox()
     205           1 :             self.checkbox.setObjectName(f"{label_to_object_name(label)}_checkbox")
     206           1 :             self.checkbox.setChecked(checked)
     207           1 :             self.checkbox.stateChanged.connect(self._on_checkbox_changed)
     208             :         else:
     209           1 :             self.checkbox = None
     210             : 
     211           1 :         self.label = QLabel(label)
     212           1 :         self.label.setMinimumWidth(25)
     213             : 
     214           1 :         self.min_spinbox = DoubleSpinBox(parent, *vrange, vdefaults[0], tooltip=f"Minimum {tooltip_label} in {unit}")
     215           1 :         self.max_spinbox = DoubleSpinBox(parent, *vrange, vdefaults[1], tooltip=f"Maximum {tooltip_label} in {unit}")
     216           1 :         self.min_spinbox.setObjectName(f"{label_to_object_name(label)}_min")
     217           1 :         self.max_spinbox.setObjectName(f"{label_to_object_name(label)}_max")
     218             : 
     219           1 :         self.unit = QLabel(unit)
     220             : 
     221           1 :         super().__init__(parent)
     222             : 
     223           1 :     def setupWidget(self) -> None:
     224           1 :         if isinstance(self.checkbox, QCheckBox):
     225           1 :             self.layout().addWidget(self.checkbox)
     226           1 :         self.layout().addWidget(self.label)
     227             : 
     228           1 :         self.layout().addWidget(self.min_spinbox)
     229           1 :         self.layout().addWidget(QLabel("to"))
     230           1 :         self.layout().addWidget(self.max_spinbox)
     231             : 
     232           1 :         self.layout().addWidget(self.unit)
     233             : 
     234           1 :     def postSetupWidget(self) -> None:
     235           1 :         self.layout().addStretch(1)
     236           1 :         self._on_checkbox_changed(self.isChecked())
     237             : 
     238           1 :     @property
     239           1 :     def spinboxes(self) -> tuple[DoubleSpinBox, DoubleSpinBox]:
     240             :         """Return the min and max spinboxes."""
     241           1 :         return (self.min_spinbox, self.max_spinbox)
     242             : 
     243           1 :     def connectAll(self, func: Callable[[], None]) -> None:
     244             :         """Connect the function to the spinbox.valueChanged signal."""
     245           0 :         if isinstance(self.checkbox, QCheckBox):
     246           0 :             self.checkbox.stateChanged.connect(lambda state: func())
     247           0 :         for spinbox in self.spinboxes:
     248           0 :             spinbox.valueChanged.connect(lambda value: func())
     249             : 
     250           1 :     def _on_checkbox_changed(self, state: bool) -> None:
     251             :         """Update the enabled state of widgets when checkbox changes."""
     252           1 :         for spinbox in self.spinboxes:
     253           1 :             spinbox.setEnabled(state)
     254             : 
     255           1 :     def isChecked(self) -> bool:
     256             :         """Return the state of the checkbox."""
     257           1 :         if isinstance(self.checkbox, QCheckBox):
     258           1 :             return self.checkbox.isChecked()
     259           1 :         return True
     260             : 
     261           1 :     def setChecked(self, checked: bool) -> None:
     262             :         """Set the state of the checkbox."""
     263           1 :         if isinstance(self.checkbox, QCheckBox):
     264           1 :             return self.checkbox.setChecked(checked)
     265           0 :         raise ValueError("Cannot (un)check a non-checkable item.")
     266             : 
     267           1 :     def values(self, default: tuple[float, float] | P | NotSet = NotSet) -> tuple[float, float] | P:
     268             :         """Return the values of the min and max spinboxes."""
     269           1 :         if not self.isChecked():
     270           0 :             if isinstance(default, NotSet):
     271           0 :                 raise ValueError("Checkbox is not checked and no default value is provided.")
     272           0 :             return default
     273           1 :         return (self.min_spinbox.value(), self.max_spinbox.value())
     274             : 
     275           1 :     def setValues(self, vmin: float, vmax: float) -> None:
     276             :         """Set the values of the min and max spinboxes and set the checkbox state to checked if applicable."""
     277           1 :         if isinstance(self.checkbox, QCheckBox):
     278           1 :             self.checkbox.setChecked(True)
     279           1 :         self.min_spinbox.setValue(vmin)
     280           1 :         self.max_spinbox.setValue(vmax)

Generated by: LCOV version 1.16