bec-widgets 0.87.0__py3-none-any.whl → 0.88.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 (77) hide show
  1. CHANGELOG.md +66 -70
  2. PKG-INFO +1 -1
  3. bec_widgets/assets/designer_icons/BECWaveformWidget.png +0 -0
  4. bec_widgets/assets/designer_icons/colormap_selector.png +0 -0
  5. bec_widgets/assets/toolbar_icons/add.svg +3 -0
  6. bec_widgets/assets/toolbar_icons/auto_range.svg +3 -0
  7. bec_widgets/assets/toolbar_icons/drag_pan_mode.svg +3 -0
  8. bec_widgets/assets/toolbar_icons/export.svg +9 -0
  9. bec_widgets/assets/toolbar_icons/fitting_parameters.svg +3 -0
  10. bec_widgets/assets/toolbar_icons/import.svg +9 -0
  11. bec_widgets/assets/toolbar_icons/line_axis.svg +3 -0
  12. bec_widgets/assets/toolbar_icons/photo_library.svg +3 -0
  13. bec_widgets/assets/toolbar_icons/rectangle_mode.svg +3 -0
  14. bec_widgets/assets/toolbar_icons/remove.svg +5 -0
  15. bec_widgets/assets/toolbar_icons/save.svg +3 -0
  16. bec_widgets/assets/toolbar_icons/settings.svg +4 -0
  17. bec_widgets/cli/client.py +307 -5
  18. bec_widgets/cli/server.py +2 -1
  19. bec_widgets/examples/jupyter_console/jupyter_console_window.py +13 -7
  20. bec_widgets/examples/plugin_example_pyside/tictactoe.py +1 -0
  21. bec_widgets/utils/bec_connector.py +19 -18
  22. bec_widgets/utils/bec_widget.py +19 -4
  23. bec_widgets/widgets/base_classes/device_input_base.py +3 -5
  24. bec_widgets/widgets/bec_queue/bec_queue.py +3 -2
  25. bec_widgets/widgets/bec_status_box/bec_status_box.py +2 -11
  26. bec_widgets/widgets/{color_map_selector/color_map_selector.py → colormap_selector/colormap_selector.py} +3 -1
  27. bec_widgets/widgets/colormap_selector/colormap_selector.pyproject +1 -0
  28. bec_widgets/widgets/{color_map_selector/color_map_selector_plugin.py → colormap_selector/colormap_selector_plugin.py} +8 -6
  29. bec_widgets/widgets/{color_map_selector/register_color_map_selector.py → colormap_selector/register_colormap_selector.py} +1 -1
  30. bec_widgets/widgets/device_box/device_box.py +2 -2
  31. bec_widgets/widgets/device_combobox/device_combobox.py +1 -8
  32. bec_widgets/widgets/device_line_edit/device_line_edit.py +2 -9
  33. bec_widgets/widgets/dock/dock.py +8 -6
  34. bec_widgets/widgets/dock/dock_area.py +4 -2
  35. bec_widgets/widgets/figure/figure.py +16 -8
  36. bec_widgets/widgets/figure/plots/axis_settings.py +38 -11
  37. bec_widgets/widgets/figure/plots/image/image.py +2 -4
  38. bec_widgets/widgets/figure/plots/motor_map/motor_map.py +1 -1
  39. bec_widgets/widgets/figure/plots/plot_base.py +7 -8
  40. bec_widgets/widgets/figure/plots/waveform/waveform.py +49 -53
  41. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +10 -1
  42. bec_widgets/widgets/jupyter_console/jupyter_console.py +6 -0
  43. bec_widgets/widgets/motor_map/bec_motor_map_widget_plugin.py +1 -1
  44. bec_widgets/widgets/motor_map/motor_map_widget.py +9 -6
  45. bec_widgets/widgets/ring_progress_bar/ring.py +0 -4
  46. bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py +8 -8
  47. bec_widgets/widgets/scan_control/scan_control.py +2 -6
  48. bec_widgets/widgets/stop_button/stop_button.py +2 -2
  49. bec_widgets/widgets/text_box/text_box.py +3 -2
  50. bec_widgets/widgets/waveform/__init__.py +0 -0
  51. bec_widgets/widgets/waveform/bec_waveform_widget.pyproject +1 -0
  52. bec_widgets/widgets/waveform/bec_waveform_widget_plugin.py +59 -0
  53. bec_widgets/widgets/waveform/register_bec_waveform_widget.py +15 -0
  54. bec_widgets/widgets/waveform/waveform_toolbar/__init__.py +0 -0
  55. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/__init__.py +0 -0
  56. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py +341 -0
  57. bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui +358 -0
  58. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/__init__.py +0 -0
  59. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary.ui +127 -0
  60. bec_widgets/widgets/waveform/waveform_toolbar/dap_summary_dialog/dap_summary_dialog.py +69 -0
  61. bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py +117 -0
  62. bec_widgets/widgets/waveform/waveform_widget.py +571 -0
  63. bec_widgets/widgets/website/website.py +2 -2
  64. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/METADATA +1 -1
  65. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/RECORD +75 -48
  66. pyproject.toml +1 -1
  67. tests/unit_tests/test_color_map_selector.py +1 -1
  68. tests/unit_tests/test_device_input_base.py +10 -4
  69. tests/unit_tests/test_waveform_widget.py +264 -0
  70. bec_widgets/widgets/color_map_selector/assets/color_map_selector_icon.png +0 -0
  71. bec_widgets/widgets/color_map_selector/color_map_selector.pyproject +0 -1
  72. /bec_widgets/assets/{bec_widgets_icon.png → app_icons/bec_widgets_icon.png} +0 -0
  73. /bec_widgets/assets/{terminal_icon.png → app_icons/terminal_icon.png} +0 -0
  74. /bec_widgets/widgets/{color_map_selector → colormap_selector}/__init__.py +0 -0
  75. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/WHEEL +0 -0
  76. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/entry_points.txt +0 -0
  77. {bec_widgets-0.87.0.dist-info → bec_widgets-0.88.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ from qtpy.QtWidgets import QVBoxLayout, QWidget
6
6
 
7
7
  from bec_widgets.qt_utils.settings_dialog import SettingsDialog
8
8
  from bec_widgets.qt_utils.toolbar import ModularToolBar
9
- from bec_widgets.utils import BECConnector
9
+ from bec_widgets.utils.bec_widget import BECWidget
10
10
  from bec_widgets.widgets.figure import BECFigure
11
11
  from bec_widgets.widgets.figure.plots.motor_map.motor_map import MotorMapConfig
12
12
  from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapSettings
@@ -18,7 +18,7 @@ from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_toolbar import (
18
18
  )
19
19
 
20
20
 
21
- class BECMotorMapWidget(BECConnector, QWidget):
21
+ class BECMotorMapWidget(BECWidget, QWidget):
22
22
  USER_ACCESS = [
23
23
  "change_motors",
24
24
  "set_max_points",
@@ -28,6 +28,7 @@ class BECMotorMapWidget(BECConnector, QWidget):
28
28
  "set_scatter_size",
29
29
  "get_data",
30
30
  "reset_history",
31
+ "export",
31
32
  ]
32
33
 
33
34
  def __init__(
@@ -202,16 +203,18 @@ class BECMotorMapWidget(BECConnector, QWidget):
202
203
  """
203
204
  self.map.set_scatter_size(scatter_size)
204
205
 
206
+ def export(self):
207
+ """
208
+ Show the export dialog for the motor map.
209
+ """
210
+ self.map.export()
211
+
205
212
  def cleanup(self):
206
213
  self.fig.cleanup()
207
214
  self.toolbar.widgets["motor_x"].device_combobox.cleanup()
208
215
  self.toolbar.widgets["motor_y"].device_combobox.cleanup()
209
216
  return super().cleanup()
210
217
 
211
- def closeEvent(self, event):
212
- self.cleanup()
213
- QWidget().closeEvent(event)
214
-
215
218
 
216
219
  def main(): # pragma: no cover
217
220
  from qtpy.QtWidgets import QApplication
@@ -288,7 +288,3 @@ class Ring(BECConnector):
288
288
  value = msg.get("signals").get(device).get("value")
289
289
  self.set_value(value)
290
290
  self.parent_progress_widget.update()
291
-
292
- def cleanup(self):
293
- self.reset_connection()
294
- super().cleanup()
@@ -10,7 +10,8 @@ from qtpy import QtCore, QtGui
10
10
  from qtpy.QtCore import QSize, Slot
11
11
  from qtpy.QtWidgets import QSizePolicy, QWidget
12
12
 
13
- from bec_widgets.utils import BECConnector, Colors, ConnectionConfig, EntryValidator
13
+ from bec_widgets.utils import Colors, ConnectionConfig, EntryValidator
14
+ from bec_widgets.utils.bec_widget import BECWidget
14
15
  from bec_widgets.widgets.ring_progress_bar.ring import Ring, RingConfig
15
16
 
16
17
 
@@ -66,7 +67,7 @@ class RingProgressBarConfig(ConnectionConfig):
66
67
  _validate_colormap = field_validator("color_map")(Colors.validate_color_map)
67
68
 
68
69
 
69
- class RingProgressBar(BECConnector, QWidget):
70
+ class RingProgressBar(BECWidget, QWidget):
70
71
  USER_ACCESS = [
71
72
  "_get_all_rpc",
72
73
  "_rpc_id",
@@ -208,7 +209,7 @@ class RingProgressBar(BECConnector, QWidget):
208
209
  index(int): Index of the progress bar to remove.
209
210
  """
210
211
  ring = self._find_ring_by_index(index)
211
- ring.cleanup()
212
+ ring.reset_connection()
212
213
  self._rings.remove(ring)
213
214
  self.config.rings.remove(ring.config)
214
215
  self.config.num_bars -= 1
@@ -622,9 +623,8 @@ class RingProgressBar(BECConnector, QWidget):
622
623
 
623
624
  def clear_all(self):
624
625
  for ring in self._rings:
625
- ring.cleanup()
626
- del ring
627
- self._rings = []
626
+ ring.reset_connection()
627
+ self._rings.clear()
628
628
  self.update()
629
629
  self.initialize_bars()
630
630
 
@@ -633,6 +633,6 @@ class RingProgressBar(BECConnector, QWidget):
633
633
  self.on_scan_queue_status, MessageEndpoints.scan_queue_status()
634
634
  )
635
635
  for ring in self._rings:
636
- ring.cleanup()
637
- del ring
636
+ ring.reset_connection()
637
+ self._rings.clear()
638
638
  super().cleanup()
@@ -10,13 +10,13 @@ from qtpy.QtWidgets import (
10
10
  QWidget,
11
11
  )
12
12
 
13
- from bec_widgets.utils import BECConnector
13
+ from bec_widgets.utils.bec_widget import BECWidget
14
14
  from bec_widgets.utils.colors import apply_theme
15
15
  from bec_widgets.widgets.scan_control.scan_group_box import ScanGroupBox
16
16
  from bec_widgets.widgets.stop_button.stop_button import StopButton
17
17
 
18
18
 
19
- class ScanControl(BECConnector, QWidget):
19
+ class ScanControl(BECWidget, QWidget):
20
20
 
21
21
  def __init__(
22
22
  self, parent=None, client=None, gui_id: str | None = None, allowed_scans: list | None = None
@@ -196,10 +196,6 @@ class ScanControl(BECConnector, QWidget):
196
196
  widget.cleanup()
197
197
  super().cleanup()
198
198
 
199
- def closeEvent(self, event):
200
- self.cleanup()
201
- return QWidget.closeEvent(self, event)
202
-
203
199
 
204
200
  # Application example
205
201
  if __name__ == "__main__": # pragma: no cover
@@ -1,10 +1,10 @@
1
1
  from qtpy.QtCore import Slot
2
2
  from qtpy.QtWidgets import QPushButton
3
3
 
4
- from bec_widgets.utils import BECConnector
4
+ from bec_widgets.utils.bec_widget import BECWidget
5
5
 
6
6
 
7
- class StopButton(BECConnector, QPushButton):
7
+ class StopButton(BECWidget, QPushButton):
8
8
  """A button that stops the current scan."""
9
9
 
10
10
  def __init__(self, parent=None, client=None, config=None, gui_id=None):
@@ -3,7 +3,8 @@ import re
3
3
  from pydantic import Field, field_validator
4
4
  from qtpy.QtWidgets import QTextEdit
5
5
 
6
- from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
6
+ from bec_widgets.utils.bec_connector import ConnectionConfig
7
+ from bec_widgets.utils.bec_widget import BECWidget
7
8
  from bec_widgets.utils.colors import Colors
8
9
 
9
10
 
@@ -27,7 +28,7 @@ class TextBoxConfig(ConnectionConfig):
27
28
  _validate_background_color = field_validator("background_color")(Colors.validate_color)
28
29
 
29
30
 
30
- class TextBox(BECConnector, QTextEdit):
31
+ class TextBox(BECWidget, QTextEdit):
31
32
 
32
33
  USER_ACCESS = ["set_color", "set_text", "set_font_size"]
33
34
 
File without changes
@@ -0,0 +1 @@
1
+ {'files': ['waveform_widget.py']}
@@ -0,0 +1,59 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+ import os
4
+
5
+ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
6
+ from qtpy.QtGui import QIcon
7
+
8
+ import bec_widgets
9
+ from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
10
+
11
+ DOM_XML = """
12
+ <ui language='c++'>
13
+ <widget class='BECWaveformWidget' name='bec_waveform_widget'>
14
+ </widget>
15
+ </ui>
16
+ """
17
+
18
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
19
+
20
+
21
+ class BECWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
22
+ def __init__(self):
23
+ super().__init__()
24
+ self._form_editor = None
25
+
26
+ def createWidget(self, parent):
27
+ t = BECWaveformWidget(parent)
28
+ return t
29
+
30
+ def domXml(self):
31
+ return DOM_XML
32
+
33
+ def group(self):
34
+ return "BEC Plots"
35
+
36
+ def icon(self):
37
+ icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "BECWaveformWidget.png")
38
+ return QIcon(icon_path)
39
+
40
+ def includeFile(self):
41
+ return "bec_waveform_widget"
42
+
43
+ def initialize(self, form_editor):
44
+ self._form_editor = form_editor
45
+
46
+ def isContainer(self):
47
+ return False
48
+
49
+ def isInitialized(self):
50
+ return self._form_editor is not None
51
+
52
+ def name(self):
53
+ return "BECWaveformWidget"
54
+
55
+ def toolTip(self):
56
+ return "BECWaveformWidget"
57
+
58
+ def whatsThis(self):
59
+ return self.toolTip()
@@ -0,0 +1,15 @@
1
+ def main(): # pragma: no cover
2
+ from qtpy import PYSIDE6
3
+
4
+ if not PYSIDE6:
5
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
6
+ return
7
+ from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
+
9
+ from bec_widgets.widgets.waveform.bec_waveform_widget_plugin import BECWaveformWidgetPlugin
10
+
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(BECWaveformWidgetPlugin())
12
+
13
+
14
+ if __name__ == "__main__": # pragma: no cover
15
+ main()
@@ -0,0 +1,341 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Literal
5
+
6
+ from pydantic import BaseModel
7
+ from qtpy.QtCore import QObject, QSize, Slot
8
+ from qtpy.QtGui import QIcon
9
+ from qtpy.QtWidgets import QComboBox, QLineEdit, QPushButton, QSpinBox, QTableWidget, QVBoxLayout
10
+
11
+ import bec_widgets
12
+ from bec_widgets.qt_utils.error_popups import WarningPopupUtility
13
+ from bec_widgets.qt_utils.settings_dialog import SettingWidget
14
+ from bec_widgets.utils import Colors, UILoader
15
+ from bec_widgets.widgets.color_button.color_button import ColorButton
16
+ from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
17
+
18
+ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
19
+
20
+
21
+ class CurveSettings(SettingWidget):
22
+ def __init__(self, parent=None, *args, **kwargs):
23
+ super().__init__(parent, *args, **kwargs)
24
+ current_path = os.path.dirname(__file__)
25
+
26
+ self.ui = UILoader(self).loader(os.path.join(current_path, "curve_dialog.ui"))
27
+ self._setup_icons()
28
+
29
+ self.warning_util = WarningPopupUtility(self)
30
+
31
+ self.layout = QVBoxLayout(self)
32
+ self.layout.addWidget(self.ui)
33
+
34
+ self.ui.add_curve.clicked.connect(self.add_curve)
35
+ self.ui.add_dap.clicked.connect(self.add_dap)
36
+ self.ui.x_mode.currentIndexChanged.connect(self.set_x_mode)
37
+ self.ui.normalize_colors_scan.clicked.connect(lambda: self.change_colormap("scan"))
38
+ self.ui.normalize_colors_dap.clicked.connect(lambda: self.change_colormap("dap"))
39
+
40
+ def _setup_icons(self):
41
+ add_icon = QIcon()
42
+ add_icon.addFile(
43
+ os.path.join(MODULE_PATH, "assets", "toolbar_icons", "add.svg"), size=QSize(20, 20)
44
+ )
45
+ self.ui.add_dap.setIcon(add_icon)
46
+ self.ui.add_dap.setToolTip("Add DAP Curve")
47
+ self.ui.add_curve.setIcon(add_icon)
48
+ self.ui.add_curve.setToolTip("Add Scan Curve")
49
+
50
+ @Slot(dict)
51
+ def display_current_settings(self, config: dict | BaseModel):
52
+
53
+ # What elements should be enabled
54
+ x_name = self.target_widget.waveform._x_axis_mode["name"]
55
+ x_entry = self.target_widget.waveform._x_axis_mode["entry"]
56
+ self._enable_ui_elements(x_name, x_entry)
57
+ cm = self.target_widget.config.color_palette
58
+ self.ui.color_map_selector_scan.combo.setCurrentText(cm)
59
+
60
+ # Scan Curve Table
61
+ for label, curve in config["scan_segment"].items():
62
+ row_count = self.ui.scan_table.rowCount()
63
+ self.ui.scan_table.insertRow(row_count)
64
+ DialogRow(
65
+ parent=self,
66
+ table_widget=self.ui.scan_table,
67
+ client=self.target_widget.client,
68
+ row=row_count,
69
+ config=curve.config,
70
+ ).add_scan_row()
71
+
72
+ # Add DAP Curves
73
+ for label, curve in config["DAP"].items():
74
+ row_count = self.ui.dap_table.rowCount()
75
+ self.ui.dap_table.insertRow(row_count)
76
+ DialogRow(
77
+ parent=self,
78
+ table_widget=self.ui.dap_table,
79
+ client=self.target_widget.client,
80
+ row=row_count,
81
+ config=curve.config,
82
+ ).add_dap_row()
83
+
84
+ def _enable_ui_elements(self, name, entry):
85
+ if name is None:
86
+ name = "best_effort"
87
+ if name in ["index", "timestamp", "best_effort"]:
88
+ self.ui.x_mode.setCurrentText(name)
89
+ self.set_x_mode()
90
+ else:
91
+ self.ui.x_mode.setCurrentText("device")
92
+ self.set_x_mode()
93
+ self.ui.x_name.setText(name)
94
+ self.ui.x_entry.setText(entry)
95
+
96
+ @Slot()
97
+ def set_x_mode(self):
98
+ x_mode = self.ui.x_mode.currentText()
99
+ if x_mode in ["index", "timestamp", "best_effort"]:
100
+ self.ui.x_name.setEnabled(False)
101
+ self.ui.x_entry.setEnabled(False)
102
+ self.ui.dap_table.setEnabled(False)
103
+ self.ui.add_dap.setEnabled(False)
104
+ if self.ui.dap_table.rowCount() > 0:
105
+ self.warning_util.show_warning(
106
+ title="DAP Warning",
107
+ message="DAP is not supported without specific x-axis device. All current DAP curves will be removed.",
108
+ detailed_text=f"Affected curves: {[self.ui.dap_table.cellWidget(row, 0).text() for row in range(self.ui.dap_table.rowCount())]}",
109
+ )
110
+ else:
111
+ self.ui.x_name.setEnabled(True)
112
+ self.ui.x_entry.setEnabled(True)
113
+ self.ui.dap_table.setEnabled(True)
114
+ self.ui.add_dap.setEnabled(True)
115
+
116
+ @Slot()
117
+ def change_colormap(self, target: Literal["scan", "dap"]):
118
+ if target == "scan":
119
+ cm = self.ui.color_map_selector_scan.combo.currentText()
120
+ table = self.ui.scan_table
121
+ if target == "dap":
122
+ cm = self.ui.color_map_selector_dap.combo.currentText()
123
+ table = self.ui.dap_table
124
+ rows = table.rowCount()
125
+ colors = Colors.golden_angle_color(colormap=cm, num=rows + 1, format="HEX")
126
+ color_button_col = 2 if target == "scan" else 3
127
+ for row, color in zip(range(rows), colors):
128
+ table.cellWidget(row, color_button_col).setColor(color)
129
+
130
+ @Slot()
131
+ def accept_changes(self):
132
+ self.accept_curve_changes()
133
+
134
+ def accept_curve_changes(self):
135
+ old_curves_scans = list(self.target_widget.waveform._curves_data["scan_segment"].values())
136
+ old_curves_dap = list(self.target_widget.waveform._curves_data["DAP"].values())
137
+ for curve in old_curves_scans:
138
+ curve.remove()
139
+ for curve in old_curves_dap:
140
+ curve.remove()
141
+ self.get_curve_params()
142
+
143
+ def get_curve_params(self):
144
+ x_mode = self.ui.x_mode.currentText()
145
+
146
+ if x_mode in ["index", "timestamp", "best_effort"]:
147
+ x_name = x_mode
148
+ x_entry = x_mode
149
+ else:
150
+ x_name = self.ui.x_name.text()
151
+ x_entry = self.ui.x_entry.text()
152
+
153
+ self.target_widget.set_x(x_name=x_name, x_entry=x_entry)
154
+
155
+ for row in range(self.ui.scan_table.rowCount()):
156
+ y_name = self.ui.scan_table.cellWidget(row, 0).text()
157
+ y_entry = self.ui.scan_table.cellWidget(row, 1).text()
158
+ color = self.ui.scan_table.cellWidget(row, 2).get_color()
159
+ style = self.ui.scan_table.cellWidget(row, 3).currentText()
160
+ width = self.ui.scan_table.cellWidget(row, 4).value()
161
+ symbol_size = self.ui.scan_table.cellWidget(row, 5).value()
162
+ self.target_widget.plot(
163
+ y_name=y_name,
164
+ y_entry=y_entry,
165
+ color=color,
166
+ pen_style=style,
167
+ pen_width=width,
168
+ symbol_size=symbol_size,
169
+ )
170
+
171
+ if x_mode not in ["index", "timestamp", "best_effort"]:
172
+
173
+ for row in range(self.ui.dap_table.rowCount()):
174
+ y_name = self.ui.dap_table.cellWidget(row, 0).text()
175
+ y_entry = self.ui.dap_table.cellWidget(row, 1).text()
176
+ dap = self.ui.dap_table.cellWidget(row, 2).currentText()
177
+ color = self.ui.dap_table.cellWidget(row, 3).get_color()
178
+ style = self.ui.dap_table.cellWidget(row, 4).currentText()
179
+ width = self.ui.dap_table.cellWidget(row, 5).value()
180
+ symbol_size = self.ui.dap_table.cellWidget(row, 6).value()
181
+
182
+ self.target_widget.add_dap(
183
+ x_name=x_name,
184
+ x_entry=x_entry,
185
+ y_name=y_name,
186
+ y_entry=y_entry,
187
+ dap=dap,
188
+ color=color,
189
+ pen_style=style,
190
+ pen_width=width,
191
+ symbol_size=symbol_size,
192
+ )
193
+ self.target_widget.scan_history(-1)
194
+
195
+ def add_curve(self):
196
+ row_count = self.ui.scan_table.rowCount()
197
+ self.ui.scan_table.insertRow(row_count)
198
+ DialogRow(
199
+ parent=self,
200
+ table_widget=self.ui.scan_table,
201
+ client=self.target_widget.client,
202
+ row=row_count,
203
+ config=None,
204
+ ).add_scan_row()
205
+
206
+ def add_dap(self):
207
+ row_count = self.ui.dap_table.rowCount()
208
+ self.ui.dap_table.insertRow(row_count)
209
+ DialogRow(
210
+ parent=self,
211
+ table_widget=self.ui.dap_table,
212
+ client=self.target_widget.client,
213
+ row=row_count,
214
+ config=None,
215
+ ).add_dap_row()
216
+
217
+
218
+ class DialogRow(QObject):
219
+ def __init__(
220
+ self,
221
+ parent=None,
222
+ table_widget: QTableWidget = None,
223
+ row: int = None,
224
+ config: dict = None,
225
+ client=None,
226
+ ):
227
+
228
+ super().__init__(parent=parent)
229
+ self.client = client
230
+
231
+ self.table_widget = table_widget
232
+ self.row = row
233
+ self.config = config
234
+ self.init_default_widgets()
235
+
236
+ def init_default_widgets(self):
237
+
238
+ # Remove Button
239
+ self.remove_button = RemoveButton()
240
+
241
+ # Name and Entry
242
+ self.device_line_edit = DeviceLineEdit()
243
+ self.entry_line_edit = QLineEdit()
244
+
245
+ self.dap_combo = QComboBox()
246
+ self.populate_dap_combobox()
247
+
248
+ # Styling
249
+ self.color_button = ColorButton()
250
+ self.style_combo = StyleComboBox()
251
+ self.width = QSpinBox()
252
+ self.width.setMinimum(1)
253
+ self.width.setMaximum(20)
254
+ self.width.setValue(2)
255
+
256
+ self.symbol_size = QSpinBox()
257
+ self.symbol_size.setMinimum(1)
258
+ self.symbol_size.setMaximum(20)
259
+ self.symbol_size.setValue(5)
260
+
261
+ self.remove_button.clicked.connect(
262
+ lambda: self.remove_row()
263
+ ) # From some reason do not work without lambda
264
+
265
+ def populate_dap_combobox(self):
266
+ available_models = [
267
+ attr
268
+ for attr in dir(self.client.dap)
269
+ if not attr.startswith("__")
270
+ and not callable(getattr(self.client.dap, attr))
271
+ and not attr.startswith("_")
272
+ ]
273
+ self.dap_combo.addItems(available_models)
274
+
275
+ def add_scan_row(self):
276
+ if self.config is not None:
277
+ self.device_line_edit.setText(self.config.signals.y.name)
278
+ self.entry_line_edit.setText(self.config.signals.y.entry)
279
+ self.color_button.setColor(self.config.color)
280
+ self.style_combo.setCurrentText(self.config.pen_style)
281
+ self.width.setValue(self.config.pen_width)
282
+ self.symbol_size.setValue(self.config.symbol_size)
283
+ else:
284
+ default_color = Colors.golden_angle_color(
285
+ colormap="magma", num=self.row + 1, format="HEX"
286
+ )[-1]
287
+ self.color_button.setColor(default_color)
288
+
289
+ self.table_widget.setCellWidget(self.row, 0, self.device_line_edit)
290
+ self.table_widget.setCellWidget(self.row, 1, self.entry_line_edit)
291
+ self.table_widget.setCellWidget(self.row, 2, self.color_button)
292
+ self.table_widget.setCellWidget(self.row, 3, self.style_combo)
293
+ self.table_widget.setCellWidget(self.row, 4, self.width)
294
+ self.table_widget.setCellWidget(self.row, 5, self.symbol_size)
295
+ self.table_widget.setCellWidget(self.row, 6, self.remove_button)
296
+
297
+ def add_dap_row(self):
298
+ if self.config is not None:
299
+ self.device_line_edit.setText(self.config.signals.y.name)
300
+ self.entry_line_edit.setText(self.config.signals.y.entry)
301
+ self.dap_combo.setCurrentText(self.config.signals.dap)
302
+ self.color_button.setColor(self.config.color)
303
+ self.style_combo.setCurrentText(self.config.pen_style)
304
+ self.width.setValue(self.config.pen_width)
305
+ self.symbol_size.setValue(self.config.symbol_size)
306
+ else:
307
+ default_color = Colors.golden_angle_color(
308
+ colormap="magma", num=self.row + 1, format="HEX"
309
+ )[-1]
310
+ self.color_button.setColor(default_color)
311
+
312
+ self.table_widget.setCellWidget(self.row, 0, self.device_line_edit)
313
+ self.table_widget.setCellWidget(self.row, 1, self.entry_line_edit)
314
+ self.table_widget.setCellWidget(self.row, 2, self.dap_combo)
315
+ self.table_widget.setCellWidget(self.row, 3, self.color_button)
316
+ self.table_widget.setCellWidget(self.row, 4, self.style_combo)
317
+ self.table_widget.setCellWidget(self.row, 5, self.width)
318
+ self.table_widget.setCellWidget(self.row, 6, self.symbol_size)
319
+ self.table_widget.setCellWidget(self.row, 7, self.remove_button)
320
+
321
+ @Slot()
322
+ def remove_row(self):
323
+ row = self.table_widget.indexAt(self.remove_button.pos()).row()
324
+ self.cleanup()
325
+ self.table_widget.removeRow(row)
326
+
327
+ def cleanup(self):
328
+ self.device_line_edit.cleanup()
329
+
330
+
331
+ class StyleComboBox(QComboBox):
332
+ def __init__(self, parent=None):
333
+ super().__init__(parent)
334
+ self.addItems(["solid", "dash", "dot", "dashdot"])
335
+
336
+
337
+ class RemoveButton(QPushButton):
338
+ def __init__(self, parent=None):
339
+ super().__init__(parent)
340
+ icon_path = os.path.join(MODULE_PATH, "assets", "toolbar_icons", "remove.svg")
341
+ self.setIcon(QIcon(icon_path))