bec-widgets 0.70.0__py3-none-any.whl → 0.71.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.
- CHANGELOG.md +42 -54
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +19 -0
- bec_widgets/utils/widget_io.py +18 -2
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py +12 -4
- bec_widgets/widgets/device_inputs/device_input_base.py +5 -2
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py +18 -5
- bec_widgets/widgets/scan_control/scan_control.py +133 -365
- bec_widgets/widgets/scan_control/scan_group_box.py +223 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/RECORD +22 -17
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/WHEEL +1 -1
- docs/user/widgets/scan_control.gif +0 -0
- docs/user/widgets/scan_control.md +35 -0
- pyproject.toml +1 -1
- tests/end-2-end/test_scan_control_e2e.py +71 -0
- tests/unit_tests/test_device_input_base.py +4 -4
- tests/unit_tests/test_device_input_widgets.py +10 -10
- tests/unit_tests/test_scan_control.py +255 -115
- tests/unit_tests/test_scan_control_group_box.py +160 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-0.70.0.dist-info → bec_widgets-0.71.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,53 +1,37 @@
|
|
1
|
+
import qdarktheme
|
1
2
|
from bec_lib.endpoints import MessageEndpoints
|
2
3
|
from qtpy.QtWidgets import (
|
3
4
|
QApplication,
|
4
|
-
QCheckBox,
|
5
5
|
QComboBox,
|
6
|
-
QDoubleSpinBox,
|
7
|
-
QFrame,
|
8
6
|
QGridLayout,
|
9
7
|
QGroupBox,
|
10
|
-
QHBoxLayout,
|
11
|
-
QHeaderView,
|
12
|
-
QLabel,
|
13
|
-
QLayout,
|
14
|
-
QLineEdit,
|
15
8
|
QPushButton,
|
16
|
-
|
17
|
-
QTableWidget,
|
18
|
-
QTableWidgetItem,
|
9
|
+
QSizePolicy,
|
19
10
|
QVBoxLayout,
|
20
11
|
QWidget,
|
21
12
|
)
|
22
13
|
|
23
|
-
from bec_widgets.utils
|
24
|
-
from bec_widgets.
|
14
|
+
from bec_widgets.utils import BECConnector
|
15
|
+
from bec_widgets.widgets.buttons.stop_button.stop_button import StopButton
|
16
|
+
from bec_widgets.widgets.scan_control.scan_group_box import ScanGroupBox
|
25
17
|
|
26
18
|
|
27
|
-
class
|
28
|
-
DEVICE = "device"
|
29
|
-
FLOAT = "float"
|
30
|
-
INT = "int"
|
31
|
-
BOOL = "bool"
|
32
|
-
STR = "str"
|
19
|
+
class ScanControl(BECConnector, QWidget):
|
33
20
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
ScanArgType.INT: QSpinBox,
|
40
|
-
ScanArgType.BOOL: QCheckBox,
|
41
|
-
ScanArgType.STR: QLineEdit,
|
42
|
-
}
|
43
|
-
|
44
|
-
def __init__(self, parent=None, client=None, allowed_scans=None):
|
45
|
-
super().__init__(parent)
|
21
|
+
def __init__(
|
22
|
+
self, parent=None, client=None, gui_id: str | None = None, allowed_scans: list | None = None
|
23
|
+
):
|
24
|
+
super().__init__(client=client, gui_id=gui_id)
|
25
|
+
QWidget.__init__(self, parent=parent)
|
46
26
|
|
47
27
|
# Client from BEC + shortcuts to device manager and scans
|
48
|
-
self.
|
49
|
-
|
50
|
-
|
28
|
+
self.get_bec_shortcuts()
|
29
|
+
|
30
|
+
# Main layout
|
31
|
+
self.layout = QVBoxLayout(self)
|
32
|
+
self.arg_box = None
|
33
|
+
self.kwarg_boxes = []
|
34
|
+
self.expert_mode = False # TODO implement in the future versions
|
51
35
|
|
52
36
|
# Scan list - allowed scans for the GUI
|
53
37
|
self.allowed_scans = allowed_scans
|
@@ -56,389 +40,173 @@ class ScanControl(QWidget):
|
|
56
40
|
self._init_UI()
|
57
41
|
|
58
42
|
def _init_UI(self):
|
59
|
-
|
43
|
+
"""
|
44
|
+
Initializes the UI of the scan control widget. Create the top box for scan selection and populate scans to main combobox.
|
45
|
+
"""
|
60
46
|
|
61
47
|
# Scan selection group box
|
62
|
-
self.scan_selection_group =
|
63
|
-
self.
|
64
|
-
self.
|
65
|
-
self.button_run_scan = QPushButton("Run Scan", self.scan_selection_group)
|
66
|
-
self.scan_selection_layout.addWidget(self.comboBox_scan_selection)
|
67
|
-
self.scan_selection_layout.addWidget(self.button_run_scan)
|
68
|
-
self.verticalLayout.addWidget(self.scan_selection_group)
|
69
|
-
|
70
|
-
# Scan control group box
|
71
|
-
self.scan_control_group = QGroupBox("Scan Control", self)
|
72
|
-
self.scan_control_layout = QVBoxLayout(self.scan_control_group)
|
73
|
-
self.verticalLayout.addWidget(self.scan_control_group)
|
74
|
-
|
75
|
-
# Kwargs layout - just placeholder
|
76
|
-
self.kwargs_layout = QGridLayout()
|
77
|
-
self.scan_control_layout.addLayout(self.kwargs_layout)
|
78
|
-
|
79
|
-
# 1st Separator
|
80
|
-
self.add_horizontal_separator(self.scan_control_layout)
|
81
|
-
|
82
|
-
# Buttons
|
83
|
-
self.button_layout = QHBoxLayout()
|
84
|
-
self.pushButton_add_bundle = QPushButton("Add Bundle", self.scan_control_group)
|
85
|
-
self.pushButton_add_bundle.clicked.connect(self.add_bundle)
|
86
|
-
self.pushButton_remove_bundle = QPushButton("Remove Bundle", self.scan_control_group)
|
87
|
-
self.pushButton_remove_bundle.clicked.connect(self.remove_bundle)
|
88
|
-
self.button_layout.addWidget(self.pushButton_add_bundle)
|
89
|
-
self.button_layout.addWidget(self.pushButton_remove_bundle)
|
90
|
-
self.scan_control_layout.addLayout(self.button_layout)
|
91
|
-
|
92
|
-
# 2nd Separator
|
93
|
-
self.add_horizontal_separator(self.scan_control_layout)
|
94
|
-
|
95
|
-
# Initialize the QTableWidget for args
|
96
|
-
self.args_table = QTableWidget()
|
97
|
-
self.args_table.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
|
98
|
-
|
99
|
-
self.scan_control_layout.addWidget(self.args_table)
|
48
|
+
self.scan_selection_group = self.create_scan_selection_group()
|
49
|
+
self.scan_selection_group.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
50
|
+
self.layout.addWidget(self.scan_selection_group)
|
100
51
|
|
101
52
|
# Connect signals
|
102
53
|
self.comboBox_scan_selection.currentIndexChanged.connect(self.on_scan_selected)
|
103
54
|
self.button_run_scan.clicked.connect(self.run_scan)
|
55
|
+
self.button_add_bundle.clicked.connect(self.add_arg_bundle)
|
56
|
+
self.button_remove_bundle.clicked.connect(self.remove_arg_bundle)
|
104
57
|
|
105
58
|
# Initialize scan selection
|
106
59
|
self.populate_scans()
|
107
60
|
|
108
|
-
def
|
61
|
+
def create_scan_selection_group(self) -> QGroupBox:
|
109
62
|
"""
|
110
|
-
|
63
|
+
Creates the scan selection group box with combobox to select the scan and start/stop button.
|
111
64
|
|
112
|
-
|
113
|
-
|
114
|
-
"""
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
65
|
+
Returns:
|
66
|
+
QGroupBox: Group box containing the scan selection widgets.
|
67
|
+
"""
|
68
|
+
|
69
|
+
scan_selection_group = QGroupBox("Scan Selection", self)
|
70
|
+
self.scan_selection_layout = QGridLayout(scan_selection_group)
|
71
|
+
self.comboBox_scan_selection = QComboBox(scan_selection_group)
|
72
|
+
# Run button
|
73
|
+
self.button_run_scan = QPushButton("Start", scan_selection_group)
|
74
|
+
self.button_run_scan.setStyleSheet("background-color: #559900; color: white")
|
75
|
+
# Stop button
|
76
|
+
self.button_stop_scan = StopButton(parent=scan_selection_group)
|
77
|
+
# Add bundle button
|
78
|
+
self.button_add_bundle = QPushButton("Add Bundle", scan_selection_group)
|
79
|
+
# Remove bundle button
|
80
|
+
self.button_remove_bundle = QPushButton("Remove Bundle", scan_selection_group)
|
81
|
+
|
82
|
+
self.scan_selection_layout.addWidget(self.comboBox_scan_selection, 0, 0, 1, 2)
|
83
|
+
self.scan_selection_layout.addWidget(self.button_run_scan, 1, 0)
|
84
|
+
self.scan_selection_layout.addWidget(self.button_stop_scan, 1, 1)
|
85
|
+
self.scan_selection_layout.addWidget(self.button_add_bundle, 2, 0)
|
86
|
+
self.scan_selection_layout.addWidget(self.button_remove_bundle, 2, 1)
|
87
|
+
|
88
|
+
return scan_selection_group
|
119
89
|
|
120
90
|
def populate_scans(self):
|
121
|
-
"""Populates the scan selection combo box with available scans"""
|
122
|
-
self.available_scans = self.client.
|
91
|
+
"""Populates the scan selection combo box with available scans from BEC session."""
|
92
|
+
self.available_scans = self.client.connector.get(
|
93
|
+
MessageEndpoints.available_scans()
|
94
|
+
).resource
|
123
95
|
if self.allowed_scans is None:
|
124
|
-
|
96
|
+
supported_scans = ["ScanBase", "SyncFlyScanBase", "AsyncFlyScanBase"]
|
97
|
+
allowed_scans = [
|
98
|
+
scan_name
|
99
|
+
for scan_name, scan_info in self.available_scans.items()
|
100
|
+
if scan_info["base_class"] in supported_scans and len(scan_info["gui_config"]) > 0
|
101
|
+
]
|
102
|
+
|
125
103
|
else:
|
126
104
|
allowed_scans = self.allowed_scans
|
127
|
-
# TODO check parent class is ScanBase -> filter out the scans not relevant for GUI
|
128
105
|
self.comboBox_scan_selection.addItems(allowed_scans)
|
129
106
|
|
130
107
|
def on_scan_selected(self):
|
131
108
|
"""Callback for scan selection combo box"""
|
109
|
+
self.reset_layout()
|
132
110
|
selected_scan_name = self.comboBox_scan_selection.currentText()
|
133
111
|
selected_scan_info = self.available_scans.get(selected_scan_name, {})
|
134
112
|
|
135
|
-
|
136
|
-
|
137
|
-
self.
|
138
|
-
|
139
|
-
# Args section
|
140
|
-
self.generate_args_input_fields(selected_scan_info)
|
141
|
-
|
142
|
-
def add_labels_to_layout(self, labels: list, grid_layout: QGridLayout) -> None:
|
143
|
-
"""
|
144
|
-
Adds labels to the given grid layout as a separate row.
|
145
|
-
|
146
|
-
Args:
|
147
|
-
labels (list): List of label names to add.
|
148
|
-
grid_layout (QGridLayout): The grid layout to which labels will be added.
|
149
|
-
"""
|
150
|
-
row_index = grid_layout.rowCount() # Get the next available row
|
151
|
-
for column_index, label_name in enumerate(labels):
|
152
|
-
label = QLabel(label_name.capitalize(), self.scan_control_group)
|
153
|
-
# Add the label to the grid layout at the calculated row and current column
|
154
|
-
grid_layout.addWidget(label, row_index, column_index)
|
155
|
-
|
156
|
-
def add_labels_to_table(
|
157
|
-
self, labels: list, table: QTableWidget
|
158
|
-
) -> None: # TODO could be moved to BECTable
|
159
|
-
"""
|
160
|
-
Adds labels to the given table widget as a header row.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
labels(list): List of label names to add.
|
164
|
-
table(QTableWidget): The table widget to which labels will be added.
|
165
|
-
"""
|
166
|
-
table.setColumnCount(len(labels))
|
167
|
-
table.setHorizontalHeaderLabels(labels)
|
168
|
-
|
169
|
-
def generate_args_input_fields(self, scan_info: dict) -> None:
|
170
|
-
"""
|
171
|
-
Generates input fields for args.
|
113
|
+
gui_config = selected_scan_info.get("gui_config", {})
|
114
|
+
self.arg_group = gui_config.get("arg_group", None)
|
115
|
+
self.kwarg_groups = gui_config.get("kwarg_groups", None)
|
172
116
|
|
173
|
-
|
174
|
-
|
175
|
-
|
117
|
+
if self.arg_box is None:
|
118
|
+
self.button_add_bundle.setEnabled(False)
|
119
|
+
self.button_remove_bundle.setEnabled(False)
|
176
120
|
|
177
|
-
|
178
|
-
|
121
|
+
if len(self.arg_group["arg_inputs"]) > 0:
|
122
|
+
self.button_add_bundle.setEnabled(True)
|
123
|
+
self.button_remove_bundle.setEnabled(True)
|
124
|
+
self.add_arg_group(self.arg_group)
|
125
|
+
if len(self.kwarg_groups) > 0:
|
126
|
+
self.add_kwargs_boxes(self.kwarg_groups)
|
179
127
|
|
180
|
-
|
181
|
-
self.
|
128
|
+
self.update()
|
129
|
+
self.adjustSize()
|
182
130
|
|
183
|
-
|
184
|
-
self.add_labels_to_table(list(self.arg_input.keys()), self.args_table)
|
185
|
-
|
186
|
-
# add minimum number of args rows
|
187
|
-
if self.arg_size_min is not None:
|
188
|
-
for i in range(self.arg_size_min):
|
189
|
-
self.add_bundle()
|
190
|
-
|
191
|
-
def generate_kwargs_input_fields(self, scan_info: dict) -> None:
|
131
|
+
def add_kwargs_boxes(self, groups: list):
|
192
132
|
"""
|
193
|
-
|
133
|
+
Adds the given gui_groups to the scan control layout.
|
194
134
|
|
195
135
|
Args:
|
196
|
-
|
197
|
-
"""
|
198
|
-
# Create a new kwarg layout to replace the old one - this is necessary because otherwise row count is not reseted
|
199
|
-
self.clear_and_delete_layout(self.kwargs_layout)
|
200
|
-
self.kwargs_layout = self.create_new_grid_layout() # Create new grid layout
|
201
|
-
self.scan_control_layout.insertLayout(0, self.kwargs_layout)
|
202
|
-
|
203
|
-
# Get signature
|
204
|
-
signature = scan_info.get("signature", [])
|
205
|
-
|
206
|
-
# Extract kwargs from the converted signature
|
207
|
-
kwargs = [param["name"] for param in signature if param["kind"] == "KEYWORD_ONLY"]
|
208
|
-
|
209
|
-
# Add labels
|
210
|
-
self.add_labels_to_layout(kwargs, self.kwargs_layout)
|
211
|
-
|
212
|
-
# Add widgets
|
213
|
-
widgets = self.generate_widgets_from_signature(kwargs, signature)
|
214
|
-
|
215
|
-
self.add_widgets_row_to_layout(self.kwargs_layout, widgets)
|
216
|
-
|
217
|
-
def generate_widgets_from_signature(self, items: list, signature: dict = None) -> list:
|
136
|
+
groups(list): List of dictionaries containing the gui_group information.
|
218
137
|
"""
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
138
|
+
for group in groups:
|
139
|
+
box = ScanGroupBox(box_type="kwargs", config=group)
|
140
|
+
box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
141
|
+
self.layout.addWidget(box)
|
142
|
+
self.kwarg_boxes.append(box)
|
224
143
|
|
225
|
-
|
226
|
-
list: List of widgets created from the given items.
|
227
|
-
"""
|
228
|
-
widgets = [] # Initialize an empty list to hold the widgets
|
229
|
-
|
230
|
-
for item in items:
|
231
|
-
if signature:
|
232
|
-
# If a signature is provided, extract type and name from it
|
233
|
-
kwarg_info = next((info for info in signature if info["name"] == item), None)
|
234
|
-
if kwarg_info:
|
235
|
-
item_type = kwarg_info.get("annotation", "_empty")
|
236
|
-
item_name = item
|
237
|
-
else:
|
238
|
-
# If no signature is provided, assume the item is a tuple of (name, type)
|
239
|
-
item_name, item_type = item
|
240
|
-
|
241
|
-
widget_class = self.WIDGET_HANDLER.get(item_type, None)
|
242
|
-
if widget_class is None:
|
243
|
-
print(f"Unsupported annotation '{item_type}' for parameter '{item_name}'")
|
244
|
-
continue
|
245
|
-
|
246
|
-
# Instantiate the widget and set some properties if necessary
|
247
|
-
widget = widget_class()
|
248
|
-
|
249
|
-
# set high default range for spin boxes #TODO can be linked to motor/device limits from BEC
|
250
|
-
if isinstance(widget, (QSpinBox, QDoubleSpinBox)):
|
251
|
-
widget.setRange(-9999, 9999)
|
252
|
-
widget.setValue(0)
|
253
|
-
# Add the widget to the list
|
254
|
-
widgets.append(widget)
|
255
|
-
|
256
|
-
return widgets
|
257
|
-
|
258
|
-
def set_args_table_limits(self, table: QTableWidget, scan_info: dict) -> None:
|
259
|
-
# Get bundle info
|
260
|
-
arg_bundle_size = scan_info.get("arg_bundle_size", {})
|
261
|
-
self.arg_size_min = arg_bundle_size.get("min", 1)
|
262
|
-
self.arg_size_max = arg_bundle_size.get("max", None)
|
263
|
-
|
264
|
-
# Clear the previous input fields
|
265
|
-
table.setRowCount(0) # Wipe table
|
266
|
-
|
267
|
-
def add_widgets_row_to_layout(
|
268
|
-
self, grid_layout: QGridLayout, widgets: list, row_index: int = None
|
269
|
-
) -> None:
|
144
|
+
def add_arg_group(self, group: dict):
|
270
145
|
"""
|
271
|
-
Adds
|
146
|
+
Adds the given gui_groups to the scan control layout.
|
272
147
|
|
273
148
|
Args:
|
274
|
-
grid_layout (QGridLayout): The grid layout to which widgets will be added.
|
275
|
-
items (list): List of parameter names to create widgets for.
|
276
|
-
row_index (int): The row index where the widgets should be added.
|
277
149
|
"""
|
278
|
-
|
279
|
-
|
280
|
-
|
150
|
+
self.arg_box = ScanGroupBox(box_type="args", config=group)
|
151
|
+
self.arg_box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
152
|
+
self.layout.addWidget(self.arg_box)
|
281
153
|
|
282
|
-
|
283
|
-
|
284
|
-
grid_layout.addWidget(widget, row_index, column_index)
|
285
|
-
|
286
|
-
def add_widgets_row_to_table(
|
287
|
-
self, table_widget: QTableWidget, widgets: list, row_index: int = None
|
288
|
-
) -> None:
|
289
|
-
"""
|
290
|
-
Adds a row of widgets to the given QTableWidget.
|
154
|
+
def add_arg_bundle(self):
|
155
|
+
self.arg_box.add_widget_bundle()
|
291
156
|
|
292
|
-
|
293
|
-
|
294
|
-
widgets (list): List of widgets to add to the table.
|
295
|
-
row_index (int): The row index where the widgets should be added. If None, add to the end.
|
296
|
-
"""
|
297
|
-
# If row_index is not specified, add to the end of the table
|
298
|
-
if row_index is None or row_index > table_widget.rowCount():
|
299
|
-
row_index = table_widget.rowCount()
|
300
|
-
if self.arg_size_max is not None: # ensure the max args size is not exceeded
|
301
|
-
if row_index >= self.arg_size_max:
|
302
|
-
return
|
303
|
-
table_widget.insertRow(row_index)
|
304
|
-
|
305
|
-
for column_index, widget in enumerate(widgets):
|
306
|
-
# If the widget is a subclass of QWidget, use setCellWidget
|
307
|
-
if issubclass(type(widget), QWidget):
|
308
|
-
table_widget.setCellWidget(row_index, column_index, widget)
|
309
|
-
else:
|
310
|
-
# Otherwise, assume it's a string or some other value that should be displayed as text
|
311
|
-
item = QTableWidgetItem(str(widget))
|
312
|
-
table_widget.setItem(row_index, column_index, item)
|
313
|
-
|
314
|
-
# Optionally, adjust the row height based on the content #TODO decide if needed
|
315
|
-
table_widget.setRowHeight(
|
316
|
-
row_index,
|
317
|
-
max(widget.sizeHint().height() for widget in widgets if isinstance(widget, QWidget)),
|
318
|
-
)
|
319
|
-
|
320
|
-
def remove_last_row_from_table(self, table_widget: QTableWidget) -> None:
|
321
|
-
"""
|
322
|
-
Removes the last row from the given QTableWidget until only one row is left.
|
323
|
-
|
324
|
-
Args:
|
325
|
-
table_widget (QTableWidget): The table widget from which the last row will be removed.
|
326
|
-
"""
|
327
|
-
row_count = table_widget.rowCount()
|
328
|
-
if (
|
329
|
-
row_count > self.arg_size_min
|
330
|
-
): # Check to ensure there is a minimum number of rows remaining
|
331
|
-
table_widget.removeRow(row_count - 1)
|
332
|
-
|
333
|
-
def create_new_grid_layout(self):
|
334
|
-
new_layout = QGridLayout()
|
335
|
-
# TODO maybe setup other layouts properties here?
|
336
|
-
return new_layout
|
337
|
-
|
338
|
-
def clear_and_delete_layout(self, layout: QLayout):
|
339
|
-
"""
|
340
|
-
Clears and deletes the given layout and all its child widgets.
|
157
|
+
def remove_arg_bundle(self):
|
158
|
+
self.arg_box.remove_widget_bundle()
|
341
159
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
widget.deleteLater()
|
351
|
-
else:
|
352
|
-
sub_layout = item.layout()
|
353
|
-
if sub_layout:
|
354
|
-
self.clear_and_delete_layout(sub_layout)
|
355
|
-
layout.deleteLater()
|
356
|
-
|
357
|
-
def add_bundle(self) -> None:
|
358
|
-
"""Adds a new bundle to the scan control layout"""
|
359
|
-
# Get widgets used for particular scan and save them to be able to use for adding bundles
|
360
|
-
args_widgets = self.generate_widgets_from_signature(
|
361
|
-
self.arg_input.items()
|
362
|
-
) # TODO decide if make sense to put widget list into method parameters
|
363
|
-
|
364
|
-
# Add first widgets row to the table
|
365
|
-
self.add_widgets_row_to_table(self.args_table, args_widgets)
|
366
|
-
|
367
|
-
def remove_bundle(self) -> None:
|
368
|
-
"""Removes the last bundle from the scan control layout"""
|
369
|
-
self.remove_last_row_from_table(self.args_table)
|
370
|
-
|
371
|
-
def extract_kwargs_from_grid_row(self, grid_layout: QGridLayout, row: int) -> dict:
|
372
|
-
kwargs = {}
|
373
|
-
for column in range(grid_layout.columnCount()):
|
374
|
-
label_item = grid_layout.itemAtPosition(row, column)
|
375
|
-
if label_item is not None:
|
376
|
-
label_widget = label_item.widget()
|
377
|
-
if isinstance(label_widget, QLabel):
|
378
|
-
key = label_widget.text()
|
379
|
-
|
380
|
-
# The corresponding value widget is in the next row
|
381
|
-
value_item = grid_layout.itemAtPosition(row + 1, column)
|
382
|
-
if value_item is not None:
|
383
|
-
value_widget = value_item.widget()
|
384
|
-
# Use WidgetIO.get_value to extract the value
|
385
|
-
value = WidgetIO.get_value(value_widget)
|
386
|
-
kwargs[key] = value
|
387
|
-
return kwargs
|
388
|
-
|
389
|
-
def extract_args_from_table(self, table: QTableWidget) -> list:
|
390
|
-
"""
|
391
|
-
Extracts the arguments from the given table widget.
|
160
|
+
def reset_layout(self):
|
161
|
+
"""Clears the scan control layout from GuiGroups and ArgGroups boxes."""
|
162
|
+
if self.arg_box is not None:
|
163
|
+
self.layout.removeWidget(self.arg_box)
|
164
|
+
self.arg_box.deleteLater()
|
165
|
+
self.arg_box = None
|
166
|
+
if self.kwarg_boxes != []:
|
167
|
+
self.remove_kwarg_boxes()
|
392
168
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
row_args = []
|
399
|
-
for column in range(table.columnCount()):
|
400
|
-
widget = table.cellWidget(row, column)
|
401
|
-
if widget:
|
402
|
-
if isinstance(widget, QLineEdit): # special case for QLineEdit for Devices
|
403
|
-
value = widget.text().lower()
|
404
|
-
if value in self.dev:
|
405
|
-
value = getattr(self.dev, value)
|
406
|
-
else:
|
407
|
-
raise ValueError(f"The device '{value}' is not recognized.")
|
408
|
-
else:
|
409
|
-
value = WidgetIO.get_value(widget)
|
410
|
-
row_args.append(value)
|
411
|
-
args.extend(row_args)
|
412
|
-
return args
|
169
|
+
def remove_kwarg_boxes(self):
|
170
|
+
for box in self.kwarg_boxes:
|
171
|
+
self.layout.removeWidget(box)
|
172
|
+
box.deleteLater()
|
173
|
+
self.kwarg_boxes = []
|
413
174
|
|
414
175
|
def run_scan(self):
|
415
|
-
|
416
|
-
kwargs = {
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
args = self.extract_args_from_table(self.args_table)
|
423
|
-
|
424
|
-
# Convert args to lowercase if they are strings
|
425
|
-
args = [arg.lower() if isinstance(arg, str) else arg for arg in args]
|
426
|
-
|
427
|
-
# Execute the scan
|
176
|
+
args = []
|
177
|
+
kwargs = {}
|
178
|
+
if self.arg_box is not None:
|
179
|
+
args = self.arg_box.get_parameters()
|
180
|
+
for box in self.kwarg_boxes:
|
181
|
+
box_kwargs = box.get_parameters()
|
182
|
+
kwargs.update(box_kwargs)
|
428
183
|
scan_function = getattr(self.scans, self.comboBox_scan_selection.currentText())
|
429
184
|
if callable(scan_function):
|
430
185
|
scan_function(*args, **kwargs)
|
431
186
|
|
187
|
+
def cleanup(self):
|
188
|
+
self.button_stop_scan.cleanup()
|
189
|
+
if self.arg_box:
|
190
|
+
for widget in self.arg_box.widgets:
|
191
|
+
if hasattr(widget, "cleanup"):
|
192
|
+
widget.cleanup()
|
193
|
+
for kwarg_box in self.kwarg_boxes:
|
194
|
+
for widget in kwarg_box.widgets:
|
195
|
+
if hasattr(widget, "cleanup"):
|
196
|
+
widget.cleanup()
|
197
|
+
super().cleanup()
|
198
|
+
|
199
|
+
def closeEvent(self, event):
|
200
|
+
self.cleanup()
|
201
|
+
QWidget().closeEvent(event)
|
202
|
+
|
432
203
|
|
433
204
|
# Application example
|
434
205
|
if __name__ == "__main__": # pragma: no cover
|
435
|
-
# BECclient global variables
|
436
|
-
client = BECDispatcher().client
|
437
|
-
client.start()
|
438
|
-
|
439
206
|
app = QApplication([])
|
440
|
-
scan_control = ScanControl(
|
207
|
+
scan_control = ScanControl()
|
441
208
|
|
209
|
+
qdarktheme.setup_theme("auto")
|
442
210
|
window = scan_control
|
443
211
|
window.show()
|
444
212
|
app.exec()
|