bec-widgets 1.0.2__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. CHANGELOG.md +19 -20
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +56 -0
  4. bec_widgets/tests/__init__.py +0 -0
  5. bec_widgets/tests/utils.py +226 -0
  6. bec_widgets/utils/filter_io.py +156 -0
  7. bec_widgets/utils/widget_io.py +12 -9
  8. bec_widgets/widgets/base_classes/device_input_base.py +331 -62
  9. bec_widgets/widgets/base_classes/device_signal_input_base.py +280 -0
  10. bec_widgets/widgets/dap_combo_box/dap_combo_box_plugin.py +1 -1
  11. bec_widgets/widgets/device_combobox/device_combo_box_plugin.py +1 -1
  12. bec_widgets/widgets/device_combobox/device_combobox.py +118 -41
  13. bec_widgets/widgets/device_line_edit/device_line_edit.py +122 -59
  14. bec_widgets/widgets/device_line_edit/device_line_edit_plugin.py +1 -1
  15. bec_widgets/widgets/image/image_widget.py +7 -1
  16. bec_widgets/widgets/motor_map/motor_map_widget.py +4 -2
  17. bec_widgets/widgets/positioner_box/positioner_box.py +4 -1
  18. bec_widgets/widgets/scan_control/scan_group_box.py +3 -1
  19. bec_widgets/widgets/signal_combobox/__init__.py +0 -0
  20. bec_widgets/widgets/signal_combobox/register_signal_combobox.py +15 -0
  21. bec_widgets/widgets/signal_combobox/signal_combobox.py +115 -0
  22. bec_widgets/widgets/signal_combobox/signal_combobox.pyproject +1 -0
  23. bec_widgets/widgets/signal_combobox/signal_combobox_plugin.py +54 -0
  24. bec_widgets/widgets/signal_line_edit/__init__.py +0 -0
  25. bec_widgets/widgets/signal_line_edit/register_signal_line_edit.py +15 -0
  26. bec_widgets/widgets/signal_line_edit/signal_line_edit.py +140 -0
  27. bec_widgets/widgets/signal_line_edit/signal_line_edit.pyproject +1 -0
  28. bec_widgets/widgets/signal_line_edit/signal_line_edit_plugin.py +54 -0
  29. {bec_widgets-1.0.2.dist-info → bec_widgets-1.1.0.dist-info}/METADATA +1 -1
  30. {bec_widgets-1.0.2.dist-info → bec_widgets-1.1.0.dist-info}/RECORD +34 -20
  31. pyproject.toml +1 -1
  32. {bec_widgets-1.0.2.dist-info → bec_widgets-1.1.0.dist-info}/WHEEL +0 -0
  33. {bec_widgets-1.0.2.dist-info → bec_widgets-1.1.0.dist-info}/entry_points.txt +0 -0
  34. {bec_widgets-1.0.2.dist-info → bec_widgets-1.1.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v1.1.0 (2024-10-25)
5
+
6
+ ### Features
7
+
8
+ * feat: add filter i/o utility class ([`0350833`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0350833f36e0a7cadce4173f9b1d1fbfdf985375))
9
+
10
+ ### Refactoring
11
+
12
+ * refactor: do not flush selection upon receiving config update; allow widgetIO to receive kwargs to be able to use get_value to receive string instead of int for QComboBox ([`91959e8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/91959e82de8586934af3ebb5aaa0923930effc51))
13
+
14
+ * refactor: allow to set selection in DeviceInput; automatic update of selection on device config update; cleanup ([`5eb15b7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5eb15b785f12e30eb8ccbc56d4ad9e759a4cf5eb))
15
+
16
+ * refactor: cleanup, added device_signal for signal inputs ([`6fb2055`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6fb20552ff57978f4aeb79fd7f062f8d6b5581e7))
17
+
18
+ ### Testing
19
+
20
+ * test(scan_control): tests added for grid_scan to ensure scan_args signal validity ([`acb7902`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/acb79020d4be546efc001ff47b6f5cdba2ee9375))
21
+
22
+
4
23
  ## v1.0.2 (2024-10-22)
5
24
 
6
25
  ### Bug Fixes
@@ -151,26 +170,6 @@ is emitted multiple times. ([`f084e25`](https://gitlab.psi.ch/bec/bec_widgets/-/
151
170
 
152
171
  ## v0.113.0 (2024-10-02)
153
172
 
154
- ### Bug Fixes
155
-
156
- * fix: add is_log checks and functionality to plot_indicator_items ([`0f9953e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0f9953e8fdcf3f9b5a09f994c69edb6b34756df9))
157
-
158
- ### Features
159
-
160
- * feat: add first draft for alignment_1d GUI ([`63c24f9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/63c24f97a355edaa928b6e222909252b276bcada))
161
-
162
- * feat: add move to position button to lmfit dialog ([`281cb27`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/281cb27d8b5433e27a7ba0ca0a19e4b45b9c544f))
163
-
164
- ### Refactoring
165
-
166
- * refactor: various minor improvements for the alignment gui ([`f554f3c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f554f3c1672c4fe32968a5991dc98802556a6f3b))
167
-
168
- * refactor: allow hiding of arg/kwarg boxes ([`efe90eb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/efe90eb163e2123a5b4d0bb59f66025a569336ad))
169
-
170
173
  ### Testing
171
174
 
172
175
  * test: add tests for scan_status_callback ([`dc0c825`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/dc0c825fd594c093a24543ff803d6c6564010e92))
173
-
174
- ### Unknown
175
-
176
- * feat : Add bec_signal_proxy to handle signals with option to unblock them manually. ([`1dcfeb6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1dcfeb6cfce3c69f0c5401731d4d3f9a1981b22e))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
bec_widgets/cli/client.py CHANGED
@@ -38,6 +38,8 @@ class Widgets(str, enum.Enum):
38
38
  ResumeButton = "ResumeButton"
39
39
  RingProgressBar = "RingProgressBar"
40
40
  ScanControl = "ScanControl"
41
+ SignalComboBox = "SignalComboBox"
42
+ SignalLineEdit = "SignalLineEdit"
41
43
  StopButton = "StopButton"
42
44
  TextBox = "TextBox"
43
45
  VSCodeEditor = "VSCodeEditor"
@@ -2591,6 +2593,24 @@ class DeviceLineEdit(RPCBase):
2591
2593
  """
2592
2594
 
2593
2595
 
2596
+ class DeviceSignalInputBase(RPCBase):
2597
+ @property
2598
+ @rpc_call
2599
+ def _config_dict(self) -> "dict":
2600
+ """
2601
+ Get the configuration of the widget.
2602
+
2603
+ Returns:
2604
+ dict: The configuration of the widget.
2605
+ """
2606
+
2607
+ @rpc_call
2608
+ def _get_all_rpc(self) -> "dict":
2609
+ """
2610
+ Get all registered RPC objects.
2611
+ """
2612
+
2613
+
2594
2614
  class LMFitDialog(RPCBase):
2595
2615
  @property
2596
2616
  @rpc_call
@@ -3003,6 +3023,42 @@ class ScanControl(RPCBase):
3003
3023
  """
3004
3024
 
3005
3025
 
3026
+ class SignalComboBox(RPCBase):
3027
+ @property
3028
+ @rpc_call
3029
+ def _config_dict(self) -> "dict":
3030
+ """
3031
+ Get the configuration of the widget.
3032
+
3033
+ Returns:
3034
+ dict: The configuration of the widget.
3035
+ """
3036
+
3037
+ @rpc_call
3038
+ def _get_all_rpc(self) -> "dict":
3039
+ """
3040
+ Get all registered RPC objects.
3041
+ """
3042
+
3043
+
3044
+ class SignalLineEdit(RPCBase):
3045
+ @property
3046
+ @rpc_call
3047
+ def _config_dict(self) -> "dict":
3048
+ """
3049
+ Get the configuration of the widget.
3050
+
3051
+ Returns:
3052
+ dict: The configuration of the widget.
3053
+ """
3054
+
3055
+ @rpc_call
3056
+ def _get_all_rpc(self) -> "dict":
3057
+ """
3058
+ Get all registered RPC objects.
3059
+ """
3060
+
3061
+
3006
3062
  class StopButton(RPCBase):
3007
3063
  @property
3008
3064
  @rpc_call
File without changes
@@ -0,0 +1,226 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ from bec_lib.device import Device as BECDevice
4
+ from bec_lib.device import Positioner as BECPositioner
5
+ from bec_lib.device import ReadoutPriority
6
+ from bec_lib.devicemanager import DeviceContainer
7
+
8
+
9
+ class FakeDevice(BECDevice):
10
+ """Fake minimal positioner class for testing."""
11
+
12
+ def __init__(self, name, enabled=True, readout_priority=ReadoutPriority.MONITORED):
13
+ super().__init__(name=name)
14
+ self._enabled = enabled
15
+ self.signals = {self.name: {"value": 1.0}}
16
+ self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
17
+ self._readout_priority = readout_priority
18
+ self._config = {
19
+ "readoutPriority": "baseline",
20
+ "deviceClass": "ophyd.Device",
21
+ "deviceConfig": {},
22
+ "deviceTags": ["user device"],
23
+ "enabled": enabled,
24
+ "readOnly": False,
25
+ "name": self.name,
26
+ }
27
+
28
+ @property
29
+ def readout_priority(self):
30
+ return self._readout_priority
31
+
32
+ @readout_priority.setter
33
+ def readout_priority(self, value):
34
+ self._readout_priority = value
35
+
36
+ @property
37
+ def limits(self) -> tuple[float, float]:
38
+ return self._limits
39
+
40
+ @limits.setter
41
+ def limits(self, value: tuple[float, float]):
42
+ self._limits = value
43
+
44
+ def __contains__(self, item):
45
+ return item == self.name
46
+
47
+ @property
48
+ def _hints(self):
49
+ return [self.name]
50
+
51
+ def set_value(self, fake_value: float = 1.0) -> None:
52
+ """
53
+ Setup fake value for device readout
54
+ Args:
55
+ fake_value(float): Desired fake value
56
+ """
57
+ self.signals[self.name]["value"] = fake_value
58
+
59
+ def describe(self) -> dict:
60
+ """
61
+ Get the description of the device
62
+ Returns:
63
+ dict: Description of the device
64
+ """
65
+ return self.description
66
+
67
+
68
+ class FakePositioner(BECPositioner):
69
+
70
+ def __init__(
71
+ self,
72
+ name,
73
+ enabled=True,
74
+ limits=None,
75
+ read_value=1.0,
76
+ readout_priority=ReadoutPriority.MONITORED,
77
+ ):
78
+ super().__init__(name=name)
79
+ # self.limits = limits if limits is not None else [0.0, 0.0]
80
+ self.read_value = read_value
81
+ self.setpoint_value = read_value
82
+ self.motor_is_moving_value = 0
83
+ self._enabled = enabled
84
+ self._limits = limits
85
+ self._readout_priority = readout_priority
86
+ self.signals = {self.name: {"value": 1.0}}
87
+ self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
88
+ self._config = {
89
+ "readoutPriority": "baseline",
90
+ "deviceClass": "ophyd_devices.SimPositioner",
91
+ "deviceConfig": {"delay": 1, "tolerance": 0.01, "update_frequency": 400},
92
+ "deviceTags": ["user motors"],
93
+ "enabled": enabled,
94
+ "readOnly": False,
95
+ "name": self.name,
96
+ }
97
+ self._info = {
98
+ "signals": {
99
+ "readback": {"kind_str": "5"}, # hinted
100
+ "setpoint": {"kind_str": "1"}, # normal
101
+ "velocity": {"kind_str": "2"}, # config
102
+ }
103
+ }
104
+ self.signals = {
105
+ self.name: {"value": self.read_value},
106
+ f"{self.name}_setpoint": {"value": self.setpoint_value},
107
+ f"{self.name}_motor_is_moving": {"value": self.motor_is_moving_value},
108
+ }
109
+
110
+ @property
111
+ def readout_priority(self):
112
+ return self._readout_priority
113
+
114
+ @readout_priority.setter
115
+ def readout_priority(self, value):
116
+ self._readout_priority = value
117
+
118
+ @property
119
+ def enabled(self) -> bool:
120
+ return self._enabled
121
+
122
+ @enabled.setter
123
+ def enabled(self, value: bool):
124
+ self._enabled = value
125
+
126
+ @property
127
+ def limits(self) -> tuple[float, float]:
128
+ return self._limits
129
+
130
+ @limits.setter
131
+ def limits(self, value: tuple[float, float]):
132
+ self._limits = value
133
+
134
+ def __contains__(self, item):
135
+ return item == self.name
136
+
137
+ @property
138
+ def _hints(self):
139
+ return [self.name]
140
+
141
+ def set_value(self, fake_value: float = 1.0) -> None:
142
+ """
143
+ Setup fake value for device readout
144
+ Args:
145
+ fake_value(float): Desired fake value
146
+ """
147
+ self.read_value = fake_value
148
+
149
+ def describe(self) -> dict:
150
+ """
151
+ Get the description of the device
152
+ Returns:
153
+ dict: Description of the device
154
+ """
155
+ return self.description
156
+
157
+ @property
158
+ def precision(self):
159
+ return 3
160
+
161
+ def set_read_value(self, value):
162
+ self.read_value = value
163
+
164
+ def read(self):
165
+ return self.signals
166
+
167
+ def set_limits(self, limits):
168
+ self.limits = limits
169
+
170
+ def move(self, value, relative=False):
171
+ """Simulates moving the device to a new position."""
172
+ if relative:
173
+ self.read_value += value
174
+ else:
175
+ self.read_value = value
176
+ # Respect the limits
177
+ self.read_value = max(min(self.read_value, self.limits[1]), self.limits[0])
178
+
179
+ @property
180
+ def readback(self):
181
+ return MagicMock(get=MagicMock(return_value=self.read_value))
182
+
183
+
184
+ class Positioner(FakePositioner):
185
+ """just placeholder for testing embedded isinstance check in DeviceCombobox"""
186
+
187
+ def __init__(self, name="test", limits=None, read_value=1.0):
188
+ super().__init__(name, limits, read_value)
189
+
190
+
191
+ class Device(FakeDevice):
192
+ """just placeholder for testing embedded isinstance check in DeviceCombobox"""
193
+
194
+ def __init__(self, name, enabled=True):
195
+ super().__init__(name, enabled)
196
+
197
+
198
+ class DMMock:
199
+ def __init__(self):
200
+ self.devices = DeviceContainer()
201
+ self.enabled_devices = [device for device in self.devices if device.enabled]
202
+
203
+ def add_devives(self, devices: list):
204
+ for device in devices:
205
+ self.devices[device.name] = device
206
+
207
+
208
+ DEVICES = [
209
+ FakePositioner("samx", limits=[-10, 10], read_value=2.0),
210
+ FakePositioner("samy", limits=[-5, 5], read_value=3.0),
211
+ FakePositioner("samz", limits=[-8, 8], read_value=4.0),
212
+ FakePositioner("aptrx", limits=None, read_value=4.0),
213
+ FakePositioner("aptry", limits=None, read_value=5.0),
214
+ FakeDevice("gauss_bpm"),
215
+ FakeDevice("gauss_adc1"),
216
+ FakeDevice("gauss_adc2"),
217
+ FakeDevice("gauss_adc3"),
218
+ FakeDevice("bpm4i"),
219
+ FakeDevice("bpm3a"),
220
+ FakeDevice("bpm3i"),
221
+ FakeDevice("eiger", readout_priority=ReadoutPriority.ASYNC),
222
+ FakeDevice("waveform1d"),
223
+ FakeDevice("async_device", readout_priority=ReadoutPriority.ASYNC),
224
+ Positioner("test", limits=[-10, 10], read_value=2.0),
225
+ Device("test_device"),
226
+ ]
@@ -0,0 +1,156 @@
1
+ """Module for handling filter I/O operations in BEC Widgets for input fields.
2
+ These operations include filtering device/signal names and/or device types.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from bec_lib.logger import bec_logger
8
+ from qtpy.QtCore import QStringListModel
9
+ from qtpy.QtWidgets import QComboBox, QCompleter, QLineEdit
10
+
11
+ logger = bec_logger.logger
12
+
13
+
14
+ class WidgetFilterHandler(ABC):
15
+ """Abstract base class for widget filter handlers"""
16
+
17
+ @abstractmethod
18
+ def set_selection(self, widget, selection: list) -> None:
19
+ """Set the filtered_selection for the widget
20
+
21
+ Args:
22
+ selection (list): Filtered selection of items
23
+ """
24
+
25
+ @abstractmethod
26
+ def check_input(self, widget, text: str) -> bool:
27
+ """Check if the input text is in the filtered selection
28
+
29
+ Args:
30
+ widget: Widget instance
31
+ text (str): Input text
32
+
33
+ Returns:
34
+ bool: True if the input text is in the filtered selection
35
+ """
36
+
37
+
38
+ class LineEditFilterHandler(WidgetFilterHandler):
39
+ """Handler for QLineEdit widget"""
40
+
41
+ def set_selection(self, widget: QLineEdit, selection: list) -> None:
42
+ """Set the selection for the widget to the completer model
43
+
44
+ Args:
45
+ widget (QLineEdit): The QLineEdit widget
46
+ selection (list): Filtered selection of items
47
+ """
48
+ if not isinstance(widget.completer, QCompleter):
49
+ completer = QCompleter(widget)
50
+ widget.setCompleter(completer)
51
+ widget.completer.setModel(QStringListModel(selection, widget))
52
+
53
+ def check_input(self, widget: QLineEdit, text: str) -> bool:
54
+ """Check if the input text is in the filtered selection
55
+
56
+ Args:
57
+ widget (QLineEdit): The QLineEdit widget
58
+ text (str): Input text
59
+
60
+ Returns:
61
+ bool: True if the input text is in the filtered selection
62
+ """
63
+ model = widget.completer.model()
64
+ model_data = [model.data(model.index(i)) for i in range(model.rowCount())]
65
+ return text in model_data
66
+
67
+
68
+ class ComboBoxFilterHandler(WidgetFilterHandler):
69
+ """Handler for QComboBox widget"""
70
+
71
+ def set_selection(self, widget: QComboBox, selection: list) -> None:
72
+ """Set the selection for the widget to the completer model
73
+
74
+ Args:
75
+ widget (QComboBox): The QComboBox widget
76
+ selection (list): Filtered selection of items
77
+ """
78
+ widget.clear()
79
+ widget.addItems(selection)
80
+
81
+ def check_input(self, widget: QComboBox, text: str) -> bool:
82
+ """Check if the input text is in the filtered selection
83
+
84
+ Args:
85
+ widget (QComboBox): The QComboBox widget
86
+ text (str): Input text
87
+
88
+ Returns:
89
+ bool: True if the input text is in the filtered selection
90
+ """
91
+ return text in [widget.itemText(i) for i in range(widget.count())]
92
+
93
+
94
+ class FilterIO:
95
+ """Public interface to set filters for input widgets.
96
+ It supports the list of widgets stored in class attribute _handlers.
97
+ """
98
+
99
+ _handlers = {QLineEdit: LineEditFilterHandler, QComboBox: ComboBoxFilterHandler}
100
+
101
+ @staticmethod
102
+ def set_selection(widget, selection: list, ignore_errors=True):
103
+ """
104
+ Retrieve value from the widget instance.
105
+
106
+ Args:
107
+ widget: Widget instance.
108
+ selection(list): List of filtered selection items.
109
+ ignore_errors(bool, optional): Whether to ignore if no handler is found.
110
+ """
111
+ handler_class = FilterIO._find_handler(widget)
112
+ if handler_class:
113
+ return handler_class().set_selection(widget=widget, selection=selection)
114
+ if not ignore_errors:
115
+ raise ValueError(
116
+ f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
117
+ )
118
+ return None
119
+
120
+ @staticmethod
121
+ def check_input(widget, text: str, ignore_errors=True):
122
+ """
123
+ Check if the input text is in the filtered selection.
124
+
125
+ Args:
126
+ widget: Widget instance.
127
+ text(str): Input text.
128
+ ignore_errors(bool, optional): Whether to ignore if no handler is found.
129
+
130
+ Returns:
131
+ bool: True if the input text is in the filtered selection.
132
+ """
133
+ handler_class = FilterIO._find_handler(widget)
134
+ if handler_class:
135
+ return handler_class().check_input(widget=widget, text=text)
136
+ if not ignore_errors:
137
+ raise ValueError(
138
+ f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
139
+ )
140
+ return None
141
+
142
+ @staticmethod
143
+ def _find_handler(widget):
144
+ """
145
+ Find the appropriate handler for the widget by checking its base classes.
146
+
147
+ Args:
148
+ widget: Widget instance.
149
+
150
+ Returns:
151
+ handler_class: The handler class if found, otherwise None.
152
+ """
153
+ for base in type(widget).__mro__:
154
+ if base in FilterIO._handlers:
155
+ return FilterIO._handlers[base]
156
+ return None
@@ -1,5 +1,6 @@
1
1
  # pylint: disable=no-name-in-module
2
2
  from abc import ABC, abstractmethod
3
+ from typing import Literal
3
4
 
4
5
  from qtpy.QtWidgets import (
5
6
  QApplication,
@@ -20,7 +21,7 @@ class WidgetHandler(ABC):
20
21
  """Abstract base class for all widget handlers."""
21
22
 
22
23
  @abstractmethod
23
- def get_value(self, widget: QWidget):
24
+ def get_value(self, widget: QWidget, **kwargs):
24
25
  """Retrieve value from the widget instance."""
25
26
 
26
27
  @abstractmethod
@@ -31,7 +32,7 @@ class WidgetHandler(ABC):
31
32
  class LineEditHandler(WidgetHandler):
32
33
  """Handler for QLineEdit widgets."""
33
34
 
34
- def get_value(self, widget: QLineEdit) -> str:
35
+ def get_value(self, widget: QLineEdit, **kwargs) -> str:
35
36
  return widget.text()
36
37
 
37
38
  def set_value(self, widget: QLineEdit, value: str) -> None:
@@ -41,7 +42,9 @@ class LineEditHandler(WidgetHandler):
41
42
  class ComboBoxHandler(WidgetHandler):
42
43
  """Handler for QComboBox widgets."""
43
44
 
44
- def get_value(self, widget: QComboBox) -> int:
45
+ def get_value(self, widget: QComboBox, as_string: bool = False, **kwargs) -> int | str:
46
+ if as_string is True:
47
+ return widget.currentText()
45
48
  return widget.currentIndex()
46
49
 
47
50
  def set_value(self, widget: QComboBox, value: int | str) -> None:
@@ -54,7 +57,7 @@ class ComboBoxHandler(WidgetHandler):
54
57
  class TableWidgetHandler(WidgetHandler):
55
58
  """Handler for QTableWidget widgets."""
56
59
 
57
- def get_value(self, widget: QTableWidget) -> list:
60
+ def get_value(self, widget: QTableWidget, **kwargs) -> list:
58
61
  return [
59
62
  [
60
63
  widget.item(row, col).text() if widget.item(row, col) else ""
@@ -73,7 +76,7 @@ class TableWidgetHandler(WidgetHandler):
73
76
  class SpinBoxHandler(WidgetHandler):
74
77
  """Handler for QSpinBox and QDoubleSpinBox widgets."""
75
78
 
76
- def get_value(self, widget):
79
+ def get_value(self, widget, **kwargs):
77
80
  return widget.value()
78
81
 
79
82
  def set_value(self, widget, value):
@@ -83,7 +86,7 @@ class SpinBoxHandler(WidgetHandler):
83
86
  class CheckBoxHandler(WidgetHandler):
84
87
  """Handler for QCheckBox widgets."""
85
88
 
86
- def get_value(self, widget):
89
+ def get_value(self, widget, **kwargs):
87
90
  return widget.isChecked()
88
91
 
89
92
  def set_value(self, widget, value):
@@ -93,7 +96,7 @@ class CheckBoxHandler(WidgetHandler):
93
96
  class LabelHandler(WidgetHandler):
94
97
  """Handler for QLabel widgets."""
95
98
 
96
- def get_value(self, widget):
99
+ def get_value(self, widget, **kwargs):
97
100
  return widget.text()
98
101
 
99
102
  def set_value(self, widget, value):
@@ -114,7 +117,7 @@ class WidgetIO:
114
117
  }
115
118
 
116
119
  @staticmethod
117
- def get_value(widget, ignore_errors=False):
120
+ def get_value(widget, ignore_errors=False, **kwargs):
118
121
  """
119
122
  Retrieve value from the widget instance.
120
123
 
@@ -124,7 +127,7 @@ class WidgetIO:
124
127
  """
125
128
  handler_class = WidgetIO._find_handler(widget)
126
129
  if handler_class:
127
- return handler_class().get_value(widget) # Instantiate the handler
130
+ return handler_class().get_value(widget, **kwargs) # Instantiate the handler
128
131
  if not ignore_errors:
129
132
  raise ValueError(f"No handler for widget type: {type(widget)}")
130
133
  return None