LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/qobjects - item.py (source / functions) Hit Total Coverage
Test: coverage.info Lines: 138 157 87.9 %
Date: 2025-09-29 10:28:29 Functions: 23 52 44.2 %

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

Generated by: LCOV version 1.16