LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/qobjects - item.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 139 158 88.0 %
Date: 2025-06-06 09:09:03 Functions: 24 52 46.2 %

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

Generated by: LCOV version 1.16