diff --git a/bec_widgets/applications/__init__.py b/bec_widgets/applications/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bec_widgets/applications/alignment/__init__.py b/bec_widgets/applications/alignment/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bec_widgets/applications/alignment/alignment_1d/__init__.py b/bec_widgets/applications/alignment/alignment_1d/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py
index 33a35558..c5d7ebc8 100644
--- a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py
+++ b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.py
@@ -1,6 +1,5 @@
""" This module contains the GUI for the 1D alignment application.
-#TODO at this stage it is a preliminary version of the GUI, which will be added to the main branch although it is not yet fully functional.
-It is a work in progress and will be updated in the future.
+It is a preliminary version of the GUI, which will be added to the main branch and steadily updated to be improved.
"""
import os
@@ -8,24 +7,32 @@ from typing import Optional
from bec_lib.device import Positioner, Signal
from bec_lib.endpoints import MessageEndpoints
+from qtpy.QtCore import QSize
from qtpy.QtCore import Signal as pyqtSignal
-from qtpy.QtWidgets import QCheckBox, QDoubleSpinBox, QSpinBox, QVBoxLayout, QWidget
+from qtpy.QtGui import QIcon
+from qtpy.QtWidgets import QCheckBox, QDoubleSpinBox, QPushButton, QSpinBox, QVBoxLayout, QWidget
+import bec_widgets
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
from bec_widgets.qt_utils.toolbar import WidgetAction
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_widget import BECWidget
+from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.widgets.bec_progressbar.bec_progressbar import BECProgressBar
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
from bec_widgets.widgets.lmfit_dialog.lmfit_dialog import LMFitDialog
-from bec_widgets.widgets.positioner_box.positioner_control_line import PositionerControlLine
+from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
+from bec_widgets.widgets.stop_button.stop_button import StopButton
from bec_widgets.widgets.toggle.toggle import ToggleSwitch
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
+MODULE_PATH = os.path.dirname(bec_widgets.__file__)
+
class Alignment1D(BECWidget, QWidget):
- """GUI for 1D alignment"""
+ """Alignment GUI to perform 1D scans"""
+ # Emit a signal when a motion is ongoing
motion_is_active = pyqtSignal(bool)
def __init__(
@@ -42,6 +49,7 @@ class Alignment1D(BECWidget, QWidget):
super().__init__(client=client, gui_id=gui_id)
QWidget.__init__(self, parent)
self.get_bec_shortcuts()
+ self._accent_colors = get_accent_colors()
self.ui_file = "alignment_1d.ui"
self.ui = None
self.progress_bar = None
@@ -50,57 +58,61 @@ class Alignment1D(BECWidget, QWidget):
def init_ui(self):
"""Initialise the UI from QT Designer file"""
-
current_path = os.path.dirname(__file__)
self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file))
layout = QVBoxLayout(self)
layout.addWidget(self.ui)
self.setLayout(layout)
- self.waveform = self.ui.findChild(BECWaveformWidget, "bec_waveform_widget")
# Customize the plotting widget
+ self.waveform = self.ui.findChild(BECWaveformWidget, "bec_waveform_widget")
self._customise_bec_waveform_widget()
- # Setup filters for comboboxes
+ # Setup comboboxes for motor and signal selection
+ # FIXME after changing the filtering in the combobox
self._setup_motor_combobox()
self._setup_signal_combobox()
- # Setup arrow item
- self._setup_arrow_item()
- # Setup Scan Control
+ # Setup motor indicator
+ self._setup_motor_indicator()
+ # Connect spinboxes to scan Control
self._setup_scan_control()
# Setup progress bar
self._setup_progress_bar()
# Add actions buttons
- self._add_action_buttons()
+ self._customise_buttons()
+ # Customize the positioner box
+ self._customize_positioner_box()
# Hook scaninfo updates
- self.bec_dispatcher.connect_slot(self._scan_status_callback, MessageEndpoints.scan_status())
+ self.bec_dispatcher.connect_slot(self.scan_status_callback, MessageEndpoints.scan_status())
##############################
############ SLOTS ###########
##############################
@Slot(dict, dict)
- def _scan_status_callback(self, content: dict, metadata: dict) -> None:
+ def scan_status_callback(self, content: dict, _) -> None:
"""This slot allows to enable/disable the UI critical components when a scan is running"""
if content["status"] in ["running", "open"]:
self.motion_is_active.emit(True)
- self._enable_ui(False)
+ self.enable_ui(False)
elif content["status"] in ["aborted", "halted", "closed"]:
self.motion_is_active.emit(False)
- self._enable_ui(True)
+ self.enable_ui(True)
- @Slot(float)
- def move_to_center(self, pos: float) -> None:
+ @Slot(tuple)
+ def move_to_center(self, move_request: tuple) -> None:
"""Move the selected motor to the center"""
motor = self.ui.device_combobox.currentText()
- self.dev[motor].move(float(pos), relative=False)
+ if move_request[0] in ["center", "center1", "center2"]:
+ pos = move_request[1]
+ self.dev.get(motor).move(float(pos), relative=False)
@Slot()
- def _reset_progress_bar(self) -> None:
+ def reset_progress_bar(self) -> None:
"""Reset the progress bar"""
self.progress_bar.set_value(0)
self.progress_bar.set_minimum(0)
@Slot(dict, dict)
- def _update_progress_bar(self, content: dict, metadata: dict) -> None:
+ def update_progress_bar(self, content: dict, _) -> None:
"""Hook to update the progress bar
Args:
@@ -113,58 +125,75 @@ class Alignment1D(BECWidget, QWidget):
self.progress_bar.set_maximum(content["max_value"])
self.progress_bar.set_value(content["value"])
+ @Slot()
+ def clear_queue(self) -> None:
+ """Clear the scan queue"""
+ self.queue.request_queue_reset()
+
##############################
######## END OF SLOTS ########
##############################
- def _enable_ui(self, enable: bool) -> None:
+ def enable_ui(self, enable: bool) -> None:
"""Enable or disable the UI components"""
- # Device selection comboboxes
+ # Enable/disable motor and signal selection
self.ui.device_combobox.setEnabled(enable)
self.ui.device_combobox_2.setEnabled(enable)
+ # Enable/disable DAP selection
self.ui.dap_combo_box.setEnabled(enable)
- # Scan button
+ # Enable/disable Scan Button
self.ui.scan_button.setEnabled(enable)
# Positioner control line
# pylint: disable=protected-access
- self.ui.positioner_control_line._toogle_enable_buttons(enable)
- # Send report to scilog
- self.ui.button_send_summary_scilog.setEnabled(enable)
+ self.ui.positioner_box._toogle_enable_buttons(enable)
# Disable move to buttons in LMFitDialog
- self.ui.findChild(LMFitDialog).set_enable_move_to_buttons(enable)
+ self.ui.findChild(LMFitDialog).set_actions_enabled(enable)
- def _add_action_buttons(self) -> None:
- """Add action buttons for the Action Control"""
+ def _customise_buttons(self) -> None:
+ """Add action buttons for the Action Control.
+ In addition, we are adding a callback to also clear the queue to the stop button
+ to ensure that upon clicking the button, no scans from another client may be queued
+ which would be confusing without the queue widget.
+ """
fit_dialog = self.ui.findChild(LMFitDialog)
- fit_dialog.update_activated_button_list(["center", "center1", "center2"])
- fit_dialog.move_to_position.connect(self.move_to_center)
+ fit_dialog.active_action_list = ["center", "center1", "center2"]
+ fit_dialog.move_action.connect(self.move_to_center)
+ scan_button = self.ui.findChild(QPushButton, "scan_button")
+ scan_button.setStyleSheet(
+ f"""
+ QPushButton:enabled {{ background-color: {self._accent_colors.success.name()};color: white; }}
+ QPushButton:disabled {{ background-color: grey;color: white; }}
+ """
+ )
+ stop_button = self.ui.findChild(StopButton)
+ stop_button.button.setText("Stop and Clear Queue")
+ stop_button.button.clicked.connect(self.clear_queue)
def _customise_bec_waveform_widget(self) -> None:
- """Customise the BEC Waveform Widget, i.e. hide toolbar buttons except roi selection"""
- for button in self.waveform.toolbar.widgets.values():
- if getattr(button, "action", None) is not None:
- button.action.setVisible(False)
- self.waveform.toolbar.widgets["roi_select"].action.setVisible(True)
+ """Customise the BEC Waveform Widget, i.e. clear the toolbar, add the DAP ROI selection to the toolbar.
+ We also move the scan_control widget which is fully hidden and solely used for setting up the scan parameters to the toolbar.
+ """
+ self.waveform.toolbar.clear()
toggle_switch = self.ui.findChild(ToggleSwitch, "toggle_switch")
scan_control = self.ui.scan_control
self.waveform.toolbar.populate_toolbar(
{
- "label": WidgetAction(label="Enable DAP ROI", widget=toggle_switch),
+ "label": WidgetAction(label="ENABLE DAP ROI", widget=toggle_switch),
"scan_control": WidgetAction(widget=scan_control),
},
self.waveform,
)
- def _setup_arrow_item(self) -> None:
+ def _setup_motor_indicator(self) -> None:
"""Setup the arrow item"""
- self.waveform.waveform.motor_pos_tick.add_to_plot()
- positioner_line = self.ui.findChild(PositionerControlLine)
- positioner_line.position_update.connect(self.waveform.waveform.motor_pos_tick.set_position)
+ self.waveform.waveform.tick_item.add_to_plot()
+ positioner_box = self.ui.findChild(PositionerBox)
+ positioner_box.position_update.connect(self.waveform.waveform.tick_item.set_position)
try:
- pos = float(positioner_line.ui.readback.text())
+ pos = float(positioner_box.ui.readback.text())
except ValueError:
pos = 0
- self.waveform.waveform.motor_pos_tick.set_position(pos)
+ self.waveform.waveform.tick_item.set_position(pos)
def _setup_motor_combobox(self) -> None:
"""Setup motor selection"""
@@ -182,9 +211,11 @@ class Alignment1D(BECWidget, QWidget):
def _setup_scan_control(self) -> None:
"""Setup scan control, connect spin and check boxes to the scan_control widget"""
+ # Connect motor
device_line_edit = self.ui.scan_control.arg_box.findChild(DeviceLineEdit)
self.ui.device_combobox.currentTextChanged.connect(device_line_edit.setText)
device_line_edit.setText(self.ui.device_combobox.currentText())
+ # Connect start, stop, step, exp_time and relative check box
spin_boxes = self.ui.scan_control.arg_box.findChildren(QDoubleSpinBox)
start = self.ui.findChild(QDoubleSpinBox, "linescan_start")
start.valueChanged.connect(spin_boxes[0].setValue)
@@ -208,18 +239,27 @@ class Alignment1D(BECWidget, QWidget):
# FIXME once the BECScanProgressBar is implemented
self.progress_bar = self.ui.findChild(BECProgressBar, "bec_progress_bar")
self.progress_bar.set_value(0)
- self.ui.bec_waveform_widget.new_scan.connect(self._reset_progress_bar)
- self.bec_dispatcher.connect_slot(
- self._update_progress_bar, MessageEndpoints.scan_progress()
- )
+ self.ui.bec_waveform_widget.new_scan.connect(self.reset_progress_bar)
+ self.bec_dispatcher.connect_slot(self.update_progress_bar, MessageEndpoints.scan_progress())
+
+ def _customize_positioner_box(self) -> None:
+ """Customize the positioner Box, i.e. remove the stop button"""
+ box = self.ui.findChild(PositionerBox)
+ box.ui.stop.setVisible(False)
+ box.ui.position_indicator.setFixedHeight(20)
-if __name__ == "__main__":
+if __name__ == "__main__": # pragma: no cover
import sys
- from qtpy.QtWidgets import QApplication
+ from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
app = QApplication(sys.argv)
+ icon = QIcon()
+ icon.addFile(
+ os.path.join(MODULE_PATH, "assets", "app_icons", "alignment_1d.png"), size=QSize(48, 48)
+ )
+ app.setWindowIcon(icon)
window = Alignment1D()
window.show()
sys.exit(app.exec_())
diff --git a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui
index 6f17e8c0..4972f1dd 100644
--- a/bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui
+++ b/bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui
@@ -6,28 +6,121 @@
0
0
- 1578
- 962
+ 1335
+ 939
Form
-
- 6
-
-
- 12
-
-
- 0
-
-
- 12
-
-
-
+
+
-
+
+
+ -
+
+
+ false
+
+
+ BEC Server State
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ BEC Queue
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ SLS Light On
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ BEAMLINE Checks
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 200
+ 40
+
+
+
+
+ 200
+ 40
+
+
+
+
+
+
+ -
+
-
@@ -39,22 +132,22 @@
0
-
+
Alignment Control
- 4
+ 2
- 4
+ 2
- 4
+ 2
- 4
+ 2
-
@@ -91,19 +184,19 @@
-
-
+
-
- 600
+ 450
95
- 600
- 95
+ 450
+ 16777215
@@ -131,19 +224,6 @@
8
-
-
-
-
-
-
-
- LMFit Model
-
-
-
- -
-
-
-
@@ -154,9 +234,6 @@
- -
-
-
-
@@ -167,6 +244,22 @@
+ -
+
+
+
+
+
+ LMFit Model
+
+
+
+ -
+
+
+ -
+
+
-
@@ -174,299 +267,271 @@
-
-
+
- 600
- 191
+ 450
+ 343
- 600
- 191
+ 450
+ 16777215
-
-
- 15
- true
-
+
+ 0
-
- false
-
-
- LineScan
-
-
-
- 8
-
-
- 8
-
-
- 8
-
-
- 8
-
-
-
-
-
-
- false
-
-
-
- Relative
-
-
-
- -
-
-
- 3
-
-
- -10000000.000000000000000
-
-
- 10000000.000000000000000
-
-
- 0.000000000000000
-
-
-
- -
-
-
- 1
-
-
- 10000000
-
-
- 1
-
-
-
- -
-
-
-
-
-
- Run Scan
-
-
- false
-
-
- false
-
-
-
- -
-
-
-
-
- -
-
-
-
- false
-
-
-
- Start
-
-
-
- -
-
-
-
- false
-
-
-
- Exposure Time
-
-
-
- -
-
-
-
- false
-
-
-
- Steps
-
-
-
- -
-
-
-
- false
-
-
-
- Stop
-
-
-
- -
-
-
-
- false
-
-
-
- Burst at each point
-
-
-
- -
-
-
- 3
-
-
- -10000000.000000000000000
-
-
- 10000000.000000000000000
-
-
- 0.000000000000000
-
-
-
- -
-
-
- 3
-
-
- -10000000.000000000000000
-
-
- 10000000.000000000000000
-
-
- 0.000000000000000
-
-
-
- -
-
-
- 0
-
-
- 10000000
-
-
- 0
-
-
-
- -
-
-
-
-
-
-
-
+
+
+ LineScan
+
+
+ -
+
+
+
+ false
+
+
+
+ Relative
+
+
+
+ -
+
+
+ 3
+
+
+ -10000000.000000000000000
+
+
+ 10000000.000000000000000
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+
+ false
+
+
+
+ Exposure Time
+
+
+
+ -
+
+
+
+ false
+
+
+
+ Start
+
+
+
+ -
+
+
+
+ false
+
+
+
+ Burst at each point
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+ false
+
+
+
+ Stop
+
+
+
+ -
+
+
+ 0
+
+
+ 10000000
+
+
+ 0
+
+
+
+ -
+
+
+
+ 0
+ 40
+
+
+
+
+
+
+ Run Scan
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ 3
+
+
+ -10000000.000000000000000
+
+
+ 10000000.000000000000000
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+ 1
+
+
+ 10000000
+
+
+ 1
+
+
+
+ -
+
+
+
+ false
+
+
+
+ Steps
+
+
+
+ -
+
+
+ 3
+
+
+ -10000000.000000000000000
+
+
+ 10000000.000000000000000
+
+
+ 0.000000000000000
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ MotorTweak
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
-
-
-
+
+
+ Qt::Orientation::Vertical
+
+
- 600
- 201
+ 20
+ 40
-
-
- 600
- 201
-
-
-
-
- 15
- true
-
-
-
- Motor Tweak
-
-
-
- 0
-
-
- 12
-
-
- 0
-
-
- 12
-
-
-
-
-
- true
-
-
-
-
-
-
- -
-
-
-
- 600
- 85
-
-
-
-
- 600
- 85
-
-
-
-
- 15
- true
-
-
-
- Utils
-
-
-
-
-
-
- Send summary to SciLog
-
-
-
-
-
+
@@ -477,7 +542,7 @@
600
- 0
+ 450
@@ -508,6 +573,18 @@
-
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 190
+
+
true
@@ -530,6 +607,18 @@
Logbook
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
-
@@ -544,14 +633,20 @@
-
-
-
-
-
-
- -
-
-
-
+
+
+
+ 0
+ 25
+
+
+
+
+ 16777215
+ 25
+
+
+
@@ -586,16 +681,16 @@
QWidget
-
- PositionerControlLine
- PositionerBox
-
-
BECProgressBar
QWidget
+
+ DarkModeButton
+ QWidget
+
+
BECWaveformWidget
QWidget
@@ -611,16 +706,11 @@
QWidget
-
- AbortButton
- QWidget
-
-
- tabWidget
device_combobox
device_combobox_2
+ tabWidget_2
linescan_start
linescan_stop
linescan_step
@@ -638,12 +728,12 @@
select_x_axis(QString)
- 214
- 130
+ 162
+ 170
- 629
- 130
+ 467
+ 170
@@ -654,12 +744,12 @@
select_y_axis(QString)
- 388
- 130
+ 297
+ 170
- 629
- 130
+ 467
+ 170
@@ -670,8 +760,8 @@
add_dap(QString,QString,QString)
- 629
- 130
+ 467
+ 170
1099
@@ -679,22 +769,6 @@
-
- device_combobox
- currentTextChanged(QString)
- positioner_control_line
- set_positioner(QString)
-
-
- 214
- 130
-
-
- 174
- 550
-
-
-
scan_button
clicked()
@@ -702,12 +776,12 @@
run_scan()
- 332
- 336
+ 455
+ 511
- 28
- 319
+ 16
+ 441
@@ -718,8 +792,8 @@
plot(QString)
- 388
- 130
+ 297
+ 170
1099
@@ -734,8 +808,8 @@
set_x(QString)
- 214
- 130
+ 162
+ 170
1099
@@ -750,8 +824,8 @@
toogle_roi_select(bool)
- 691
- 536
+ 529
+ 728
1099
@@ -770,8 +844,24 @@
258
- 1098
- 668
+ 1157
+ 929
+
+
+
+
+ device_combobox
+ currentTextChanged(QString)
+ positioner_box
+ set_positioner(QString)
+
+
+ 109
+ 155
+
+
+ 160
+ 286
diff --git a/bec_widgets/assets/app_icons/alignment_1d.png b/bec_widgets/assets/app_icons/alignment_1d.png
new file mode 100644
index 00000000..8067b417
Binary files /dev/null and b/bec_widgets/assets/app_icons/alignment_1d.png differ
diff --git a/bec_widgets/qt_utils/toolbar.py b/bec_widgets/qt_utils/toolbar.py
index 5e2db8b8..170b3a54 100644
--- a/bec_widgets/qt_utils/toolbar.py
+++ b/bec_widgets/qt_utils/toolbar.py
@@ -9,7 +9,7 @@ from typing import Literal
from bec_qthemes._icon.material_icons import material_icon
from qtpy.QtCore import QSize
from qtpy.QtGui import QAction, QColor, QIcon
-from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QToolBar, QToolButton, QWidget
+from qtpy.QtWidgets import QHBoxLayout, QLabel, QMenu, QSizePolicy, QToolBar, QToolButton, QWidget
import bec_widgets
@@ -165,7 +165,9 @@ class WidgetAction(ToolBarAction):
layout.setContentsMargins(0, 0, 0, 0)
if self.label is not None:
label = QLabel(f"{self.label}")
+ label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
layout.addWidget(label)
+ self.widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
layout.addWidget(self.widget)
toolbar.addWidget(widget)
diff --git a/bec_widgets/utils/linear_region_selector.py b/bec_widgets/utils/linear_region_selector.py
index 7e906088..8fdeb49f 100644
--- a/bec_widgets/utils/linear_region_selector.py
+++ b/bec_widgets/utils/linear_region_selector.py
@@ -30,7 +30,7 @@ class LinearRegionWrapper(QObject):
self.proxy = None
self.change_roi_color((color, hover_color))
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
- self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
+ self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
# Slot for changing the color of the region selector (edge and fill)
@Slot(tuple)
diff --git a/bec_widgets/utils/plot_indicator_items.py b/bec_widgets/utils/plot_indicator_items.py
index 975fd932..46017737 100644
--- a/bec_widgets/utils/plot_indicator_items.py
+++ b/bec_widgets/utils/plot_indicator_items.py
@@ -2,10 +2,13 @@
import numpy as np
import pyqtgraph as pg
-from qtpy.QtCore import QObject, Qt, Signal, Slot
+from bec_lib.logger import bec_logger
+from qtpy.QtCore import QObject, QPointF, Signal, Slot
from bec_widgets.utils.colors import get_accent_colors
+logger = bec_logger.logger
+
class BECIndicatorItem(QObject):
@@ -13,10 +16,20 @@ class BECIndicatorItem(QObject):
super().__init__(parent=parent)
self.accent_colors = get_accent_colors()
self.plot_item = plot_item
+ self._item_on_plot = False
self._pos = None
self.is_log_x = False
self.is_log_y = False
+ @property
+ def item_on_plot(self) -> bool:
+ """Returns if the item is on the plot"""
+ return self._item_on_plot
+
+ @item_on_plot.setter
+ def item_on_plot(self, value: bool) -> None:
+ self._item_on_plot = value
+
def add_to_plot(self) -> None:
"""Add the item to the plot"""
raise NotImplementedError("Method add_to_plot not implemented")
@@ -25,8 +38,11 @@ class BECIndicatorItem(QObject):
"""Remove the item from the plot"""
raise NotImplementedError("Method remove_from_plot not implemented")
- def set_position(self, pos: tuple[float, float] | float) -> None:
- """Set the position of the item"""
+ def set_position(self, pos) -> None:
+ """This method should implement the logic to set the position of the
+ item on the plot. Depending on the child class, the position can be
+ a tuple (x,y) or a single value, i.e. x position where y position is fixed.
+ """
raise NotImplementedError("Method set_position not implemented")
def check_log(self):
@@ -35,11 +51,6 @@ class BECIndicatorItem(QObject):
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
self.set_position(self._pos)
- def cleanup(self) -> None:
- """Cleanup the item"""
- self.remove_from_plot()
- self.deleteLater()
-
class BECTickItem(BECIndicatorItem):
"""Class to create a tick item which can be added to a pyqtgraph plot.
@@ -51,15 +62,17 @@ class BECTickItem(BECIndicatorItem):
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
super().__init__(plot_item=plot_item, parent=parent)
- self.tick_item = pg.TickSliderItem(allowAdd=False, allowRemove=False)
+ self.tick_item = pg.TickSliderItem(
+ parent=parent, allowAdd=False, allowRemove=False, orientation="bottom"
+ )
+ self.tick_item.skip_auto_range = True
self.tick = None
- # Set _pos to float
- self._pos = 0
+ self._pos = 0.0
self._range = [0, 1]
@Slot(float)
def set_position(self, pos: float) -> None:
- """Set the position of the tick item
+ """Set the x position of the tick item
Args:
pos (float): The position of the tick item.
@@ -74,36 +87,71 @@ class BECTickItem(BECIndicatorItem):
self.position_changed.emit(pos)
self.position_changed_str.emit(str(pos))
- def update_range(self, vb, viewRange) -> None:
+ @Slot()
+ def update_range(self, _, view_range: tuple[float, float]) -> None:
"""Update the range of the tick item
Args:
vb (pg.ViewBox): The view box.
viewRange (tuple): The view range.
"""
- origin = self.tick_item.tickSize / 2.0
- length = self.tick_item.length
+ if self._pos < view_range[0] or self._pos > view_range[1]:
+ self.tick_item.setVisible(False)
+ else:
+ self.tick_item.setVisible(True)
- lengthIncludingPadding = length + self.tick_item.tickSize + 2
+ if self.tick_item.isVisible():
+ origin = self.tick_item.tickSize / 2.0
+ length = self.tick_item.length
- self._range = viewRange
- tickValueIncludingPadding = (self._pos - viewRange[0]) / (viewRange[1] - viewRange[0])
- tickValue = (tickValueIncludingPadding * lengthIncludingPadding - origin) / length
- self.tick_item.setTickValue(self.tick, tickValue)
+ length_with_padding = length + self.tick_item.tickSize + 2
+
+ self._range = view_range
+ tick_with_padding = (self._pos - view_range[0]) / (view_range[1] - view_range[0])
+ tick_value = (tick_with_padding * length_with_padding - origin) / length
+ self.tick_item.setTickValue(self.tick, tick_value)
def add_to_plot(self):
"""Add the tick item to the view box or plot item."""
- if self.plot_item is not None:
- self.plot_item.layout.addItem(self.tick_item, 4, 1)
- self.tick = self.tick_item.addTick(0, movable=False, color=self.accent_colors.highlight)
- self.plot_item.vb.sigXRangeChanged.connect(self.update_range)
- self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
- self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
+ if self.plot_item is None:
+ return
+
+ self.plot_item.layout.addItem(self.tick_item, 2, 1)
+ self.tick_item.setOrientation("top")
+ self.tick = self.tick_item.addTick(0, movable=False, color=self.accent_colors.highlight)
+ self.update_tick_pos_y()
+ self.plot_item.vb.sigXRangeChanged.connect(self.update_range)
+ self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
+ self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
+ self.plot_item.vb.geometryChanged.connect(self.update_tick_pos_y)
+ self.item_on_plot = True
+
+ @Slot()
+ def update_tick_pos_y(self):
+ """Update tick position, while respecting the tick_item coordinates"""
+ pos = self.tick.pos()
+ pos = self.tick_item.mapToParent(pos)
+ new_pos = self.plot_item.vb.geometry().bottom()
+ new_pos = self.tick_item.mapFromParent(QPointF(pos.x(), new_pos))
+ self.tick.setPos(new_pos)
def remove_from_plot(self):
"""Remove the tick item from the view box or plot item."""
- if self.plot_item is not None:
- self.plot_item.layout.removeItem(self.tick_item)
+ if self.plot_item is not None and self.item_on_plot is True:
+ self.plot_item.vb.sigXRangeChanged.disconnect(self.update_range)
+ self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
+ self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
+ if self.plot_item.layout is not None:
+ self.plot_item.layout.removeItem(self.tick_item)
+ self.item_on_plot = False
+
+ def cleanup(self) -> None:
+ """Cleanup the item"""
+ self.remove_from_plot()
+ if self.tick_item is not None:
+ self.tick_item.close()
+ self.tick_item.deleteLater()
+ self.tick_item = None
class BECArrowItem(BECIndicatorItem):
@@ -126,8 +174,10 @@ class BECArrowItem(BECIndicatorItem):
def __init__(self, plot_item: pg.PlotItem = None, parent=None):
super().__init__(plot_item=plot_item, parent=parent)
- self.arrow_item = pg.ArrowItem()
+ self.arrow_item = pg.ArrowItem(parent=parent)
+ self.arrow_item.skip_auto_range = True
self._pos = (0, 0)
+ self.arrow_item.setVisible(False)
@Slot(dict)
def set_style(self, style: dict) -> None:
@@ -166,24 +216,38 @@ class BECArrowItem(BECIndicatorItem):
def add_to_plot(self):
"""Add the arrow item to the view box or plot item."""
+ if not self.arrow_item:
+ logger.warning(f"Arrow item was already destroyed, cannot be created")
+ return
+
self.arrow_item.setStyle(
angle=-90,
pen=pg.mkPen(self.accent_colors.emergency, width=1),
brush=pg.mkBrush(self.accent_colors.highlight),
headLen=20,
)
+ self.arrow_item.setVisible(True)
if self.plot_item is not None:
self.plot_item.addItem(self.arrow_item)
self.plot_item.ctrl.logXCheck.checkStateChanged.connect(self.check_log)
self.plot_item.ctrl.logYCheck.checkStateChanged.connect(self.check_log)
+ self.item_on_plot = True
def remove_from_plot(self):
"""Remove the arrow item from the view box or plot item."""
- if self.plot_item is not None:
+ if self.plot_item is not None and self.item_on_plot is True:
+ self.plot_item.ctrl.logXCheck.checkStateChanged.disconnect(self.check_log)
+ self.plot_item.ctrl.logYCheck.checkStateChanged.disconnect(self.check_log)
self.plot_item.removeItem(self.arrow_item)
+ self.item_on_plot = False
def check_log(self):
"""Checks if the x or y axis is in log scale and updates the internal state accordingly."""
self.is_log_x = self.plot_item.ctrl.logXCheck.isChecked()
self.is_log_y = self.plot_item.ctrl.logYCheck.isChecked()
self.set_position(self._pos)
+
+ def cleanup(self) -> None:
+ """Cleanup the item"""
+ self.remove_from_plot()
+ self.arrow_item = None
diff --git a/bec_widgets/widgets/figure/plots/plot_base.py b/bec_widgets/widgets/figure/plots/plot_base.py
index 7f2279b9..49aac682 100644
--- a/bec_widgets/widgets/figure/plots/plot_base.py
+++ b/bec_widgets/widgets/figure/plots/plot_base.py
@@ -106,7 +106,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
self.add_legend()
self.crosshair = None
- self.motor_pos_tick = BECTickItem(parent=self, plot_item=self.plot_item)
+ self.tick_item = BECTickItem(parent=self, plot_item=self.plot_item)
self.arrow_item = BECArrowItem(parent=self, plot_item=self.plot_item)
self._connect_to_theme_change()
@@ -431,7 +431,7 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
def cleanup_pyqtgraph(self):
"""Cleanup pyqtgraph items."""
self.unhook_crosshair()
- self.motor_pos_tick.cleanup()
+ self.tick_item.cleanup()
self.arrow_item.cleanup()
item = self.plot_item
item.vb.menu.close()
diff --git a/bec_widgets/widgets/figure/plots/waveform/waveform.py b/bec_widgets/widgets/figure/plots/waveform/waveform.py
index 6d84d846..0c9fd81e 100644
--- a/bec_widgets/widgets/figure/plots/waveform/waveform.py
+++ b/bec_widgets/widgets/figure/plots/waveform/waveform.py
@@ -1,6 +1,6 @@
+# pylint: disable=too_many_lines
from __future__ import annotations
-import time
from collections import defaultdict
from typing import Any, Literal, Optional
@@ -13,7 +13,7 @@ from bec_lib.logger import bec_logger
from pydantic import Field, ValidationError, field_validator
from pyqtgraph.exporters import MatplotlibExporter
from qtpy.QtCore import Signal as pyqtSignal
-from qtpy.QtWidgets import QApplication, QWidget
+from qtpy.QtWidgets import QWidget
from bec_widgets.qt_utils.error_popups import SafeSlot as Slot
from bec_widgets.utils import Colors, EntryValidator
@@ -42,11 +42,12 @@ class BECSignalProxy(pg.SignalProxy):
self.args = args
if self.blocking:
return
+ # self.blocking = True
super().signalReceived(*args)
- def flush(self):
- """If there is a signal queued send it out"""
- super().flush()
+ # def flush(self):
+ # """If there is a signal queued send it out"""
+ # super().flush()
@Slot()
def unblock_proxy(self):
@@ -190,7 +191,7 @@ class BECWaveform(BECPlotBase):
self.roi_select.linear_region_selector.setRegion(region)
except Exception as e:
logger.error(f"Error setting region {tuple}; Exception raised: {e}")
- raise ValueError(f"Error setting region {tuple}; Exception raised: {e}")
+ raise ValueError(f"Error setting region {tuple}; Exception raised: {e}") from e
def _hook_roi(self):
"""Hook the linear region selector to the plot."""
@@ -244,7 +245,7 @@ class BECWaveform(BECPlotBase):
# Reset curves
self._curves_data = defaultdict(dict)
self._curves = self.plot_item.curves
- for curve_id, curve_config in self.config.curves.items():
+ for curve_config in self.config.curves.values():
self.add_curve_by_config(curve_config)
if replot_last_scan:
self.scan_history(scan_index=-1)
@@ -367,7 +368,7 @@ class BECWaveform(BECPlotBase):
Returns:
CurveConfig|dict: Configuration of the curve.
"""
- for source, curves in self._curves_data.items():
+ for curves in self._curves_data.values():
if curve_id in curves:
if dict_output:
return curves[curve_id].config.model_dump()
@@ -387,7 +388,7 @@ class BECWaveform(BECPlotBase):
if isinstance(identifier, int):
return self.plot_item.curves[identifier]
elif isinstance(identifier, str):
- for source_type, curves in self._curves_data.items():
+ for curves in self._curves_data.values():
if identifier in curves:
return curves[identifier]
raise ValueError(f"Curve with ID '{identifier}' not found.")
@@ -538,6 +539,7 @@ class BECWaveform(BECPlotBase):
@Slot()
def auto_range(self):
+ """Manually set auto range of the plotitem"""
self.plot_item.autoRange()
def set_auto_range(self, enabled: bool, axis: str = "xy"):
@@ -576,7 +578,6 @@ class BECWaveform(BECPlotBase):
Returns:
BECCurve: The curve object.
"""
- curve_source = curve_source
curve_id = label or f"Curve {len(self.plot_item.curves) + 1}"
curve_exits = self._check_curve_id(curve_id, self._curves_data)
@@ -750,7 +751,7 @@ class BECWaveform(BECPlotBase):
if self.x_axis_mode["readout_priority"] == "async":
raise ValueError(
- f"Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
+ "Async signals cannot be fitted at the moment. Please switch to 'monitored' or 'baseline' signals."
)
if validate_bec is True:
@@ -1024,7 +1025,7 @@ class BECWaveform(BECPlotBase):
Args:
curve_id(str): ID of the curve to be removed.
"""
- for source, curves in self._curves_data.items():
+ for curves in self._curves_data.values():
if curve_id in curves:
curve = curves.pop(curve_id)
self.plot_item.removeItem(curve)
@@ -1047,7 +1048,7 @@ class BECWaveform(BECPlotBase):
self.plot_item.removeItem(curve)
del self.config.curves[curve_id]
# Remove from self.curve_data
- for source, curves in self._curves_data.items():
+ for curves in self._curves_data.values():
if curve_id in curves:
del curves[curve_id]
break
@@ -1077,7 +1078,7 @@ class BECWaveform(BECPlotBase):
if self._curves_data["DAP"]:
self.setup_dap(self.old_scan_id, self.scan_id)
if self._curves_data["async"]:
- for curve_id, curve in self._curves_data["async"].items():
+ for curve in self._curves_data["async"].values():
self.setup_async(
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
)
@@ -1212,7 +1213,8 @@ class BECWaveform(BECPlotBase):
"""Callback for DAP response message."""
if self.proxy_update_dap is not None:
self.proxy_update_dap.unblock_proxy()
- self.msg = msg
+
+ # pylint: disable=unused-variable
scan_id, x_name, x_entry, y_name, y_entry = msg["dap_request"].content["config"]["args"]
model = msg["dap_request"].content["config"]["class_kwargs"]["model"]
@@ -1241,7 +1243,7 @@ class BECWaveform(BECPlotBase):
metadata(dict): Metadata of the message.
"""
instruction = metadata.get("async_update")
- for curve_id, curve in self._curves_data["async"].items():
+ for curve in self._curves_data["async"].values():
y_name = curve.config.signals.y.name
y_entry = curve.config.signals.y.entry
x_name = self._x_axis_mode["name"]
@@ -1365,7 +1367,7 @@ class BECWaveform(BECPlotBase):
if self._x_axis_mode["name"] is None or self._x_axis_mode["name"] == "best_effort":
if len(self._curves_data["async"]) > 0:
x_data = None
- self._x_axis_mode["label_suffix"] = f" [auto: index]"
+ self._x_axis_mode["label_suffix"] = " [auto: index]"
current_label = "" if self.config.axis.x_label is None else self.config.axis.x_label
self.plot_item.setLabel(
"bottom", f"{current_label}{self._x_axis_mode['label_suffix']}"
@@ -1511,7 +1513,7 @@ class BECWaveform(BECPlotBase):
self.bec_dispatcher.disconnect_slot(
self.update_dap, MessageEndpoints.dap_response(self.scan_id)
)
- for curve_id, curve in self._curves_data["async"].items():
+ for curve_id in self._curves_data["async"]:
self.bec_dispatcher.disconnect_slot(
self.on_async_readback,
MessageEndpoints.device_async_readback(self.scan_id, curve_id),
diff --git a/bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py b/bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py
index dbb9f6cb..50ac87c8 100644
--- a/bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py
+++ b/bec_widgets/widgets/lmfit_dialog/lmfit_dialog.py
@@ -1,12 +1,12 @@
import os
-from bec_lib.endpoints import MessageEndpoints
from bec_lib.logger import bec_logger
from qtpy.QtCore import Property, Signal, Slot
from qtpy.QtWidgets import QPushButton, QTreeWidgetItem, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_widget import BECWidget
+from bec_widgets.utils.colors import get_accent_colors
logger = bec_logger.logger
@@ -17,8 +17,8 @@ class LMFitDialog(BECWidget, QWidget):
ICON_NAME = "monitoring"
# Signal to emit the currently selected fit curve_id
selected_fit = Signal(str)
- # Signal to emit a position to move to.
- move_to_position = Signal(float)
+ # Signal to emit a move action in form of a tuple (param_name, value)
+ move_action = Signal(tuple)
def __init__(
self,
@@ -53,56 +53,44 @@ class LMFitDialog(BECWidget, QWidget):
self._fit_curve_id = None
self._deci_precision = 3
self._always_show_latest = False
- self._activated_buttons_for_move_action = []
self.ui.curve_list.currentItemChanged.connect(self.display_fit_details)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
- self._enable_move_to_buttons = False
+ self._active_actions = []
+ self._enable_actions = True
self._move_buttons = []
+ self._accent_colors = get_accent_colors()
+ self.action_buttons = {}
- @Property(bool)
- def enable_move_to_buttons(self):
+ @property
+ def enable_actions(self) -> bool:
"""Property to enable the move to buttons."""
- return self._enable_move_to_buttons
+ return self._enable_actions
- @enable_move_to_buttons.setter
- def enable_move_to_buttons(self, enable: bool):
- self._enable_move_to_buttons = enable
- for button in self._move_buttons:
- if button.text().split(" ")[-1] in self.activated_buttons_for_move_action:
- button.setEnabled(enable)
+ @enable_actions.setter
+ def enable_actions(self, enable: bool):
+ self._enable_actions = enable
+ for button in self.action_buttons.values():
+ button.setEnabled(enable)
+ @Property(list)
+ def active_action_list(self) -> list[str]:
+ """Property to list the names of the fit parameters for which actions should be enabled."""
+ return self._active_actions
+
+ @active_action_list.setter
+ def active_action_list(self, actions: list[str]):
+ self._active_actions = actions
+
+ # This slot needed?
@Slot(bool)
- def set_enable_move_to_buttons(self, enable: bool):
+ def set_actions_enabled(self, enable: bool) -> bool:
"""Slot to enable the move to buttons.
Args:
- enable (bool): Whether to enable the move to buttons.
+ enable (bool): Whether to enable the action buttons.
"""
- self.enable_move_to_buttons = enable
-
- @Property(list)
- def activated_buttons_for_move_action(self) -> list:
- """Property for the buttons that should be activated for the move action in the parameter list."""
- return self._activated_buttons_for_move_action
-
- @activated_buttons_for_move_action.setter
- def activated_buttons_for_move_action(self, buttons: list):
- """Setter for the buttons that should be activated for the move action.
-
- Args:
- buttons (list): The buttons that should be activated for the move action.
- """
- self._activated_buttons_for_move_action = buttons
-
- @Slot(list)
- def update_activated_button_list(self, names: list) -> None:
- """Update the list of activated buttons for the move action.
-
- Args:
- names (list): List of button names to be activated.
- """
- self.activated_buttons_for_move_action = names
+ self.enable_actions = enable
@Property(bool)
def always_show_latest(self):
@@ -128,7 +116,7 @@ class LMFitDialog(BECWidget, QWidget):
self.ui.group_curve_selection.setVisible(not show)
@Property(bool)
- def hide_summary(self):
+ def hide_summary(self) -> bool:
"""Property for showing the summary."""
return not self.ui.group_summary.isVisible()
@@ -142,7 +130,7 @@ class LMFitDialog(BECWidget, QWidget):
self.ui.group_summary.setVisible(not show)
@Property(bool)
- def hide_parameters(self):
+ def hide_parameters(self) -> bool:
"""Property for showing the parameters."""
return not self.ui.group_parameters.isVisible()
@@ -156,7 +144,7 @@ class LMFitDialog(BECWidget, QWidget):
self.ui.group_parameters.setVisible(not show)
@property
- def fit_curve_id(self):
+ def fit_curve_id(self) -> str:
"""Property for the currently displayed fit curve_id."""
return self._fit_curve_id
@@ -266,28 +254,48 @@ class LMFitDialog(BECWidget, QWidget):
param_std = f"{param_std:.{self._deci_precision}f}"
else:
param_std = "None"
- # Create a push button to move the motor to a specific position
- # Per default, this feature is deactivated
- widget = QWidget()
- layout = QVBoxLayout(widget)
- push_button = QPushButton(f"Move to {param_name}")
- if param_name in self.activated_buttons_for_move_action:
- push_button.setEnabled(True)
- push_button.clicked.connect(
- lambda _, value=param[1]: self.move_to_position.emit(float(value))
- )
- else:
- push_button.setEnabled(False)
- self._move_buttons.append(push_button)
- layout.addWidget(push_button)
- layout.setContentsMargins(0, 0, 0, 0)
tree_item = QTreeWidgetItem(self.ui.param_tree, [param_name, param_value, param_std])
- self.ui.param_tree.setItemWidget(tree_item, 3, widget)
+ if param_name in self.active_action_list: # pylint: disable=unsupported-membership-test
+ # Create a push button to move the motor to a specific position
+ widget = QWidget()
+ button = QPushButton(f"Move to {param_name}")
+ button.clicked.connect(self._create_move_action(param_name, param[1]))
+ if self.enable_actions is True:
+ button.setEnabled(True)
+ else:
+ button.setEnabled(False)
+ button.setStyleSheet(
+ f"""
+ QPushButton:enabled {{ background-color: {self._accent_colors.success.name()};color: white; }}
+ QPushButton:disabled {{ background-color: grey;color: white; }}
+ """
+ )
+ self.action_buttons[param_name] = button
+ layout = QVBoxLayout()
+ layout.addWidget(self.action_buttons[param_name])
+ layout.setContentsMargins(0, 0, 0, 0)
+ widget.setLayout(layout)
+ self.ui.param_tree.setItemWidget(tree_item, 3, widget)
+
+ def _create_move_action(self, param_name: str, param_value: float) -> callable:
+ """Create a move action for the given parameter name and value.
+
+ Args:
+ param_name (str): The name of the parameter.
+ param_value (float): The value of the parameter.
+ Returns:
+ callable: The move action with the given parameter name and value.
+ """
+
+ def move_action():
+ self.move_action.emit((param_name, param_value))
+
+ return move_action
def populate_curve_list(self):
"""Populate the curve list with the available fit curves."""
- for curve_name in self.summary_data.keys():
+ for curve_name in self.summary_data:
self.ui.curve_list.addItem(curve_name)
def refresh_curve_list(self):
@@ -310,10 +318,10 @@ class LMFitDialog(BECWidget, QWidget):
self.update_summary_tree(data, {"curve_id": curve_name})
-if __name__ == "__main__":
+if __name__ == "__main__": # pragma: no cover
import sys
- from qtpy.QtWidgets import QApplication
+ from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
app = QApplication(sys.argv)
dialog = LMFitDialog()
diff --git a/bec_widgets/widgets/positioner_box/positioner_box.py b/bec_widgets/widgets/positioner_box/positioner_box.py
index c8f00c41..b60df661 100644
--- a/bec_widgets/widgets/positioner_box/positioner_box.py
+++ b/bec_widgets/widgets/positioner_box/positioner_box.py
@@ -321,7 +321,7 @@ class PositionerBox(BECWidget, QWidget):
if __name__ == "__main__": # pragma: no cover
import sys
- from qtpy.QtWidgets import QApplication
+ from qtpy.QtWidgets import QApplication # pylint: disable=ungrouped-imports
app = QApplication(sys.argv)
set_theme("dark")
diff --git a/bec_widgets/widgets/stop_button/stop_button.py b/bec_widgets/widgets/stop_button/stop_button.py
index 62769b64..dbddb226 100644
--- a/bec_widgets/widgets/stop_button/stop_button.py
+++ b/bec_widgets/widgets/stop_button/stop_button.py
@@ -1,6 +1,6 @@
from bec_qthemes import material_icon
from qtpy.QtCore import Qt
-from qtpy.QtWidgets import QHBoxLayout, QPushButton, QToolButton, QWidget
+from qtpy.QtWidgets import QHBoxLayout, QPushButton, QSizePolicy, QToolButton, QWidget
from bec_widgets.qt_utils.error_popups import SafeSlot
from bec_widgets.utils.bec_widget import BECWidget
@@ -28,9 +28,10 @@ class StopButton(BECWidget, QWidget):
self.button.setToolTip("Stop the scan queue")
else:
self.button = QPushButton()
+ self.button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.button.setText("Stop")
self.button.setStyleSheet(
- "background-color: #cc181e; color: white; font-weight: bold; font-size: 12px;"
+ f"background-color: #cc181e; color: white; font-weight: bold; font-size: 12px;"
)
self.button.clicked.connect(self.stop_scan)
@@ -47,3 +48,14 @@ class StopButton(BECWidget, QWidget):
scan_id(str|None): The scan id to stop. If None, the current scan will be stopped.
"""
self.queue.request_scan_halt()
+
+
+if __name__ == "__main__":
+ import sys
+
+ from qtpy.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ w = StopButton()
+ w.show()
+ sys.exit(app.exec_())
diff --git a/tests/unit_tests/test_lmfit_dialog.py b/tests/unit_tests/test_lmfit_dialog.py
index f016d353..558f62c6 100644
--- a/tests/unit_tests/test_lmfit_dialog.py
+++ b/tests/unit_tests/test_lmfit_dialog.py
@@ -168,6 +168,7 @@ def test_remove_dap_data(lmfit_dialog):
def test_update_summary_tree(lmfit_dialog, lmfit_message):
"""Test display_fit_details method"""
+ lmfit_dialog.active_action_list = ["center", "amplitude"]
lmfit_dialog.update_summary_tree(data=lmfit_message, metadata={"curve_id": "test_curve_id"})
# Check if the data is updated
assert lmfit_dialog.summary_data == {"test_curve_id": lmfit_message}
diff --git a/tests/unit_tests/test_utils_plot_indicators.py b/tests/unit_tests/test_utils_plot_indicators.py
index 5eb5e7d5..6dbb8d3c 100644
--- a/tests/unit_tests/test_utils_plot_indicators.py
+++ b/tests/unit_tests/test_utils_plot_indicators.py
@@ -1,5 +1,6 @@
import pyqtgraph as pg
import pytest
+from qtpy.QtCore import QPointF
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
@@ -21,7 +22,7 @@ def plot_widget_with_tick_item(qtbot, mocked_client):
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
- yield widget.waveform.motor_pos_tick, widget.waveform.plot_item
+ yield widget.waveform.tick_item, widget.waveform.plot_item
def test_arrow_item_add_to_plot(plot_widget_with_arrow_item):
@@ -33,15 +34,6 @@ def test_arrow_item_add_to_plot(plot_widget_with_arrow_item):
assert arrow_item.plot_item.items == [arrow_item.arrow_item]
-def test_arrow_item_remove_to_plot(plot_widget_with_arrow_item):
- """Test the remove_from_plot method"""
- arrow_item, plot_item = plot_widget_with_arrow_item
- arrow_item.add_to_plot()
- assert arrow_item.plot_item.items == [arrow_item.arrow_item]
- arrow_item.remove_from_plot()
- assert arrow_item.plot_item.items == []
-
-
def test_arrow_item_set_position(plot_widget_with_arrow_item):
"""Test the set_position method"""
arrow_item, plot_item = plot_widget_with_arrow_item
@@ -53,28 +45,37 @@ def test_arrow_item_set_position(plot_widget_with_arrow_item):
arrow_item.add_to_plot()
arrow_item.position_changed.connect(signal_callback)
arrow_item.set_position(pos=(1, 1))
- assert arrow_item.arrow_item.pos().toTuple() == (1, 1)
+ point = QPointF(1.0, 1.0)
+ assert arrow_item.arrow_item.pos() == point
arrow_item.set_position(pos=(2, 2))
- assert arrow_item.arrow_item.pos().toTuple() == (2, 2)
+ point = QPointF(2.0, 2.0)
+ assert arrow_item.arrow_item.pos() == point
assert container == [(1, 1), (2, 2)]
+def test_arrow_item_cleanup(plot_widget_with_arrow_item):
+ """Test cleanup procedure"""
+ arrow_item, plot_item = plot_widget_with_arrow_item
+ arrow_item.add_to_plot()
+ assert arrow_item.item_on_plot is True
+ arrow_item.cleanup()
+ assert arrow_item.plot_item.items == []
+ assert arrow_item.item_on_plot is False
+ assert arrow_item.arrow_item is None
+
+
def test_tick_item_add_to_plot(plot_widget_with_tick_item):
"""Test the add_to_plot method"""
tick_item, plot_item = plot_widget_with_tick_item
assert tick_item.plot_item is not None
assert tick_item.plot_item.items == []
tick_item.add_to_plot()
- assert tick_item.plot_item.layout.itemAt(4, 1) == tick_item.tick_item
-
-
-def test_tick_item_remove_to_plot(plot_widget_with_tick_item):
- """Test the remove_from_plot method"""
- tick_item, plot_item = plot_widget_with_tick_item
- tick_item.add_to_plot()
- assert tick_item.plot_item.layout.itemAt(4, 1) == tick_item.tick_item
- tick_item.remove_from_plot()
- assert tick_item.plot_item.layout.itemAt(4, 1) is None
+ assert tick_item.plot_item.layout.itemAt(2, 1) == tick_item.tick_item
+ assert tick_item.item_on_plot is True
+ new_pos = plot_item.vb.geometry().bottom()
+ pos = tick_item.tick.pos()
+ new_pos = tick_item.tick_item.mapFromParent(QPointF(pos.x(), new_pos))
+ assert new_pos.y() == pos.y()
def test_tick_item_set_position(plot_widget_with_tick_item):
@@ -93,3 +94,15 @@ def test_tick_item_set_position(plot_widget_with_tick_item):
tick_item.set_position(pos=2)
assert tick_item._pos == 2
assert container == [1.0, 2.0]
+
+
+def test_tick_item_cleanup(plot_widget_with_tick_item):
+ """Test cleanup procedure"""
+ tick_item, plot_item = plot_widget_with_tick_item
+ tick_item.add_to_plot()
+ assert tick_item.item_on_plot is True
+ tick_item.cleanup()
+ ticks = getattr(tick_item.plot_item.layout.itemAt(3, 1), "ticks", None)
+ assert ticks == None
+ assert tick_item.item_on_plot is False
+ assert tick_item.tick_item is None