bec-widgets 1.25.1__py3-none-any.whl → 2.0.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.
- .gitlab-ci.yml +3 -5
- CHANGELOG.md +631 -0
- PKG-INFO +3 -3
- bec_widgets/__init__.py +4 -0
- bec_widgets/applications/bw_launch.py +23 -0
- bec_widgets/applications/launch_window.py +430 -0
- bec_widgets/assets/app_icons/auto_update.png +0 -0
- bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
- bec_widgets/cli/__init__.py +0 -1
- bec_widgets/cli/client.py +1779 -2064
- bec_widgets/cli/client_utils.py +346 -174
- bec_widgets/cli/generate_cli.py +143 -37
- bec_widgets/cli/rpc/rpc_base.py +152 -21
- bec_widgets/cli/rpc/rpc_register.py +113 -6
- bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
- bec_widgets/cli/server.py +125 -239
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
- bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
- bec_widgets/utils/bec_connector.py +190 -21
- bec_widgets/utils/bec_designer.py +7 -0
- bec_widgets/utils/bec_dispatcher.py +71 -4
- bec_widgets/utils/bec_plugin_helper.py +89 -0
- bec_widgets/utils/bec_signal_proxy.py +1 -1
- bec_widgets/utils/bec_widget.py +26 -10
- bec_widgets/utils/colors.py +1 -1
- bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
- bec_widgets/utils/container_utils.py +37 -12
- bec_widgets/utils/crosshair.py +25 -8
- bec_widgets/utils/entry_validator.py +3 -1
- bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
- bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
- bec_widgets/utils/forms_from_types/forms.py +182 -0
- bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
- bec_widgets/utils/generate_designer_plugin.py +40 -36
- bec_widgets/utils/linear_region_selector.py +2 -0
- bec_widgets/utils/name_utils.py +16 -0
- bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
- bec_widgets/utils/plot_indicator_items.py +2 -5
- bec_widgets/utils/plugin_utils.py +47 -1
- bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
- bec_widgets/utils/rpc_server.py +277 -0
- bec_widgets/utils/serialization.py +44 -0
- bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
- bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
- bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
- bec_widgets/utils/ui_loader.py +8 -8
- bec_widgets/utils/widget_io.py +166 -25
- bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
- bec_widgets/widgets/containers/dock/dock.py +157 -49
- bec_widgets/widgets/containers/dock/dock_area.py +186 -138
- bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
- bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
- bec_widgets/widgets/containers/main_window/main_window.py +189 -41
- bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
- bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
- bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
- bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
- bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
- bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +31 -13
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +3 -1
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
- bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
- bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
- bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
- bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
- bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
- bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
- bec_widgets/widgets/editors/console/console.py +1 -1
- bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
- bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
- bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
- bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
- bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
- bec_widgets/widgets/editors/text_box/text_box.py +2 -3
- bec_widgets/widgets/editors/website/website.py +2 -2
- bec_widgets/widgets/games/minesweeper.py +3 -2
- bec_widgets/widgets/plots/image/image.py +960 -0
- bec_widgets/widgets/plots/image/image.pyproject +1 -0
- bec_widgets/widgets/plots/image/image_item.py +279 -0
- bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
- bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
- bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
- bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
- bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
- bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
- bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
- bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
- bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
- bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
- bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
- bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
- bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
- bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
- bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
- bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
- bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
- bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
- bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
- bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
- bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
- bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
- bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
- bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
- bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
- bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
- bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
- bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
- bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
- bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
- bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
- bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
- bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
- bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
- bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
- bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/METADATA +3 -3
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/RECORD +168 -153
- pyproject.toml +3 -3
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
- bec_widgets/applications/bec_app.py +0 -84
- bec_widgets/cli/auto_updates.py +0 -168
- bec_widgets/widgets/containers/figure/__init__.py +0 -1
- bec_widgets/widgets/containers/figure/figure.py +0 -796
- bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
- bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
- bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
- bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
- bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
- bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
- bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
- bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
- bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
- bec_widgets/widgets/plots/image/image_widget.py +0 -515
- bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
- bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
- bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
- bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
- bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
- bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
- /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
- /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
- /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
- /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
- /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/WHEEL +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,576 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import json
|
4
|
+
from typing import TYPE_CHECKING
|
5
|
+
|
6
|
+
from bec_lib.logger import bec_logger
|
7
|
+
from bec_qthemes._icon.material_icons import material_icon
|
8
|
+
from qtpy.QtGui import QColor
|
9
|
+
from qtpy.QtWidgets import (
|
10
|
+
QColorDialog,
|
11
|
+
QComboBox,
|
12
|
+
QHBoxLayout,
|
13
|
+
QLabel,
|
14
|
+
QLineEdit,
|
15
|
+
QPushButton,
|
16
|
+
QSizePolicy,
|
17
|
+
QSpinBox,
|
18
|
+
QToolButton,
|
19
|
+
QTreeWidget,
|
20
|
+
QTreeWidgetItem,
|
21
|
+
QVBoxLayout,
|
22
|
+
QWidget,
|
23
|
+
)
|
24
|
+
|
25
|
+
from bec_widgets.utils import ConnectionConfig, EntryValidator
|
26
|
+
from bec_widgets.utils.bec_widget import BECWidget
|
27
|
+
from bec_widgets.utils.colors import Colors
|
28
|
+
from bec_widgets.utils.toolbar import MaterialIconAction, ModularToolBar
|
29
|
+
from bec_widgets.widgets.control.device_input.device_line_edit.device_line_edit import (
|
30
|
+
DeviceLineEdit,
|
31
|
+
)
|
32
|
+
from bec_widgets.widgets.dap.dap_combo_box.dap_combo_box import DapComboBox
|
33
|
+
from bec_widgets.widgets.plots.waveform.curve import CurveConfig, DeviceSignal
|
34
|
+
from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget
|
35
|
+
|
36
|
+
if TYPE_CHECKING: # pragma: no cover
|
37
|
+
from bec_widgets.widgets.plots.waveform.waveform import Waveform
|
38
|
+
|
39
|
+
|
40
|
+
logger = bec_logger.logger
|
41
|
+
|
42
|
+
|
43
|
+
class ColorButton(QPushButton):
|
44
|
+
"""A QPushButton subclass that displays a color.
|
45
|
+
|
46
|
+
The background is set to the given color and the button text is the hex code.
|
47
|
+
The text color is chosen automatically (black if the background is light, white if dark)
|
48
|
+
to guarantee good readability.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def __init__(self, color="#000000", parent=None):
|
52
|
+
"""Initialize the color button.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
color (str): The initial color in hex format (e.g., '#000000').
|
56
|
+
parent: Optional QWidget parent.
|
57
|
+
"""
|
58
|
+
super().__init__(parent)
|
59
|
+
self.set_color(color)
|
60
|
+
|
61
|
+
def set_color(self, color):
|
62
|
+
"""Set the button's color and update its appearance.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
color (str or QColor): The new color to assign.
|
66
|
+
"""
|
67
|
+
if isinstance(color, QColor):
|
68
|
+
self._color = color.name()
|
69
|
+
else:
|
70
|
+
self._color = color
|
71
|
+
self._update_appearance()
|
72
|
+
|
73
|
+
def color(self):
|
74
|
+
"""Return the current color in hex."""
|
75
|
+
return self._color
|
76
|
+
|
77
|
+
def _update_appearance(self):
|
78
|
+
"""Update the button style based on the background color's brightness."""
|
79
|
+
c = QColor(self._color)
|
80
|
+
brightness = c.lightnessF()
|
81
|
+
text_color = "#000000" if brightness > 0.5 else "#FFFFFF"
|
82
|
+
self.setStyleSheet(f"background-color: {self._color}; color: {text_color};")
|
83
|
+
self.setText(self._color)
|
84
|
+
|
85
|
+
|
86
|
+
class CurveRow(QTreeWidgetItem):
|
87
|
+
DELETE_BUTTON_COLOR = "#CC181E"
|
88
|
+
"""A unified row that can represent either a device or a DAP curve.
|
89
|
+
|
90
|
+
Columns:
|
91
|
+
0: Actions (delete or "Add DAP" if source=device)
|
92
|
+
1..2: DeviceLineEdit and QLineEdit if source=device, or "Model" label and DapComboBox if source=dap
|
93
|
+
3: ColorButton
|
94
|
+
4: Style QComboBox
|
95
|
+
5: Pen width QSpinBox
|
96
|
+
6: Symbol size QSpinBox
|
97
|
+
"""
|
98
|
+
|
99
|
+
def __init__(
|
100
|
+
self,
|
101
|
+
tree: QTreeWidget,
|
102
|
+
parent_item: QTreeWidgetItem | None = None,
|
103
|
+
config: CurveConfig | None = None,
|
104
|
+
device_manager=None,
|
105
|
+
):
|
106
|
+
if parent_item:
|
107
|
+
super().__init__(parent_item)
|
108
|
+
else:
|
109
|
+
# A top-level device row.
|
110
|
+
super().__init__(tree)
|
111
|
+
|
112
|
+
self.tree = tree
|
113
|
+
self.parent_item = parent_item
|
114
|
+
self.curve_tree = tree.parent() # The CurveTree widget
|
115
|
+
self.curve_tree.all_items.append(self) # Track stable ordering
|
116
|
+
|
117
|
+
# BEC user input
|
118
|
+
self.device_edit = None
|
119
|
+
self.dap_combo = None
|
120
|
+
|
121
|
+
self.dev = device_manager
|
122
|
+
self.entry_validator = EntryValidator(self.dev)
|
123
|
+
|
124
|
+
self.config = config or CurveConfig()
|
125
|
+
self.source = self.config.source
|
126
|
+
self.dap_rows = []
|
127
|
+
|
128
|
+
# Create column 0 (Actions)
|
129
|
+
self._init_actions()
|
130
|
+
# Create columns 1..2, depending on source
|
131
|
+
self._init_source_ui()
|
132
|
+
# Create columns 3..6 (color, style, width, symbol)
|
133
|
+
self._init_style_controls()
|
134
|
+
|
135
|
+
def _init_actions(self):
|
136
|
+
"""Create the actions widget in column 0, including a delete button and maybe 'Add DAP'."""
|
137
|
+
self.actions_widget = QWidget()
|
138
|
+
actions_layout = QHBoxLayout(self.actions_widget)
|
139
|
+
actions_layout.setContentsMargins(0, 0, 0, 0)
|
140
|
+
actions_layout.setSpacing(0)
|
141
|
+
|
142
|
+
# Delete button
|
143
|
+
self.delete_button = QToolButton()
|
144
|
+
delete_icon = material_icon(
|
145
|
+
"delete",
|
146
|
+
size=(20, 20),
|
147
|
+
convert_to_pixmap=False,
|
148
|
+
filled=False,
|
149
|
+
color=self.DELETE_BUTTON_COLOR,
|
150
|
+
)
|
151
|
+
self.delete_button.setIcon(delete_icon)
|
152
|
+
self.delete_button.clicked.connect(lambda: self.remove_self())
|
153
|
+
actions_layout.addWidget(self.delete_button)
|
154
|
+
|
155
|
+
# If device row, add "Add DAP" button
|
156
|
+
if self.source == "device":
|
157
|
+
self.add_dap_button = QPushButton("DAP")
|
158
|
+
self.add_dap_button.clicked.connect(lambda: self.add_dap_row())
|
159
|
+
actions_layout.addWidget(self.add_dap_button)
|
160
|
+
|
161
|
+
self.tree.setItemWidget(self, 0, self.actions_widget)
|
162
|
+
|
163
|
+
def _init_source_ui(self):
|
164
|
+
"""Create columns 1 and 2. For device rows, we have device/entry edits; for dap rows, label/model combo."""
|
165
|
+
if self.source == "device":
|
166
|
+
# Device row: columns 1..2 are device line edits
|
167
|
+
self.device_edit = DeviceLineEdit(parent=self.tree)
|
168
|
+
self.entry_edit = QLineEdit(parent=self.tree) # TODO in future will be signal line edit
|
169
|
+
if self.config.signal:
|
170
|
+
self.device_edit.setText(self.config.signal.name or "")
|
171
|
+
self.entry_edit.setText(self.config.signal.entry or "")
|
172
|
+
|
173
|
+
self.tree.setItemWidget(self, 1, self.device_edit)
|
174
|
+
self.tree.setItemWidget(self, 2, self.entry_edit)
|
175
|
+
|
176
|
+
else:
|
177
|
+
# DAP row: column1= "Model" label, column2= DapComboBox
|
178
|
+
self.label_widget = QLabel("Model")
|
179
|
+
self.tree.setItemWidget(self, 1, self.label_widget)
|
180
|
+
self.dap_combo = DapComboBox(parent=self.tree)
|
181
|
+
self.dap_combo.populate_fit_model_combobox()
|
182
|
+
# If config.signal has a dap
|
183
|
+
if self.config.signal and self.config.signal.dap:
|
184
|
+
dap_value = self.config.signal.dap
|
185
|
+
idx = self.dap_combo.fit_model_combobox.findText(dap_value)
|
186
|
+
if idx >= 0:
|
187
|
+
self.dap_combo.fit_model_combobox.setCurrentIndex(idx)
|
188
|
+
else:
|
189
|
+
self.dap_combo.select_fit_model("GaussianModel") # default
|
190
|
+
|
191
|
+
self.tree.setItemWidget(self, 2, self.dap_combo)
|
192
|
+
|
193
|
+
def _init_style_controls(self):
|
194
|
+
"""Create columns 3..6: color button, style combo, width spin, symbol spin."""
|
195
|
+
# Color in col 3
|
196
|
+
self.color_button = ColorButton(self.config.color)
|
197
|
+
self.color_button.clicked.connect(lambda: self._select_color(self.color_button))
|
198
|
+
self.tree.setItemWidget(self, 3, self.color_button)
|
199
|
+
|
200
|
+
# Style in col 4
|
201
|
+
self.style_combo = QComboBox()
|
202
|
+
self.style_combo.addItems(["solid", "dash", "dot", "dashdot"])
|
203
|
+
idx = self.style_combo.findText(self.config.pen_style)
|
204
|
+
if idx >= 0:
|
205
|
+
self.style_combo.setCurrentIndex(idx)
|
206
|
+
self.tree.setItemWidget(self, 4, self.style_combo)
|
207
|
+
|
208
|
+
# Pen width in col 5
|
209
|
+
self.width_spin = QSpinBox()
|
210
|
+
self.width_spin.setRange(1, 20)
|
211
|
+
self.width_spin.setValue(self.config.pen_width)
|
212
|
+
self.tree.setItemWidget(self, 5, self.width_spin)
|
213
|
+
|
214
|
+
# Symbol size in col 6
|
215
|
+
self.symbol_spin = QSpinBox()
|
216
|
+
self.symbol_spin.setRange(1, 20)
|
217
|
+
self.symbol_spin.setValue(self.config.symbol_size)
|
218
|
+
self.tree.setItemWidget(self, 6, self.symbol_spin)
|
219
|
+
|
220
|
+
def _select_color(self, button):
|
221
|
+
"""
|
222
|
+
Selects a new color using a color dialog and applies it to the specified button. Updates
|
223
|
+
related configuration properties based on the chosen color.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
button: The button widget whose color is being modified.
|
227
|
+
"""
|
228
|
+
current_color = QColor(button.color())
|
229
|
+
chosen_color = QColorDialog.getColor(current_color, self.tree, "Select Curve Color")
|
230
|
+
if chosen_color.isValid():
|
231
|
+
button.set_color(chosen_color)
|
232
|
+
self.config.color = chosen_color.name()
|
233
|
+
self.config.symbol_color = chosen_color.name()
|
234
|
+
|
235
|
+
def add_dap_row(self):
|
236
|
+
"""Create a new DAP row as a child. Only valid if source='device'."""
|
237
|
+
if self.source != "device":
|
238
|
+
return
|
239
|
+
curve_tree = self.tree.parent()
|
240
|
+
parent_label = self.config.label
|
241
|
+
|
242
|
+
# Inherit device name/entry
|
243
|
+
dev_name = ""
|
244
|
+
dev_entry = ""
|
245
|
+
if self.config.signal:
|
246
|
+
dev_name = self.config.signal.name
|
247
|
+
dev_entry = self.config.signal.entry
|
248
|
+
|
249
|
+
# Create a new config for the DAP row
|
250
|
+
dap_cfg = CurveConfig(
|
251
|
+
widget_class="Curve",
|
252
|
+
source="dap",
|
253
|
+
parent_label=parent_label,
|
254
|
+
signal=DeviceSignal(name=dev_name, entry=dev_entry),
|
255
|
+
)
|
256
|
+
new_dap = CurveRow(self.tree, parent_item=self, config=dap_cfg, device_manager=self.dev)
|
257
|
+
# Expand device row to show new child
|
258
|
+
self.tree.expandItem(self)
|
259
|
+
|
260
|
+
# Give the new row a color from the buffer:
|
261
|
+
curve_tree._ensure_color_buffer_size()
|
262
|
+
idx = len(curve_tree.all_items) - 1
|
263
|
+
new_col = curve_tree.color_buffer[idx]
|
264
|
+
new_dap.color_button.set_color(new_col)
|
265
|
+
new_dap.config.color = new_col
|
266
|
+
new_dap.config.symbol_color = new_col
|
267
|
+
|
268
|
+
def remove_self(self):
|
269
|
+
"""Remove this row from the tree and from the parent's item list."""
|
270
|
+
# Recursively remove all child rows first
|
271
|
+
for i in reversed(range(self.childCount())):
|
272
|
+
child = self.child(i)
|
273
|
+
if isinstance(child, CurveRow):
|
274
|
+
child.remove_self()
|
275
|
+
|
276
|
+
# Clean up the widget references if they still exist
|
277
|
+
if getattr(self, "device_edit", None) is not None:
|
278
|
+
self.device_edit.close()
|
279
|
+
self.device_edit.deleteLater()
|
280
|
+
self.device_edit = None
|
281
|
+
|
282
|
+
if getattr(self, "dap_combo", None) is not None:
|
283
|
+
self.dap_combo.close()
|
284
|
+
self.dap_combo.deleteLater()
|
285
|
+
self.dap_combo = None
|
286
|
+
|
287
|
+
# Remove the item from the tree widget
|
288
|
+
index = self.tree.indexOfTopLevelItem(self)
|
289
|
+
if index != -1:
|
290
|
+
self.tree.takeTopLevelItem(index)
|
291
|
+
elif self.parent_item:
|
292
|
+
self.parent_item.removeChild(self)
|
293
|
+
|
294
|
+
# Finally, remove self from the registration list in the curve tree
|
295
|
+
curve_tree = self.tree.parent()
|
296
|
+
if self in curve_tree.all_items:
|
297
|
+
curve_tree.all_items.remove(self)
|
298
|
+
|
299
|
+
def export_data(self) -> dict:
|
300
|
+
"""Collect data from the GUI widgets, update config, and return as a dict.
|
301
|
+
|
302
|
+
Returns:
|
303
|
+
dict: The serialized config based on the GUI state.
|
304
|
+
"""
|
305
|
+
if self.source == "device":
|
306
|
+
# Gather device name/entry
|
307
|
+
device_name = ""
|
308
|
+
device_entry = ""
|
309
|
+
if hasattr(self, "device_edit"):
|
310
|
+
device_name = self.device_edit.text()
|
311
|
+
if hasattr(self, "entry_edit"):
|
312
|
+
device_entry = self.entry_validator.validate_signal(
|
313
|
+
name=device_name, entry=self.entry_edit.text()
|
314
|
+
)
|
315
|
+
self.entry_edit.setText(device_entry)
|
316
|
+
self.config.signal = DeviceSignal(name=device_name, entry=device_entry)
|
317
|
+
self.config.source = "device"
|
318
|
+
self.config.label = f"{device_name}-{device_entry}"
|
319
|
+
else:
|
320
|
+
# DAP logic
|
321
|
+
parent_conf_dict = {}
|
322
|
+
if self.parent_item:
|
323
|
+
parent_conf_dict = self.parent_item.export_data()
|
324
|
+
parent_conf = CurveConfig(**parent_conf_dict)
|
325
|
+
dev_name = ""
|
326
|
+
dev_entry = ""
|
327
|
+
if parent_conf.signal:
|
328
|
+
dev_name = parent_conf.signal.name
|
329
|
+
dev_entry = parent_conf.signal.entry
|
330
|
+
# Dap from the DapComboBox
|
331
|
+
new_dap = "GaussianModel"
|
332
|
+
if hasattr(self, "dap_combo"):
|
333
|
+
new_dap = self.dap_combo.fit_model_combobox.currentText()
|
334
|
+
self.config.signal = DeviceSignal(name=dev_name, entry=dev_entry, dap=new_dap)
|
335
|
+
self.config.source = "dap"
|
336
|
+
self.config.parent_label = parent_conf.label
|
337
|
+
self.config.label = f"{parent_conf.label}-{new_dap}"
|
338
|
+
|
339
|
+
# Common style fields
|
340
|
+
self.config.color = self.color_button.color()
|
341
|
+
self.config.symbol_color = self.color_button.color()
|
342
|
+
self.config.pen_style = self.style_combo.currentText()
|
343
|
+
self.config.pen_width = self.width_spin.value()
|
344
|
+
self.config.symbol_size = self.symbol_spin.value()
|
345
|
+
|
346
|
+
return self.config.model_dump()
|
347
|
+
|
348
|
+
def closeEvent(self, event) -> None:
|
349
|
+
logger.info(f"CurveRow closeEvent: {self.config.label}")
|
350
|
+
return super().closeEvent(event)
|
351
|
+
|
352
|
+
|
353
|
+
class CurveTree(BECWidget, QWidget):
|
354
|
+
"""A tree widget that manages device and DAP curves."""
|
355
|
+
|
356
|
+
PLUGIN = False
|
357
|
+
RPC = False
|
358
|
+
|
359
|
+
def __init__(
|
360
|
+
self,
|
361
|
+
parent: QWidget | None = None,
|
362
|
+
config: ConnectionConfig | None = None,
|
363
|
+
client=None,
|
364
|
+
gui_id: str | None = None,
|
365
|
+
waveform: Waveform | None = None,
|
366
|
+
**kwargs,
|
367
|
+
) -> None:
|
368
|
+
if config is None:
|
369
|
+
config = ConnectionConfig(widget_class=self.__class__.__name__)
|
370
|
+
super().__init__(parent=parent, client=client, gui_id=gui_id, config=config, **kwargs)
|
371
|
+
|
372
|
+
self.waveform = waveform
|
373
|
+
if self.waveform and hasattr(self.waveform, "color_palette"):
|
374
|
+
self.color_palette = self.waveform.color_palette
|
375
|
+
else:
|
376
|
+
self.color_palette = "plasma"
|
377
|
+
|
378
|
+
self.get_bec_shortcuts()
|
379
|
+
|
380
|
+
self.color_buffer = []
|
381
|
+
self.all_items = []
|
382
|
+
self.layout = QVBoxLayout(self)
|
383
|
+
self._init_toolbar()
|
384
|
+
self._init_tree()
|
385
|
+
self.refresh_from_waveform()
|
386
|
+
|
387
|
+
def _init_toolbar(self):
|
388
|
+
"""Initialize the toolbar with actions: add, send, refresh, expand, collapse, renormalize."""
|
389
|
+
self.toolbar = ModularToolBar(parent=self, target_widget=self, orientation="horizontal")
|
390
|
+
add = MaterialIconAction(
|
391
|
+
icon_name="add", tooltip="Add new curve", checkable=False, parent=self
|
392
|
+
)
|
393
|
+
expand = MaterialIconAction(
|
394
|
+
icon_name="unfold_more", tooltip="Expand All DAP", checkable=False, parent=self
|
395
|
+
)
|
396
|
+
collapse = MaterialIconAction(
|
397
|
+
icon_name="unfold_less", tooltip="Collapse All DAP", checkable=False, parent=self
|
398
|
+
)
|
399
|
+
|
400
|
+
self.toolbar.add_action("add", add, self)
|
401
|
+
self.toolbar.add_action("expand_all", expand, self)
|
402
|
+
self.toolbar.add_action("collapse_all", collapse, self)
|
403
|
+
|
404
|
+
# Add colormap widget (not updating waveform's color_palette until Send is pressed)
|
405
|
+
self.spacer = QWidget()
|
406
|
+
self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
407
|
+
self.toolbar.addWidget(self.spacer)
|
408
|
+
|
409
|
+
# Renormalize colors button
|
410
|
+
renorm_action = MaterialIconAction(
|
411
|
+
icon_name="palette", tooltip="Normalize All Colors", checkable=False, parent=self
|
412
|
+
)
|
413
|
+
self.toolbar.add_action("renormalize_colors", renorm_action, self)
|
414
|
+
renorm_action.action.triggered.connect(lambda checked: self.renormalize_colors())
|
415
|
+
|
416
|
+
self.colormap_widget = BECColorMapWidget(cmap=self.color_palette or "plasma")
|
417
|
+
self.toolbar.addWidget(self.colormap_widget)
|
418
|
+
self.colormap_widget.colormap_changed_signal.connect(self.handle_colormap_changed)
|
419
|
+
|
420
|
+
add.action.triggered.connect(lambda checked: self.add_new_curve())
|
421
|
+
expand.action.triggered.connect(lambda checked: self.expand_all_daps())
|
422
|
+
collapse.action.triggered.connect(lambda checked: self.collapse_all_daps())
|
423
|
+
|
424
|
+
self.layout.addWidget(self.toolbar)
|
425
|
+
|
426
|
+
def _init_tree(self):
|
427
|
+
"""Initialize the QTreeWidget with 7 columns and compact widths."""
|
428
|
+
self.tree = QTreeWidget()
|
429
|
+
self.tree.setColumnCount(7)
|
430
|
+
self.tree.setHeaderLabels(["Actions", "Name", "Entry", "Color", "Style", "Width", "Symbol"])
|
431
|
+
self.tree.setColumnWidth(0, 90)
|
432
|
+
self.tree.setColumnWidth(1, 100)
|
433
|
+
self.tree.setColumnWidth(2, 100)
|
434
|
+
self.tree.setColumnWidth(3, 70)
|
435
|
+
self.tree.setColumnWidth(4, 80)
|
436
|
+
self.tree.setColumnWidth(5, 40)
|
437
|
+
self.tree.setColumnWidth(6, 40)
|
438
|
+
self.layout.addWidget(self.tree)
|
439
|
+
|
440
|
+
def _init_color_buffer(self, size: int):
|
441
|
+
"""
|
442
|
+
Initializes the color buffer with a calculated set of colors based on the golden
|
443
|
+
angle sequence.
|
444
|
+
|
445
|
+
Args:
|
446
|
+
size (int): The number of colors to be generated for the color buffer.
|
447
|
+
"""
|
448
|
+
self.color_buffer = Colors.golden_angle_color(
|
449
|
+
colormap=self.colormap_widget.colormap, num=size, format="HEX"
|
450
|
+
)
|
451
|
+
|
452
|
+
def _ensure_color_buffer_size(self):
|
453
|
+
"""
|
454
|
+
Ensures that the color buffer size meets the required number of items.
|
455
|
+
"""
|
456
|
+
current_count = len(self.color_buffer)
|
457
|
+
color_list = Colors.golden_angle_color(
|
458
|
+
colormap=self.color_palette, num=max(10, current_count + 1), format="HEX"
|
459
|
+
)
|
460
|
+
self.color_buffer = color_list
|
461
|
+
|
462
|
+
def handle_colormap_changed(self, new_cmap: str):
|
463
|
+
"""
|
464
|
+
Handles the updating of the color palette when the colormap is changed.
|
465
|
+
|
466
|
+
Args:
|
467
|
+
new_cmap: The new colormap to be set as the color palette.
|
468
|
+
"""
|
469
|
+
self.color_palette = new_cmap
|
470
|
+
|
471
|
+
def renormalize_colors(self):
|
472
|
+
"""Overwrite all existing rows with new colors from the buffer in their creation order."""
|
473
|
+
total = len(self.all_items)
|
474
|
+
self._ensure_color_buffer_size()
|
475
|
+
for idx, item in enumerate(self.all_items):
|
476
|
+
if hasattr(item, "color_button"):
|
477
|
+
new_col = self.color_buffer[idx]
|
478
|
+
item.color_button.set_color(new_col)
|
479
|
+
if hasattr(item, "config"):
|
480
|
+
item.config.color = new_col
|
481
|
+
item.config.symbol_color = new_col
|
482
|
+
|
483
|
+
def add_new_curve(self, name: str = None, entry: str = None):
|
484
|
+
"""Add a new device-type CurveRow with an assigned colormap color.
|
485
|
+
|
486
|
+
Args:
|
487
|
+
name (str, optional): Device name.
|
488
|
+
entry (str, optional): Device entry.
|
489
|
+
style (str, optional): Pen style. Defaults to "solid".
|
490
|
+
width (int, optional): Pen width. Defaults to 4.
|
491
|
+
symbol_size (int, optional): Symbol size. Defaults to 7.
|
492
|
+
|
493
|
+
Returns:
|
494
|
+
CurveRow: The newly created top-level row.
|
495
|
+
"""
|
496
|
+
cfg = CurveConfig(
|
497
|
+
widget_class="Curve",
|
498
|
+
parent_id=self.waveform.gui_id,
|
499
|
+
source="device",
|
500
|
+
signal=DeviceSignal(name=name or "", entry=entry or ""),
|
501
|
+
)
|
502
|
+
new_row = CurveRow(self.tree, parent_item=None, config=cfg, device_manager=self.dev)
|
503
|
+
|
504
|
+
# Assign color from the buffer ONLY to this new curve.
|
505
|
+
total_items = len(self.all_items)
|
506
|
+
self._ensure_color_buffer_size()
|
507
|
+
color_idx = total_items - 1 # new row is last
|
508
|
+
new_col = self.color_buffer[color_idx]
|
509
|
+
new_row.color_button.set_color(new_col)
|
510
|
+
new_row.config.color = new_col
|
511
|
+
new_row.config.symbol_color = new_col
|
512
|
+
|
513
|
+
return new_row
|
514
|
+
|
515
|
+
def send_curve_json(self):
|
516
|
+
"""Send the current tree's config as JSON to the waveform, updating wavefrom.color_palette as well."""
|
517
|
+
if self.waveform is not None:
|
518
|
+
self.waveform.color_palette = self.color_palette
|
519
|
+
data = self.export_all_curves()
|
520
|
+
json_data = json.dumps(data, indent=2)
|
521
|
+
if self.waveform is not None:
|
522
|
+
self.waveform.curve_json = json_data
|
523
|
+
|
524
|
+
def export_all_curves(self) -> list:
|
525
|
+
"""Recursively export data from each row.
|
526
|
+
|
527
|
+
Returns:
|
528
|
+
list: A list of exported config dicts for every row (device and DAP).
|
529
|
+
"""
|
530
|
+
curves = []
|
531
|
+
for i in range(self.tree.topLevelItemCount()):
|
532
|
+
item = self.tree.topLevelItem(i)
|
533
|
+
if isinstance(item, CurveRow):
|
534
|
+
curves.append(item.export_data())
|
535
|
+
for j in range(item.childCount()):
|
536
|
+
child = item.child(j)
|
537
|
+
if isinstance(child, CurveRow):
|
538
|
+
curves.append(child.export_data())
|
539
|
+
return curves
|
540
|
+
|
541
|
+
def expand_all_daps(self):
|
542
|
+
"""Expand all top-level rows to reveal child DAP rows."""
|
543
|
+
for i in range(self.tree.topLevelItemCount()):
|
544
|
+
item = self.tree.topLevelItem(i)
|
545
|
+
self.tree.expandItem(item)
|
546
|
+
|
547
|
+
def collapse_all_daps(self):
|
548
|
+
"""Collapse all top-level rows, hiding child DAP rows."""
|
549
|
+
for i in range(self.tree.topLevelItemCount()):
|
550
|
+
item = self.tree.topLevelItem(i)
|
551
|
+
self.tree.collapseItem(item)
|
552
|
+
|
553
|
+
def refresh_from_waveform(self):
|
554
|
+
"""Clear the tree and rebuild from the waveform's existing curves if any, else add sample rows."""
|
555
|
+
if self.waveform is None:
|
556
|
+
return
|
557
|
+
self.tree.clear()
|
558
|
+
self.all_items = []
|
559
|
+
|
560
|
+
device_curves = [c for c in self.waveform.curves if c.config.source == "device"]
|
561
|
+
dap_curves = [c for c in self.waveform.curves if c.config.source == "dap"]
|
562
|
+
for dev in device_curves:
|
563
|
+
dr = CurveRow(self.tree, parent_item=None, config=dev.config, device_manager=self.dev)
|
564
|
+
for dap in dap_curves:
|
565
|
+
if dap.config.parent_label == dev.config.label:
|
566
|
+
CurveRow(self.tree, parent_item=dr, config=dap.config, device_manager=self.dev)
|
567
|
+
|
568
|
+
def cleanup(self):
|
569
|
+
"""Cleanup the widget."""
|
570
|
+
all_items = list(self.all_items)
|
571
|
+
for item in all_items:
|
572
|
+
item.remove_self()
|
573
|
+
|
574
|
+
def closeEvent(self, event):
|
575
|
+
self.cleanup()
|
576
|
+
return super().closeEvent(event)
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import pyqtgraph as pg
|
2
|
+
from qtpy.QtCore import QObject, Signal, Slot
|
3
|
+
|
4
|
+
from bec_widgets.utils.colors import get_accent_colors
|
5
|
+
from bec_widgets.utils.linear_region_selector import LinearRegionWrapper
|
6
|
+
|
7
|
+
|
8
|
+
class WaveformROIManager(QObject):
|
9
|
+
"""
|
10
|
+
A reusable helper class that manages a single linear ROI region on a given plot item.
|
11
|
+
It provides signals to notify about region changes and active state.
|
12
|
+
"""
|
13
|
+
|
14
|
+
roi_changed = Signal(tuple) # Emitted when the ROI (left, right) changes
|
15
|
+
roi_active = Signal(bool) # Emitted when ROI is enabled or disabled
|
16
|
+
|
17
|
+
def __init__(self, plot_item: pg.PlotItem, parent=None):
|
18
|
+
super().__init__(parent)
|
19
|
+
self._plot_item = plot_item
|
20
|
+
self._roi_wrapper: LinearRegionWrapper | None = None
|
21
|
+
self._roi_region: tuple[float, float] | None = None
|
22
|
+
self._accent_colors = get_accent_colors()
|
23
|
+
|
24
|
+
@property
|
25
|
+
def roi_region(self) -> tuple[float, float] | None:
|
26
|
+
return self._roi_region
|
27
|
+
|
28
|
+
@roi_region.setter
|
29
|
+
def roi_region(self, value: tuple[float, float] | None):
|
30
|
+
self._roi_region = value
|
31
|
+
if self._roi_wrapper is not None and value is not None:
|
32
|
+
self._roi_wrapper.linear_region_selector.setRegion(value)
|
33
|
+
|
34
|
+
@Slot(bool)
|
35
|
+
def toggle_roi(self, enabled: bool) -> None:
|
36
|
+
if enabled:
|
37
|
+
self._enable_roi()
|
38
|
+
else:
|
39
|
+
self._disable_roi()
|
40
|
+
|
41
|
+
@Slot(tuple)
|
42
|
+
def select_roi(self, region: tuple[float, float]):
|
43
|
+
# If ROI not present, enabling it
|
44
|
+
if self._roi_wrapper is None:
|
45
|
+
self.toggle_roi(True)
|
46
|
+
self.roi_region = region
|
47
|
+
|
48
|
+
def _enable_roi(self):
|
49
|
+
if self._roi_wrapper is not None:
|
50
|
+
# Already enabled
|
51
|
+
return
|
52
|
+
color = self._accent_colors.default
|
53
|
+
color.setAlpha(int(0.2 * 255))
|
54
|
+
hover_color = self._accent_colors.default
|
55
|
+
hover_color.setAlpha(int(0.35 * 255))
|
56
|
+
|
57
|
+
self._roi_wrapper = LinearRegionWrapper(
|
58
|
+
self._plot_item, color=color, hover_color=hover_color, parent=self
|
59
|
+
)
|
60
|
+
self._roi_wrapper.add_region_selector()
|
61
|
+
self._roi_wrapper.region_changed.connect(self._on_region_changed)
|
62
|
+
|
63
|
+
# If we already had a region, apply it
|
64
|
+
if self._roi_region is not None:
|
65
|
+
self._roi_wrapper.linear_region_selector.setRegion(self._roi_region)
|
66
|
+
else:
|
67
|
+
self._roi_region = self._roi_wrapper.linear_region_selector.getRegion()
|
68
|
+
|
69
|
+
self.roi_active.emit(True)
|
70
|
+
|
71
|
+
def _disable_roi(self):
|
72
|
+
if self._roi_wrapper is not None:
|
73
|
+
self._roi_wrapper.region_changed.disconnect(self._on_region_changed)
|
74
|
+
self._roi_wrapper.cleanup()
|
75
|
+
self._roi_wrapper.deleteLater()
|
76
|
+
self._roi_wrapper = None
|
77
|
+
|
78
|
+
self._roi_region = None
|
79
|
+
self.roi_active.emit(False)
|
80
|
+
|
81
|
+
@Slot(tuple)
|
82
|
+
def _on_region_changed(self, region: tuple[float, float]):
|
83
|
+
self._roi_region = region
|
84
|
+
self.roi_changed.emit(region)
|