bec-widgets 0.53.3__py3-none-any.whl → 0.55.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 (72) hide show
  1. CHANGELOG.md +24 -26
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +265 -13
  4. bec_widgets/cli/client_utils.py +0 -3
  5. bec_widgets/cli/generate_cli.py +10 -5
  6. bec_widgets/cli/rpc_wigdet_handler.py +2 -1
  7. bec_widgets/cli/server.py +5 -7
  8. bec_widgets/examples/jupyter_console/jupyter_console_window.py +11 -5
  9. bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
  10. bec_widgets/widgets/__init__.py +1 -10
  11. bec_widgets/widgets/figure/figure.py +40 -23
  12. bec_widgets/widgets/figure/plots/__init__.py +0 -0
  13. bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
  14. bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
  15. bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
  16. bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
  17. bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
  18. bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
  19. bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
  20. bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
  21. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
  22. bec_widgets/widgets/motor_control/__init__.py +0 -7
  23. bec_widgets/widgets/motor_control/motor_control.py +2 -948
  24. bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
  25. bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
  26. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  27. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
  28. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  29. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
  30. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  31. bec_widgets/widgets/motor_control/selection/selection.py +110 -0
  32. bec_widgets/widgets/spiral_progress_bar/__init__.py +1 -0
  33. bec_widgets/widgets/spiral_progress_bar/ring.py +184 -0
  34. bec_widgets/widgets/spiral_progress_bar/spiral_progress_bar.py +594 -0
  35. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/METADATA +1 -1
  36. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/RECORD +56 -53
  37. docs/requirements.txt +1 -0
  38. pyproject.toml +1 -1
  39. tests/end-2-end/test_bec_dock_rpc_e2e.py +82 -1
  40. tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
  41. tests/end-2-end/test_rpc_register_e2e.py +1 -1
  42. tests/unit_tests/test_bec_dock.py +1 -1
  43. tests/unit_tests/test_bec_figure.py +6 -4
  44. tests/unit_tests/test_bec_motor_map.py +2 -3
  45. tests/unit_tests/test_motor_control.py +6 -5
  46. tests/unit_tests/test_spiral_progress_bar.py +338 -0
  47. tests/unit_tests/test_waveform1d.py +13 -1
  48. bec_widgets/validation/__init__.py +0 -2
  49. bec_widgets/validation/monitor_config_validator.py +0 -258
  50. bec_widgets/widgets/monitor/__init__.py +0 -1
  51. bec_widgets/widgets/monitor/config_dialog.py +0 -574
  52. bec_widgets/widgets/monitor/config_dialog.ui +0 -210
  53. bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
  54. bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
  55. bec_widgets/widgets/monitor/monitor.py +0 -845
  56. bec_widgets/widgets/monitor/tab_template.ui +0 -180
  57. bec_widgets/widgets/motor_map/__init__.py +0 -1
  58. bec_widgets/widgets/motor_map/motor_map.py +0 -594
  59. bec_widgets/widgets/plots/__init__.py +0 -4
  60. tests/unit_tests/test_bec_monitor.py +0 -220
  61. tests/unit_tests/test_config_dialog.py +0 -178
  62. tests/unit_tests/test_motor_map.py +0 -171
  63. tests/unit_tests/test_validator_errors.py +0 -110
  64. /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
  65. /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
  66. /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
  67. /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
  68. /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
  69. /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
  70. /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
  71. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/WHEEL +0 -0
  72. {bec_widgets-0.53.3.dist-info → bec_widgets-0.55.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,574 +0,0 @@
1
- import os
2
-
3
- from pydantic import ValidationError
4
- from qtpy import uic
5
- from qtpy.QtCore import Signal as pyqtSignal
6
- from qtpy.QtWidgets import (
7
- QApplication,
8
- QLineEdit,
9
- QMessageBox,
10
- QTableWidget,
11
- QTableWidgetItem,
12
- QTabWidget,
13
- QVBoxLayout,
14
- QWidget,
15
- )
16
-
17
- from bec_widgets.utils.bec_dispatcher import BECDispatcher
18
- from bec_widgets.utils.yaml_dialog import load_yaml, save_yaml
19
- from bec_widgets.validation import MonitorConfigValidator
20
-
21
- current_path = os.path.dirname(__file__)
22
- Ui_Form, BaseClass = uic.loadUiType(os.path.join(current_path, "config_dialog.ui"))
23
- Tab_Ui_Form, Tab_BaseClass = uic.loadUiType(os.path.join(current_path, "tab_template.ui"))
24
-
25
- # test configs for demonstration purpose
26
-
27
- # Configuration for default mode when only devices are monitored
28
- CONFIG_DEFAULT = {
29
- "plot_settings": {
30
- "background_color": "black",
31
- "num_columns": 1,
32
- "colormap": "plasma",
33
- "scan_types": False,
34
- },
35
- "plot_data": [
36
- {
37
- "plot_name": "BPM4i plots vs samx",
38
- "x_label": "Motor Y",
39
- "y_label": "bpm4i",
40
- "sources": [
41
- {
42
- "type": "scan_segment",
43
- "signals": {
44
- "x": [{"name": "samx", "entry": "samx"}],
45
- "y": [{"name": "bpm4i", "entry": "bpm4i"}],
46
- },
47
- }
48
- ],
49
- },
50
- {
51
- "plot_name": "Gauss plots vs samx",
52
- "x_label": "Motor X",
53
- "y_label": "Gauss",
54
- "sources": [
55
- {
56
- "type": "scan_segment",
57
- "signals": {
58
- "x": [{"name": "samx", "entry": "samx"}],
59
- "y": [
60
- {"name": "gauss_bpm"},
61
- {"name": "gauss_adc1"},
62
- {"name": "gauss_adc2"},
63
- ],
64
- },
65
- }
66
- ],
67
- },
68
- ],
69
- }
70
-
71
- # Configuration which is dynamically changing depending on the scan type
72
- CONFIG_SCAN_MODE = {
73
- "plot_settings": {
74
- "background_color": "white",
75
- "num_columns": 3,
76
- "colormap": "plasma",
77
- "scan_types": True,
78
- },
79
- "plot_data": {
80
- "grid_scan": [
81
- {
82
- "plot_name": "Grid plot 1",
83
- "x_label": "Motor X",
84
- "y_label": "BPM",
85
- "sources": [
86
- {
87
- "type": "scan_segment",
88
- "signals": {
89
- "x": [{"name": "samx", "entry": "samx"}],
90
- "y": [{"name": "gauss_bpm"}],
91
- },
92
- }
93
- ],
94
- },
95
- {
96
- "plot_name": "Grid plot 2",
97
- "x_label": "Motor X",
98
- "y_label": "BPM",
99
- "sources": [
100
- {
101
- "type": "scan_segment",
102
- "signals": {
103
- "x": [{"name": "samx", "entry": "samx"}],
104
- "y": [{"name": "gauss_adc1"}],
105
- },
106
- }
107
- ],
108
- },
109
- {
110
- "plot_name": "Grid plot 3",
111
- "x_label": "Motor X",
112
- "y_label": "BPM",
113
- "sources": [
114
- {
115
- "type": "scan_segment",
116
- "signals": {"x": [{"name": "samy"}], "y": [{"name": "gauss_adc2"}]},
117
- }
118
- ],
119
- },
120
- {
121
- "plot_name": "Grid plot 4",
122
- "x_label": "Motor X",
123
- "y_label": "BPM",
124
- "sources": [
125
- {
126
- "type": "scan_segment",
127
- "signals": {
128
- "x": [{"name": "samy", "entry": "samy"}],
129
- "y": [{"name": "gauss_adc3"}],
130
- },
131
- }
132
- ],
133
- },
134
- ],
135
- "line_scan": [
136
- {
137
- "plot_name": "BPM plots vs samx",
138
- "x_label": "Motor X",
139
- "y_label": "Gauss",
140
- "sources": [
141
- {
142
- "type": "scan_segment",
143
- "signals": {
144
- "x": [{"name": "samx", "entry": "samx"}],
145
- "y": [{"name": "bpm4i"}],
146
- },
147
- }
148
- ],
149
- },
150
- {
151
- "plot_name": "Gauss plots vs samx",
152
- "x_label": "Motor X",
153
- "y_label": "Gauss",
154
- "sources": [
155
- {
156
- "type": "scan_segment",
157
- "signals": {
158
- "x": [{"name": "samx", "entry": "samx"}],
159
- "y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
160
- },
161
- }
162
- ],
163
- },
164
- ],
165
- },
166
- }
167
-
168
-
169
- class ConfigDialog(QWidget, Ui_Form):
170
- config_updated = pyqtSignal(dict)
171
-
172
- def __init__(self, client=None, default_config=None, skip_validation: bool = False):
173
- super(ConfigDialog, self).__init__()
174
- self.setupUi(self)
175
-
176
- # Client
177
- bec_dispatcher = BECDispatcher()
178
- self.client = bec_dispatcher.client if client is None else client
179
- self.dev = self.client.device_manager.devices
180
-
181
- # Init validator
182
- self.skip_validation = skip_validation
183
- if self.skip_validation is False:
184
- self.validator = MonitorConfigValidator(self.dev)
185
-
186
- # Connect the Ok/Apply/Cancel buttons
187
- self.pushButton_ok.clicked.connect(self.apply_and_close)
188
- self.pushButton_apply.clicked.connect(self.apply_config)
189
- self.pushButton_cancel.clicked.connect(self.close)
190
-
191
- # Hook signals top level
192
- self.pushButton_new_scan_type.clicked.connect(
193
- lambda: self.generate_empty_scan_tab(
194
- self.tabWidget_scan_types, self.lineEdit_scan_type.text()
195
- )
196
- )
197
-
198
- # Load/save yaml file buttons
199
- self.pushButton_import.clicked.connect(self.load_config_from_yaml)
200
- self.pushButton_export.clicked.connect(self.save_config_to_yaml)
201
-
202
- # Scan Types changed
203
- self.comboBox_scanTypes.currentIndexChanged.connect(self._init_default)
204
-
205
- # Make scan tabs closable
206
- self.tabWidget_scan_types.tabCloseRequested.connect(self.handle_tab_close_request)
207
-
208
- # Init functions to make a default dialog
209
- if default_config is None:
210
- self._init_default()
211
- else:
212
- self.load_config(default_config)
213
-
214
- def _init_default(self):
215
- """Init default dialog"""
216
-
217
- if self.comboBox_scanTypes.currentText() == "Disabled": # Default mode
218
- self.add_new_scan_tab(self.tabWidget_scan_types, "Default")
219
- self.add_new_plot_tab(self.tabWidget_scan_types.widget(0))
220
- self.pushButton_new_scan_type.setEnabled(False)
221
- self.lineEdit_scan_type.setEnabled(False)
222
- else: # Scan mode with clear tab
223
- self.pushButton_new_scan_type.setEnabled(True)
224
- self.lineEdit_scan_type.setEnabled(True)
225
- self.tabWidget_scan_types.clear()
226
-
227
- def add_new_scan_tab(
228
- self, parent_tab: QTabWidget, scan_name: str, closable: bool = False
229
- ) -> QWidget:
230
- """
231
- Add a new scan tab to the parent tab widget
232
-
233
- Args:
234
- parent_tab(QTabWidget): Parent tab widget, where to add scan tab
235
- scan_name(str): Scan name
236
- closable(bool): If True, the scan tab will be closable
237
-
238
- Returns:
239
- scan_tab(QWidget): Scan tab widget
240
- """
241
- # Check for an existing tab with the same name
242
- for index in range(parent_tab.count()):
243
- if parent_tab.tabText(index) == scan_name:
244
- print(f'Scan name "{scan_name}" already exists.')
245
- return None # or return the existing tab: return parent_tab.widget(index)
246
-
247
- # Create a new scan tab
248
- scan_tab = QWidget()
249
- scan_tab_layout = QVBoxLayout(scan_tab)
250
-
251
- # Set a tab widget for plots
252
- tabWidget_plots = QTabWidget()
253
- tabWidget_plots.setObjectName("tabWidget_plots") # TODO decide if needed to give a name
254
- tabWidget_plots.setTabsClosable(True)
255
- tabWidget_plots.tabCloseRequested.connect(self.handle_tab_close_request)
256
- scan_tab_layout.addWidget(tabWidget_plots)
257
-
258
- # Add scan tab
259
- parent_tab.addTab(scan_tab, scan_name)
260
-
261
- # Make tabs closable
262
- if closable:
263
- parent_tab.setTabsClosable(closable)
264
-
265
- return scan_tab
266
-
267
- def add_new_plot_tab(self, scan_tab: QWidget) -> QWidget:
268
- """
269
- Add a new plot tab to the scan tab
270
-
271
- Args:
272
- scan_tab (QWidget): Scan tab widget
273
-
274
- Returns:
275
- plot_tab (QWidget): Plot tab
276
- """
277
-
278
- # Create a new plot tab from .ui template
279
- plot_tab = QWidget()
280
- plot_tab_ui = Tab_Ui_Form()
281
- plot_tab_ui.setupUi(plot_tab)
282
- plot_tab.ui = plot_tab_ui
283
-
284
- # Add plot to current scan tab
285
- tabWidget_plots = scan_tab.findChild(
286
- QTabWidget, "tabWidget_plots"
287
- ) # TODO decide if putting name is needed
288
- plot_name = f"Plot {tabWidget_plots.count() + 1}"
289
- tabWidget_plots.addTab(plot_tab, plot_name)
290
-
291
- # Hook signal
292
- self.hook_plot_tab_signals(scan_tab=scan_tab, plot_tab=plot_tab.ui)
293
-
294
- return plot_tab
295
-
296
- def hook_plot_tab_signals(self, scan_tab: QTabWidget, plot_tab: Tab_Ui_Form) -> None:
297
- """
298
- Hook signals of the plot tab
299
- Args:
300
- scan_tab(QTabWidget): Scan tab widget
301
- plot_tab(Tab_Ui_Form): Plot tab widget
302
- """
303
- plot_tab.pushButton_add_new_plot.clicked.connect(
304
- lambda: self.add_new_plot_tab(scan_tab=scan_tab)
305
- )
306
- plot_tab.pushButton_y_new.clicked.connect(
307
- lambda: self.add_new_signal(plot_tab.tableWidget_y_signals)
308
- )
309
-
310
- def add_new_signal(self, table: QTableWidget) -> None:
311
- """
312
- Add a new signal to the table
313
-
314
- Args:
315
- table(QTableWidget): Table widget
316
- """
317
-
318
- row_position = table.rowCount()
319
- table.insertRow(row_position)
320
- table.setItem(row_position, 0, QTableWidgetItem(""))
321
- table.setItem(row_position, 1, QTableWidgetItem(""))
322
-
323
- def handle_tab_close_request(self, index: int) -> None:
324
- """
325
- Handle tab close request
326
-
327
- Args:
328
- index(int): Index of the tab to be closed
329
- """
330
-
331
- parent_tab = self.sender()
332
- if parent_tab.count() > 1: # ensure there is at least one tab
333
- parent_tab.removeTab(index)
334
-
335
- def generate_empty_scan_tab(self, parent_tab: QTabWidget, scan_name: str):
336
- """
337
- Generate an empty scan tab
338
-
339
- Args:
340
- parent_tab (QTabWidget): Parent tab widget where to add the scan tab
341
- scan_name(str): name of the scan tab
342
- """
343
-
344
- scan_tab = self.add_new_scan_tab(parent_tab, scan_name, closable=True)
345
- if scan_tab is not None:
346
- self.add_new_plot_tab(scan_tab)
347
-
348
- def get_plot_config(self, plot_tab: QWidget) -> dict:
349
- """
350
- Get plot configuration from the plot tab adn send it as dict
351
-
352
- Args:
353
- plot_tab(QWidget): Plot tab widget
354
-
355
- Returns:
356
- dict: Plot configuration
357
- """
358
-
359
- ui = plot_tab.ui
360
- table = ui.tableWidget_y_signals
361
-
362
- x_signals = [
363
- {
364
- "name": self.safe_text(ui.lineEdit_x_name),
365
- "entry": self.safe_text(ui.lineEdit_x_entry),
366
- }
367
- ]
368
-
369
- y_signals = [
370
- {
371
- "name": self.safe_text(table.item(row, 0)),
372
- "entry": self.safe_text(table.item(row, 1)),
373
- }
374
- for row in range(table.rowCount())
375
- ]
376
-
377
- plot_data = {
378
- "plot_name": self.safe_text(ui.lineEdit_plot_title),
379
- "x_label": self.safe_text(ui.lineEdit_x_label),
380
- "y_label": self.safe_text(ui.lineEdit_y_label),
381
- "sources": [{"type": "scan_segment", "signals": {"x": x_signals, "y": y_signals}}],
382
- }
383
-
384
- return plot_data
385
-
386
- def apply_config(self) -> dict:
387
- """
388
- Apply configuration from the whole configuration window
389
-
390
- Returns:
391
- dict: Current configuration
392
-
393
- """
394
-
395
- # General settings
396
- config = {
397
- "plot_settings": {
398
- "background_color": self.comboBox_appearance.currentText(),
399
- "num_columns": self.spinBox_n_column.value(),
400
- "colormap": self.comboBox_colormap.currentText(),
401
- "scan_types": True if self.comboBox_scanTypes.currentText() == "Enabled" else False,
402
- },
403
- "plot_data": {} if self.comboBox_scanTypes.currentText() == "Enabled" else [],
404
- }
405
-
406
- # Iterate through the plot tabs - Device monitor mode
407
- if config["plot_settings"]["scan_types"] == False:
408
- plot_tab = self.tabWidget_scan_types.widget(0).findChild(QTabWidget)
409
- for index in range(plot_tab.count()):
410
- plot_data = self.get_plot_config(plot_tab.widget(index))
411
- config["plot_data"].append(plot_data)
412
-
413
- # Iterate through the scan tabs - Scan mode
414
- elif config["plot_settings"]["scan_types"] == True:
415
- # Iterate through the scan tabs
416
- for index in range(self.tabWidget_scan_types.count()):
417
- scan_tab = self.tabWidget_scan_types.widget(index)
418
- scan_name = self.tabWidget_scan_types.tabText(index)
419
- plot_tab = scan_tab.findChild(QTabWidget)
420
- config["plot_data"][scan_name] = []
421
- # Iterate through the plot tabs
422
- for index in range(plot_tab.count()):
423
- plot_data = self.get_plot_config(plot_tab.widget(index))
424
- config["plot_data"][scan_name].append(plot_data)
425
-
426
- return config
427
-
428
- def load_config(self, config: dict) -> None:
429
- """
430
- Load configuration to the configuration window
431
-
432
- Args:
433
- config(dict): Configuration to be loaded
434
- """
435
-
436
- # Plot setting General box
437
- plot_settings = config.get("plot_settings", {})
438
-
439
- self.comboBox_appearance.setCurrentText(plot_settings.get("background_color", "black"))
440
- self.spinBox_n_column.setValue(plot_settings.get("num_columns", 1))
441
- self.comboBox_colormap.setCurrentText(
442
- plot_settings.get("colormap", "magma")
443
- ) # TODO make logic to allow also different colormaps -> validation of incoming dict
444
- self.comboBox_scanTypes.setCurrentText(
445
- "Enabled" if plot_settings.get("scan_types", False) else "Disabled"
446
- )
447
-
448
- # Clear exiting scan tabs
449
- self.tabWidget_scan_types.clear()
450
-
451
- # Get what mode is active - scan vs default device monitor
452
- scan_mode = plot_settings.get("scan_types", False)
453
-
454
- if scan_mode is False: # default mode:
455
- plot_data = config.get("plot_data", [])
456
- self.add_new_scan_tab(self.tabWidget_scan_types, "Default")
457
- for plot_config in plot_data: # Create plot tab for each plot and populate GUI
458
- plot = self.add_new_plot_tab(self.tabWidget_scan_types.widget(0))
459
- self.load_plot_setting(plot, plot_config)
460
- elif scan_mode is True: # scan mode
461
- plot_data = config.get("plot_data", {})
462
- for scan_name, scan_config in plot_data.items():
463
- scan_tab = self.add_new_scan_tab(self.tabWidget_scan_types, scan_name)
464
- for plot_config in scan_config:
465
- plot = self.add_new_plot_tab(scan_tab)
466
- self.load_plot_setting(plot, plot_config)
467
-
468
- def load_plot_setting(self, plot: QWidget, plot_config: dict) -> None:
469
- """
470
- Load plot setting from config
471
-
472
- Args:
473
- plot (QWidget): plot tab widget
474
- plot_config (dict): config for single plot tab
475
- """
476
- sources = plot_config.get("sources", [{}])[0]
477
- x_signals = sources.get("signals", {}).get("x", [{}])[0]
478
- y_signals = sources.get("signals", {}).get("y", [])
479
-
480
- # LabelBox
481
- plot.ui.lineEdit_plot_title.setText(plot_config.get("plot_name", ""))
482
- plot.ui.lineEdit_x_label.setText(plot_config.get("x_label", ""))
483
- plot.ui.lineEdit_y_label.setText(plot_config.get("y_label", ""))
484
-
485
- # X axis
486
- plot.ui.lineEdit_x_name.setText(x_signals.get("name", ""))
487
- plot.ui.lineEdit_x_entry.setText(x_signals.get("entry", ""))
488
-
489
- # Y axis
490
- for y_signal in y_signals:
491
- row_position = plot.ui.tableWidget_y_signals.rowCount()
492
- plot.ui.tableWidget_y_signals.insertRow(row_position)
493
- plot.ui.tableWidget_y_signals.setItem(
494
- row_position, 0, QTableWidgetItem(y_signal.get("name", ""))
495
- )
496
- plot.ui.tableWidget_y_signals.setItem(
497
- row_position, 1, QTableWidgetItem(y_signal.get("entry", ""))
498
- )
499
-
500
- def load_config_from_yaml(self):
501
- """
502
- Load configuration from yaml file
503
- """
504
- config = load_yaml(self)
505
- self.load_config(config)
506
-
507
- def save_config_to_yaml(self):
508
- """
509
- Save configuration to yaml file
510
- """
511
- config = self.apply_config()
512
- save_yaml(self, config)
513
-
514
- @staticmethod
515
- def safe_text(line_edit: QLineEdit) -> str:
516
- """
517
- Get text from a line edit, if it is None, return empty string
518
- Args:
519
- line_edit(QLineEdit): Line edit widget
520
-
521
- Returns:
522
- str: Text from the line edit
523
- """
524
- return "" if line_edit is None else line_edit.text()
525
-
526
- def apply_and_close(self):
527
- new_config = self.apply_config()
528
- if self.skip_validation is True:
529
- self.config_updated.emit(new_config)
530
- self.close()
531
- else:
532
- try:
533
- validated_config = self.validator.validate_monitor_config(new_config)
534
- approved_config = validated_config.model_dump()
535
- self.config_updated.emit(approved_config)
536
- self.close()
537
- except ValidationError as e:
538
- error_str = str(e)
539
- formatted_error_message = ConfigDialog.format_validation_error(error_str)
540
-
541
- # Display the formatted error message in a popup
542
- QMessageBox.critical(self, "Configuration Error", formatted_error_message)
543
-
544
- @staticmethod
545
- def format_validation_error(error_str: str) -> str:
546
- """
547
- Format the validation error string to be displayed in a popup.
548
- Args:
549
- error_str(str): Error string from the validation error.
550
- """
551
- error_lines = error_str.split("\n")
552
- # The first line contains the number of errors.
553
- error_header = f"<p><b>{error_lines[0]}</b></p><hr>"
554
-
555
- formatted_error_message = error_header
556
- # Skip the first line as it's the header.
557
- error_details = error_lines[1:]
558
-
559
- # Iterate through pairs of lines (each error's two lines).
560
- for i in range(0, len(error_details), 2):
561
- location = error_details[i]
562
- message = error_details[i + 1] if i + 1 < len(error_details) else ""
563
-
564
- formatted_error_message += f"<p><b>{location}</b><br>{message}</p><hr>"
565
-
566
- return formatted_error_message
567
-
568
-
569
- if __name__ == "__main__": # pragma: no cover
570
- app = QApplication([])
571
- main_app = ConfigDialog()
572
- main_app.show()
573
- main_app.load_config(CONFIG_SCAN_MODE)
574
- app.exec()