LCOV - code coverage report
Current view: top level - src/pairinteraction_gui/qobjects - item.py (source / functions) Coverage Total Hit
Test: coverage.info Lines: 91.5 % 153 140
Test Date: 2026-11-02 09:44:19 Functions: 92.0 % 25 23

            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 2.0-1