mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-16 13:38:51 +02:00
Compare commits
7 Commits
feature/me
...
v3.3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d46ffb59f0 | ||
| da400d20b6 | |||
|
|
20f06d8659 | ||
| 3d29a67c0b | |||
|
|
e7ef8a3891 | ||
| 90222f3082 | |||
| 79af15a88b |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,33 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v3.3.3 (2026-03-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **positioner_box**: Remove CompactPopupWidget inheritance
|
||||
([`da400d2`](https://github.com/bec-project/bec_widgets/commit/da400d20b672236241ce3a4480481ac6a5df1b2e))
|
||||
|
||||
|
||||
## v3.3.2 (2026-03-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Typos
|
||||
([`3d29a67`](https://github.com/bec-project/bec_widgets/commit/3d29a67c0b2175f2f29b8e5a7befce55f3d28fd3))
|
||||
|
||||
|
||||
## v3.3.1 (2026-03-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **dap_combobox**: Added safeguard for no DAP models
|
||||
([`79af15a`](https://github.com/bec-project/bec_widgets/commit/79af15a88b993cd5b6bf730796f995f20cf6f188))
|
||||
|
||||
- **dap_combobox**: Rewritten as proper combobox
|
||||
([`90222f3`](https://github.com/bec-project/bec_widgets/commit/90222f30821f822eb24b0179401d4e43050e0156))
|
||||
|
||||
|
||||
## v3.3.0 (2026-03-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -263,7 +263,7 @@ class BECMainApp(BECMainWindow):
|
||||
developer_view_step = self.guided_tour.register_widget(
|
||||
widget=sidebar_developer_view,
|
||||
title="Developer View",
|
||||
text="Click here to access the Developer view to write scripts and makros.",
|
||||
text="Click here to access the Developer view to write scripts and macros.",
|
||||
)
|
||||
tour_steps.append(developer_view_step)
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ class DeviceManagerDisplayWidget(DockAreaWidget):
|
||||
self._upload_redis_dialog: UploadRedisDialog | None = None
|
||||
self._dialog_validation_connection: QMetaObject.Connection | None = None
|
||||
|
||||
# NOTE: We need here a seperate config helper instance to avoid conflicts with
|
||||
# NOTE: We need here a separate config helper instance to avoid conflicts with
|
||||
# other communications to REDIS as uploading a config through a CommunicationConfigAction
|
||||
# will block if we use the config_helper from self.client.config._config_helper
|
||||
self._config_helper = config_helper.ConfigHelper(self.client.connector)
|
||||
@@ -607,8 +607,8 @@ class DeviceManagerDisplayWidget(DockAreaWidget):
|
||||
self.device_table_view._is_config_in_sync_with_redis()
|
||||
)
|
||||
validation_results = self.device_table_view.get_validation_results()
|
||||
for config, config_status, connnection_status in validation_results.values():
|
||||
if connnection_status == ConnectionStatus.CONNECTED.value:
|
||||
for config, config_status, connection_status in validation_results.values():
|
||||
if connection_status == ConnectionStatus.CONNECTED.value:
|
||||
self.device_table_view.update_device_validation(
|
||||
config, config_status, ConnectionStatus.CAN_CONNECT, ""
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ logger = bec_logger.logger
|
||||
|
||||
|
||||
class _WidgetsEnumType(str, enum.Enum):
|
||||
"""Enum for the available widgets, to be generated programatically"""
|
||||
"""Enum for the available widgets, to be generated programmatically"""
|
||||
|
||||
...
|
||||
|
||||
@@ -985,7 +985,7 @@ class Curve(RPCBase):
|
||||
|
||||
|
||||
class DapComboBox(RPCBase):
|
||||
"""The DAPComboBox widget is an extension to the QComboBox with all avaialble DAP model from BEC."""
|
||||
"""Editable combobox listing the available DAP models."""
|
||||
|
||||
@rpc_call
|
||||
def select_y_axis(self, y_axis: str):
|
||||
@@ -1011,7 +1011,7 @@ class DapComboBox(RPCBase):
|
||||
Slot to update the fit model.
|
||||
|
||||
Args:
|
||||
default_device(str): Default device name.
|
||||
fit_name(str): Fit model name.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ logger = bec_logger.logger
|
||||
if self._base:
|
||||
self.content += """
|
||||
class _WidgetsEnumType(str, enum.Enum):
|
||||
\"\"\" Enum for the available widgets, to be generated programatically \"\"\"
|
||||
\"\"\" Enum for the available widgets, to be generated programmatically \"\"\"
|
||||
...
|
||||
"""
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ class BECConnector:
|
||||
)
|
||||
self.config = ConnectionConfig(widget_class=self.__class__.__name__)
|
||||
|
||||
# If the gui_id is passed, it should be respected. However, this should be revisted since
|
||||
# If the gui_id is passed, it should be respected. However, this should be revisited since
|
||||
# the gui_id has to be unique, and may no longer be.
|
||||
if gui_id:
|
||||
self.config.gui_id = gui_id
|
||||
@@ -399,7 +399,7 @@ class BECConnector:
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
# FIXME some thoughts are required to decide how thhis should work with rpc registry
|
||||
# FIXME some thoughts are required to decide how this should work with rpc registry
|
||||
def apply_config(self, config: dict, generate_new_id: bool = True) -> None:
|
||||
"""
|
||||
Apply the configuration to the widget.
|
||||
@@ -417,7 +417,7 @@ class BECConnector:
|
||||
else:
|
||||
self.gui_id = self.config.gui_id
|
||||
|
||||
# FIXME some thoughts are required to decide how thhis should work with rpc registry
|
||||
# FIXME some thoughts are required to decide how this should work with rpc registry
|
||||
def load_config(self, path: str | None = None, gui: bool = False):
|
||||
"""
|
||||
Load the configuration of the widget from YAML.
|
||||
|
||||
@@ -43,7 +43,7 @@ class WidgetContainerUtils:
|
||||
if list_of_names is None:
|
||||
list_of_names = []
|
||||
ii = 0
|
||||
while ii < 1000: # 1000 is arbritrary!
|
||||
while ii < 1000: # 1000 is arbitrary!
|
||||
name_candidate = f"{name}_{ii}"
|
||||
if name_candidate not in list_of_names:
|
||||
return name_candidate
|
||||
|
||||
@@ -71,7 +71,7 @@ class FormItemSpec(BaseModel):
|
||||
"""
|
||||
The specification for an item in a dynamically generated form. Uses a pydantic FieldInfo
|
||||
to store most annotation info, since one of the main purposes is to store data for
|
||||
forms genrated from pydantic models, but can also be composed from other sources or by hand.
|
||||
forms generated from pydantic models, but can also be composed from other sources or by hand.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
@@ -192,7 +192,7 @@ class DynamicFormItem(QWidget):
|
||||
@abstractmethod
|
||||
def _add_main_widget(self) -> None:
|
||||
self._main_widget: QWidget
|
||||
"""Add the main data entry widget to self._main_widget and appply any
|
||||
"""Add the main data entry widget to self._main_widget and apply any
|
||||
constraints from the field info"""
|
||||
|
||||
@SafeSlot()
|
||||
|
||||
@@ -15,7 +15,7 @@ class Kind(IFBase):
|
||||
"""
|
||||
This is used in the .kind attribute of all OphydObj (Signals, Devices).
|
||||
|
||||
A Device examines its components' .kind atttribute to decide whether to
|
||||
A Device examines its components' .kind attribute to decide whether to
|
||||
traverse it in read(), read_configuration(), or neither. Additionally, if
|
||||
decides whether to include its name in `hints['fields']`.
|
||||
"""
|
||||
|
||||
@@ -156,7 +156,7 @@ class RPCServer:
|
||||
if method == "raise" and hasattr(
|
||||
obj, "setWindowState"
|
||||
): # special case for raising windows, should work even if minimized
|
||||
# this is a special case for raising windows for gnome on rethat 9 systems where changing focus is supressed by default
|
||||
# this is a special case for raising windows for gnome on Red Hat (RHEL) 9 systems where changing focus is suppressed by default
|
||||
# The procedure is as follows:
|
||||
# 1. Get the current window state to check if the window is minimized and remove minimized flag
|
||||
# 2. Then in order to force gnome to raise the window, we set the window to stay on top temporarily
|
||||
@@ -442,5 +442,5 @@ class RPCServer:
|
||||
self.status = messages.BECStatus.IDLE
|
||||
self._heartbeat_timer.stop()
|
||||
self.emit_heartbeat()
|
||||
logger.info("Succeded in shutting down CLI server")
|
||||
logger.info("Succeeded in shutting down CLI server")
|
||||
self.client.shutdown()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from .positioner_box_base import PositionerBoxBase
|
||||
|
||||
__ALL__ = ["PositionerBoxBase"]
|
||||
@@ -14,9 +14,9 @@ from qtpy.QtWidgets import QDoubleSpinBox
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.colors import apply_theme, get_accent_colors
|
||||
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
||||
from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase
|
||||
from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import (
|
||||
from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import (
|
||||
DeviceUpdateUIComponents,
|
||||
PositionerBoxBase,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
@@ -63,10 +63,10 @@ class PositionerBox(PositionerBoxBase):
|
||||
|
||||
self.ui = UILoader(self).loader(os.path.join(self.current_path, self.ui_file))
|
||||
|
||||
self.addWidget(self.ui)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter)
|
||||
self.main_layout.addWidget(self.ui)
|
||||
self.main_layout.setSpacing(0)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter)
|
||||
ui_min_size = self.ui.minimumSize()
|
||||
ui_min_hint = self.ui.minimumSizeHint()
|
||||
self.setMinimumSize(
|
||||
@@ -115,8 +115,6 @@ class PositionerBox(PositionerBoxBase):
|
||||
return
|
||||
old_device = self._device
|
||||
self._device = value
|
||||
if not self.label:
|
||||
self.label = value
|
||||
self.device_changed.emit(old_device, value)
|
||||
|
||||
@SafeProperty(bool)
|
||||
|
||||
@@ -15,9 +15,9 @@ from qtpy.QtWidgets import QDoubleSpinBox
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
|
||||
from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase
|
||||
from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import (
|
||||
from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import (
|
||||
DeviceUpdateUIComponents,
|
||||
PositionerBoxBase,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
@@ -96,9 +96,9 @@ class PositionerBox2D(PositionerBoxBase):
|
||||
|
||||
def connect_ui(self):
|
||||
"""Connect the UI components to signals, data, or routines"""
|
||||
self.addWidget(self.ui)
|
||||
self.layout.setSpacing(0)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.addWidget(self.ui)
|
||||
self.main_layout.setSpacing(0)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
def _init_ui(val: QDoubleValidator, device_id: DeviceId):
|
||||
ui = self._device_ui_components_hv(device_id)
|
||||
@@ -200,7 +200,6 @@ class PositionerBox2D(PositionerBoxBase):
|
||||
return
|
||||
old_device = self._device_hor
|
||||
self._device_hor = value
|
||||
self.label = f"{self._device_hor}, {self._device_ver}"
|
||||
self.device_changed_hor.emit(old_device, value)
|
||||
self._init_device(self.device_hor, self.position_update_hor.emit, self.update_limits_hor)
|
||||
|
||||
@@ -220,7 +219,6 @@ class PositionerBox2D(PositionerBoxBase):
|
||||
return
|
||||
old_device = self._device_ver
|
||||
self._device_ver = value
|
||||
self.label = f"{self._device_hor}, {self._device_ver}"
|
||||
self.device_changed_ver.emit(old_device, value)
|
||||
self._init_device(self.device_ver, self.position_update_ver.emit, self.update_limits_ver)
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ from qtpy.QtWidgets import (
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.compact_popup import CompactPopupWidget
|
||||
from bec_widgets.widgets.control.device_control.position_indicator.position_indicator import (
|
||||
PositionIndicator,
|
||||
)
|
||||
@@ -43,7 +43,7 @@ class DeviceUpdateUIComponents(TypedDict):
|
||||
units: QLabel
|
||||
|
||||
|
||||
class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
||||
class PositionerBoxBase(BECWidget, QWidget):
|
||||
"""Contains some core logic for positioner box widgets"""
|
||||
|
||||
current_path = ""
|
||||
@@ -57,7 +57,10 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
||||
parent: The parent widget.
|
||||
device (Positioner): The device to control.
|
||||
"""
|
||||
super().__init__(parent=parent, layout=QVBoxLayout, **kwargs)
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.setSpacing(0)
|
||||
self._dialog = None
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
@@ -173,11 +176,9 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
||||
if is_moving:
|
||||
spinner.start()
|
||||
spinner.setToolTip("Device is moving")
|
||||
self.set_global_state("warning")
|
||||
else:
|
||||
spinner.stop()
|
||||
spinner.setToolTip("Device is idle")
|
||||
self.set_global_state("success")
|
||||
else:
|
||||
spinner.setVisible(False)
|
||||
|
||||
@@ -196,9 +197,8 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
||||
pos = (readback_val - limits[0]) / (limits[1] - limits[0])
|
||||
position_indicator.set_value(pos)
|
||||
|
||||
def _update_limits_ui(
|
||||
self, limits: tuple[float, float], position_indicator, setpoint_validator
|
||||
):
|
||||
@staticmethod
|
||||
def _update_limits_ui(limits: tuple[float, float], position_indicator, setpoint_validator):
|
||||
if limits is not None and limits[0] != limits[1]:
|
||||
position_indicator.setToolTip(f"Min: {limits[0]}, Max: {limits[1]}")
|
||||
setpoint_validator.setRange(limits[0], limits[1])
|
||||
@@ -223,8 +223,9 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget):
|
||||
self.bec_dispatcher.disconnect_slot(slot, MessageEndpoints.device_readback(old_device))
|
||||
self.bec_dispatcher.connect_slot(slot, MessageEndpoints.device_readback(new_device))
|
||||
|
||||
def _toggle_enable_buttons(self, ui: DeviceUpdateUIComponents, enable: bool) -> None:
|
||||
"""Toogle enable/disable on available buttons
|
||||
@staticmethod
|
||||
def _toggle_enable_buttons(ui: DeviceUpdateUIComponents, enable: bool) -> None:
|
||||
"""Toggle enable/disable on available buttons
|
||||
|
||||
Args:
|
||||
enable (bool): Enable buttons
|
||||
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
|
||||
from bec_lib.device import Positioner
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import QSizePolicy
|
||||
|
||||
from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox
|
||||
|
||||
@@ -22,7 +24,82 @@ class PositionerControlLine(PositionerBox):
|
||||
device (Positioner): The device to control.
|
||||
"""
|
||||
self.current_path = os.path.dirname(__file__)
|
||||
self._indicator_switch_width = 0
|
||||
self._horizontal_indicator_width = 0
|
||||
self._vertical_indicator_width = 15
|
||||
self._indicator_thickness = 10
|
||||
self._indicator_is_horizontal = False
|
||||
self._line_height = self.dimensions[0]
|
||||
super().__init__(parent=parent, device=device, *args, **kwargs)
|
||||
self._configure_line_layout()
|
||||
self._update_indicator_orientation()
|
||||
|
||||
def _configure_line_layout(self):
|
||||
device_box = self.ui.device_box
|
||||
indicator = self.ui.position_indicator
|
||||
|
||||
self.main_layout.setAlignment(Qt.AlignmentFlag(0))
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.ui.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
device_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
|
||||
self._line_height = max(
|
||||
self.dimensions[0],
|
||||
self.ui.minimumSizeHint().height(),
|
||||
self.ui.sizeHint().height(),
|
||||
device_box.minimumSizeHint().height(),
|
||||
device_box.sizeHint().height(),
|
||||
)
|
||||
device_box.setFixedHeight(self._line_height)
|
||||
device_box.setMinimumWidth(self.dimensions[1])
|
||||
device_box.setMaximumWidth(16777215)
|
||||
self.setFixedHeight(self._line_height)
|
||||
self.setMinimumWidth(self.dimensions[1])
|
||||
|
||||
self.ui.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.ui.verticalLayout.setSpacing(0)
|
||||
self.ui.readback.setMaximumWidth(16777215)
|
||||
self.ui.setpoint.setMaximumWidth(16777215)
|
||||
self.ui.step_size.setMaximumWidth(16777215)
|
||||
|
||||
indicator_hint = indicator.minimumSizeHint()
|
||||
step_hint = self.ui.step_size.sizeHint()
|
||||
self._indicator_thickness = max(indicator_hint.height(), 10)
|
||||
self._vertical_indicator_width = max(indicator.minimumWidth(), 15)
|
||||
self._horizontal_indicator_width = max(90, step_hint.width())
|
||||
base_width = max(device_box.minimumSizeHint().width(), self.dimensions[1])
|
||||
self._indicator_switch_width = (
|
||||
base_width - self._vertical_indicator_width + self._horizontal_indicator_width
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self._update_indicator_orientation()
|
||||
|
||||
def _update_indicator_orientation(self):
|
||||
if not hasattr(self, "ui"):
|
||||
return
|
||||
|
||||
indicator = self.ui.position_indicator
|
||||
available_width = self.ui.device_box.width() or self.width() or self.dimensions[1]
|
||||
should_use_horizontal = available_width >= self._indicator_switch_width
|
||||
if should_use_horizontal == self._indicator_is_horizontal:
|
||||
return
|
||||
|
||||
self._indicator_is_horizontal = should_use_horizontal
|
||||
indicator.vertical = not should_use_horizontal
|
||||
|
||||
if should_use_horizontal:
|
||||
indicator.setMinimumSize(self._horizontal_indicator_width, self._indicator_thickness)
|
||||
indicator.setMaximumHeight(self._indicator_thickness)
|
||||
indicator.setMaximumWidth(16777215)
|
||||
indicator.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed)
|
||||
else:
|
||||
indicator.setMinimumSize(self._vertical_indicator_width, self._indicator_thickness)
|
||||
indicator.setMaximumSize(self._vertical_indicator_width, 16777215)
|
||||
indicator.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||
|
||||
indicator.updateGeometry()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>612</width>
|
||||
<height>91</height>
|
||||
<width>592</width>
|
||||
<height>76</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@@ -26,8 +32,29 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="device_box">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Device Name</string>
|
||||
</property>
|
||||
@@ -227,12 +254,12 @@
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PositionIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<extends></extends>
|
||||
<header>position_indicator</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SpinnerWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<extends></extends>
|
||||
<header>spinner_widget</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
|
||||
@@ -27,30 +27,13 @@ class PositionerGroupBox(QGroupBox):
|
||||
self.layout().setContentsMargins(0, 0, 0, 0)
|
||||
self.layout().setSpacing(0)
|
||||
self.widget = PositionerBox(self, dev_name)
|
||||
self.widget.compact_view = True
|
||||
self.widget.expand_popup = False
|
||||
self.layout().addWidget(self.widget)
|
||||
self.widget.position_update.connect(self._on_position_update)
|
||||
self.widget.expand.connect(self._on_expand)
|
||||
self.setTitle(self.device_name)
|
||||
self.widget.force_update_readback()
|
||||
|
||||
def _on_expand(self, expand):
|
||||
if expand:
|
||||
self.setTitle("")
|
||||
self.setFlat(True)
|
||||
else:
|
||||
self.setTitle(self.device_name)
|
||||
self.setFlat(False)
|
||||
|
||||
def _on_position_update(self, pos: float):
|
||||
self.position_update.emit(pos)
|
||||
precision = getattr(self.widget.dev[self.widget.device], "precision", 8)
|
||||
try:
|
||||
precision = int(precision)
|
||||
except (TypeError, ValueError):
|
||||
precision = int(8)
|
||||
self.widget.label = f"{pos:.{precision}f}"
|
||||
|
||||
def close(self):
|
||||
self.widget.close()
|
||||
|
||||
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
||||
from .available_device_group import AvailableDeviceGroup
|
||||
|
||||
|
||||
class _DeviceListWiget(QListWidget):
|
||||
class _DeviceListWidget(QListWidget):
|
||||
|
||||
def _item_iter(self):
|
||||
return (self.item(i) for i in range(self.count()))
|
||||
@@ -44,7 +44,7 @@ class Ui_AvailableDeviceGroup(object):
|
||||
self.n_included.setObjectName("n_included")
|
||||
title_layout.addWidget(self.n_included)
|
||||
|
||||
self.device_list = _DeviceListWiget(AvailableDeviceGroup)
|
||||
self.device_list = _DeviceListWidget(AvailableDeviceGroup)
|
||||
self.device_list.setSelectionMode(QListWidget.SelectionMode.ExtendedSelection)
|
||||
self.device_list.setObjectName("device_list")
|
||||
self.device_list.setFrameStyle(0)
|
||||
|
||||
@@ -34,13 +34,13 @@ class HashModel(str, Enum):
|
||||
class DeviceResourceBackend(Protocol):
|
||||
@property
|
||||
def tag_groups(self) -> dict[str, set[HashableDevice]]:
|
||||
"""A dictionary of all availble devices separated by tag groups. The same device may
|
||||
"""A dictionary of all available devices separated by tag groups. The same device may
|
||||
appear more than once (in different groups)."""
|
||||
...
|
||||
|
||||
@property
|
||||
def all_devices(self) -> set[HashableDevice]:
|
||||
"""A set of all availble devices. The same device may not appear more than once."""
|
||||
"""A set of all available devices. The same device may not appear more than once."""
|
||||
...
|
||||
|
||||
@property
|
||||
|
||||
@@ -347,14 +347,14 @@ class ScanGroupBox(QGroupBox):
|
||||
|
||||
def get_parameters(self, device_object: bool = True):
|
||||
"""
|
||||
Returns the parameters from the widgets in the scan control layout formated to run scan from BEC.
|
||||
Returns the parameters from the widgets in the scan control layout formatted to run scan from BEC.
|
||||
"""
|
||||
if self.box_type == "args":
|
||||
return self._get_arg_parameterts(device_object=device_object)
|
||||
return self._get_arg_parameters(device_object=device_object)
|
||||
elif self.box_type == "kwargs":
|
||||
return self._get_kwarg_parameters(device_object=device_object)
|
||||
|
||||
def _get_arg_parameterts(self, device_object: bool = True):
|
||||
def _get_arg_parameters(self, device_object: bool = True):
|
||||
args = []
|
||||
for i in range(1, self.layout.rowCount()):
|
||||
for j in range(self.layout.columnCount()):
|
||||
|
||||
@@ -2,22 +2,19 @@
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Property, Signal, Slot
|
||||
from qtpy.QtWidgets import QComboBox, QVBoxLayout, QWidget
|
||||
from qtpy.QtWidgets import QComboBox
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DapComboBox(BECWidget, QWidget):
|
||||
class DapComboBox(BECWidget, QComboBox):
|
||||
"""
|
||||
The DAPComboBox widget is an extension to the QComboBox with all avaialble DAP model from BEC.
|
||||
Editable combobox listing the available DAP models.
|
||||
|
||||
Args:
|
||||
parent: Parent widget.
|
||||
client: BEC client object.
|
||||
gui_id: GUI ID.
|
||||
default: Default device name.
|
||||
The widget behaves as a plain QComboBox and keeps ``fit_model_combobox`` as an alias to itself
|
||||
for backwards compatibility with older call sites.
|
||||
"""
|
||||
|
||||
ICON_NAME = "data_exploration"
|
||||
@@ -45,19 +42,20 @@ class DapComboBox(BECWidget, QWidget):
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(parent=parent, client=client, gui_id=gui_id, **kwargs)
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.fit_model_combobox = QComboBox(self)
|
||||
self.layout.addWidget(self.fit_model_combobox)
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._available_models = None
|
||||
self.fit_model_combobox = self # Just for backwards compatibility with older call sites, the widget itself is the combobox
|
||||
self._available_models: list[str] = []
|
||||
self._x_axis = None
|
||||
self._y_axis = None
|
||||
self.populate_fit_model_combobox()
|
||||
self.fit_model_combobox.currentTextChanged.connect(self._update_current_fit)
|
||||
# Set default fit model
|
||||
self.select_default_fit(default_fit)
|
||||
self._is_valid_input = False
|
||||
|
||||
def select_default_fit(self, default_fit: str | None):
|
||||
self.setEditable(True)
|
||||
|
||||
self.populate_fit_model_combobox()
|
||||
self.currentTextChanged.connect(self._on_text_changed)
|
||||
self.select_default_fit(default_fit)
|
||||
self.check_validity(self.currentText())
|
||||
|
||||
def select_default_fit(self, default_fit: str | None = "GaussianModel"):
|
||||
"""Set the default fit model.
|
||||
|
||||
Args:
|
||||
@@ -65,8 +63,8 @@ class DapComboBox(BECWidget, QWidget):
|
||||
"""
|
||||
if self._validate_dap_model(default_fit):
|
||||
self.select_fit_model(default_fit)
|
||||
else:
|
||||
self.select_fit_model("GaussianModel")
|
||||
elif self.available_models:
|
||||
self.select_fit_model(self.available_models[0])
|
||||
|
||||
@property
|
||||
def available_models(self):
|
||||
@@ -114,12 +112,40 @@ class DapComboBox(BECWidget, QWidget):
|
||||
self._y_axis = y_axis
|
||||
self.y_axis_updated.emit(y_axis)
|
||||
|
||||
def _update_current_fit(self, fit_name: str):
|
||||
"""Update the current fit."""
|
||||
@Slot(str)
|
||||
def _on_text_changed(self, fit_name: str):
|
||||
"""
|
||||
Validate and emit updates for the current text.
|
||||
|
||||
Args:
|
||||
fit_name(str): The current text in the combobox, representing the selected fit model.
|
||||
"""
|
||||
self.check_validity(fit_name)
|
||||
if not self._is_valid_input:
|
||||
return
|
||||
|
||||
self.fit_model_updated.emit(fit_name)
|
||||
if self.x_axis is not None and self.y_axis is not None:
|
||||
self.new_dap_config.emit(self._x_axis, self._y_axis, fit_name)
|
||||
|
||||
@Slot(str)
|
||||
def check_validity(self, fit_name: str):
|
||||
"""
|
||||
Highlight invalid manual entries similarly to DeviceComboBox.
|
||||
|
||||
Args:
|
||||
fit_name(str): The current text in the combobox, representing the selected fit model.
|
||||
"""
|
||||
if self._validate_dap_model(fit_name):
|
||||
self._is_valid_input = True
|
||||
self.setStyleSheet("border: 1px solid transparent;")
|
||||
else:
|
||||
self._is_valid_input = False
|
||||
if self.isEnabled():
|
||||
self.setStyleSheet("border: 1px solid red;")
|
||||
else:
|
||||
self.setStyleSheet("border: 1px solid transparent;")
|
||||
|
||||
@Slot(str)
|
||||
def select_x_axis(self, x_axis: str):
|
||||
"""Slot to update the x axis.
|
||||
@@ -128,7 +154,7 @@ class DapComboBox(BECWidget, QWidget):
|
||||
x_axis(str): X axis.
|
||||
"""
|
||||
self.x_axis = x_axis
|
||||
self._update_current_fit(self.fit_model_combobox.currentText())
|
||||
self._on_text_changed(self.currentText())
|
||||
|
||||
@Slot(str)
|
||||
def select_y_axis(self, y_axis: str):
|
||||
@@ -138,25 +164,26 @@ class DapComboBox(BECWidget, QWidget):
|
||||
y_axis(str): Y axis.
|
||||
"""
|
||||
self.y_axis = y_axis
|
||||
self._update_current_fit(self.fit_model_combobox.currentText())
|
||||
self._on_text_changed(self.currentText())
|
||||
|
||||
@Slot(str)
|
||||
def select_fit_model(self, fit_name: str | None):
|
||||
"""Slot to update the fit model.
|
||||
|
||||
Args:
|
||||
default_device(str): Default device name.
|
||||
fit_name(str): Fit model name.
|
||||
"""
|
||||
if not self._validate_dap_model(fit_name):
|
||||
raise ValueError(f"Fit {fit_name} is not valid.")
|
||||
self.fit_model_combobox.setCurrentText(fit_name)
|
||||
self.setCurrentText(fit_name)
|
||||
|
||||
def populate_fit_model_combobox(self):
|
||||
"""Populate the fit_model_combobox with the devices."""
|
||||
# pylint: disable=protected-access
|
||||
self.available_models = [model for model in self.client.dap._available_dap_plugins.keys()]
|
||||
self.fit_model_combobox.clear()
|
||||
self.fit_model_combobox.addItems(self.available_models)
|
||||
available_plugins = getattr(getattr(self.client, "dap", None), "_available_dap_plugins", {})
|
||||
self.available_models = [model for model in available_plugins.keys()]
|
||||
self.clear()
|
||||
self.addItems(self.available_models)
|
||||
|
||||
def _validate_dap_model(self, model: str | None) -> bool:
|
||||
"""Validate the DAP model.
|
||||
@@ -166,23 +193,23 @@ class DapComboBox(BECWidget, QWidget):
|
||||
"""
|
||||
if model is None:
|
||||
return False
|
||||
if model not in self.available_models:
|
||||
return False
|
||||
return True
|
||||
return model in self.available_models
|
||||
|
||||
@property
|
||||
def is_valid_input(self) -> bool:
|
||||
"""Whether the current text matches an available DAP model."""
|
||||
return self._is_valid_input
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
|
||||
app = QApplication([])
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("dark")
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(200, 200)
|
||||
layout = QVBoxLayout()
|
||||
widget.setLayout(layout)
|
||||
layout.addWidget(DapComboBox())
|
||||
widget.show()
|
||||
app.exec_()
|
||||
dialog = DapComboBox()
|
||||
dialog.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -1778,7 +1778,7 @@ class Waveform(PlotBase):
|
||||
if parent_curve is None:
|
||||
logger.warning(
|
||||
f"No device curve found for DAP curve '{dap_curve.name()}'!"
|
||||
) # TODO triggerd when DAP curve is removed from the curve dialog, why?
|
||||
) # TODO triggered when DAP curve is removed from the curve dialog, why?
|
||||
continue
|
||||
|
||||
x_data, y_data = parent_curve.get_data()
|
||||
|
||||
@@ -276,7 +276,7 @@ class Ring(BECConnector, QWidget):
|
||||
for obj in dev_obj._info["signals"].values()
|
||||
if obj["kind_str"] == "hinted"
|
||||
and obj["signal_class"]
|
||||
not in ["ProgressSignal", "AyncSignal", "AsyncMultiSignal", "DynamicSignal"]
|
||||
not in ["ProgressSignal", "AsyncSignal", "AsyncMultiSignal", "DynamicSignal"]
|
||||
]
|
||||
|
||||
normal_signals = [
|
||||
|
||||
@@ -88,7 +88,7 @@ class DeviceBrowser(BECWidget, QWidget):
|
||||
self.setLayout(layout)
|
||||
|
||||
def init_warning_label(self):
|
||||
self.ui.scan_running_warning.setText("Warning: editing diabled while scan is running!")
|
||||
self.ui.scan_running_warning.setText("Warning: editing disabled while scan is running!")
|
||||
self.ui.scan_running_warning.setStyleSheet(
|
||||
"background-color: #fcba03; color: rgb(0, 0, 0);"
|
||||
)
|
||||
|
||||
@@ -160,8 +160,8 @@ class ScanHistoryMetadataViewer(BECWidget, QtWidgets.QGroupBox):
|
||||
Clear the view by resetting the labels and values.
|
||||
"""
|
||||
layout = self.layout()
|
||||
lauout_counts = layout.count()
|
||||
for i in range(lauout_counts):
|
||||
layout_counts = layout.count()
|
||||
for i in range(layout_counts):
|
||||
item = layout.itemAt(i)
|
||||
if item.widget():
|
||||
item.widget().close()
|
||||
|
||||
@@ -305,7 +305,7 @@ class ScanHistoryView(BECWidget, QtWidgets.QTreeWidget):
|
||||
def remove_scan(self, index: int):
|
||||
"""
|
||||
Remove a scan entry from the tree widget.
|
||||
We supoprt negative indexing where -1, -2, etc.
|
||||
We support negative indexing where -1, -2, etc.
|
||||
|
||||
Args:
|
||||
index (int): The index of the scan entry to remove.
|
||||
|
||||
@@ -31,7 +31,7 @@ api_reference/api_reference.md
|
||||
|
||||
## Introduction
|
||||
|
||||
An introduction into the single-resposibility principle and the modular design of BEC Widgets.
|
||||
An introduction into the single-responsibility principle and the modular design of BEC Widgets.
|
||||
```
|
||||
|
||||
```{grid-item-card}
|
||||
|
||||
@@ -19,7 +19,7 @@ cd bec_widgets
|
||||
```
|
||||
**Install in Editable Mode**:
|
||||
|
||||
Please install the package in editable mode into your BEC Python environemnt.
|
||||
Please install the package in editable mode into your BEC Python environment.
|
||||
```bash
|
||||
pip install -e '.[dev,pyside6]'
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ that the widgets are discoverable.
|
||||
- make sure that the widget class inherits from both `BECWidget` as well as `QWidget` or a subclass
|
||||
of it, such as `QComboBox` or `QLineEdit`.
|
||||
- make sure it initialises each of these superclasses in its `__init__()` method, and passes the
|
||||
`parent` keyword argumment on to `QWidget.__init__()`.
|
||||
`parent` keyword argument on to `QWidget.__init__()`.
|
||||
- add `PLUGIN = True` as a class variable to the widget class
|
||||
- add `USER_ACCESS = [...]`, including any methods and properties which should be accessible in the
|
||||
client to the list, as strings.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
````
|
||||
|
||||
````{tab} Examples - CLI
|
||||
In the following examples, we will use `BECIPythonClient` as the main object to interact with the `BECDockArea`. These tutorials focus on how to work with the `BECDockArea` framework, such as adding and removing docks, saving and restoring layouts, and managing the docked widgets. By default the `BECDockArea` is refered as `gui` in `BECIPythonClient`. For more detailed examples of each individual component, please refer to the example sections of each individual [`widget`](user.widgets).
|
||||
In the following examples, we will use `BECIPythonClient` as the main object to interact with the `BECDockArea`. These tutorials focus on how to work with the `BECDockArea` framework, such as adding and removing docks, saving and restoring layouts, and managing the docked widgets. By default the `BECDockArea` is referred to as `gui` in `BECIPythonClient`. For more detailed examples of each individual component, please refer to the example sections of each individual [`widget`](user.widgets).
|
||||
|
||||
## Example 1 - Adding Docks to BECDockArea
|
||||
|
||||
@@ -62,7 +62,7 @@ dock_area.waveform_dock
|
||||
dock_area.motor_dock
|
||||
dock_area.image_dock
|
||||
|
||||
# If objects were closed, we will keep a refernce that will indicate that the dock was deleted
|
||||
# If objects were closed, we will keep a reference that will indicate that the dock was deleted
|
||||
# Try closing the window with the dock_area via mouse click on x
|
||||
|
||||
dock_area
|
||||
|
||||
@@ -79,7 +79,7 @@ if __name__ == "__main__":
|
||||
|
||||
````
|
||||
|
||||
````{tab} Examples - BEC desginer
|
||||
````{tab} Examples - BEC designer
|
||||
The various properties can also be set when the SignalLabel widget is added to a UI in BEC designer:
|
||||
|
||||
```{figure} ./designer_screenshot.png
|
||||
|
||||
@@ -215,7 +215,7 @@ Display custom text or HTML content.
|
||||
Display website content.
|
||||
```
|
||||
|
||||
```{grid-item-card} Toogle Widget
|
||||
```{grid-item-card} Toggle Widget
|
||||
:link: user.widgets.toggle
|
||||
:link-type: ref
|
||||
:img-top: /assets/widget_screenshots/toggle.png
|
||||
@@ -244,7 +244,7 @@ Modern progress bar for BEC.
|
||||
:link-type: ref
|
||||
:img-top: /assets/widget_screenshots/position_indicator.png
|
||||
|
||||
Display position of motor withing its limits.
|
||||
Display position of motor within its limits.
|
||||
```
|
||||
|
||||
```{grid-item-card} LMFit Dialog
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "3.3.0"
|
||||
version = "3.3.3"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
|
||||
@@ -32,8 +32,8 @@ def threads_check_fixture(threads_check):
|
||||
|
||||
@pytest.fixture
|
||||
def gui_id():
|
||||
"""New gui id each time, to ensure no 'gui is alive' zombie key can perturbate"""
|
||||
return f"figure_{random.randint(0,100)}" # make a new gui id each time, to ensure no 'gui is alive' zombie key can perturbate
|
||||
"""New gui id each time, to ensure no 'gui is alive' zombie key can perturb"""
|
||||
return f"figure_{random.randint(0,100)}" # make a new gui id each time, to ensure no 'gui is alive' zombie key can perturb
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
|
||||
@@ -22,7 +22,7 @@ from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def gui_id():
|
||||
"""New gui id each time, to ensure no 'gui is alive' zombie key can perturbate"""
|
||||
"""New gui id each time, to ensure no 'gui is alive' zombie key can perturb"""
|
||||
return f"figure_{random.randint(0,100)}"
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ def dap_combobox(qtbot, mocked_client):
|
||||
|
||||
def test_dap_combobox_init(dap_combobox):
|
||||
"""Test DapComboBox init."""
|
||||
assert dap_combobox.fit_model_combobox is dap_combobox
|
||||
assert dap_combobox.isEditable() is True
|
||||
assert dap_combobox.fit_model_combobox.currentText() == "GaussianModel"
|
||||
assert dap_combobox.available_models == ["GaussianModel", "LorentzModel", "SineModel"]
|
||||
assert dap_combobox._validate_dap_model("GaussianModel") is True
|
||||
@@ -30,7 +32,7 @@ def test_dap_combobox_set_axis(dap_combobox):
|
||||
container = []
|
||||
|
||||
def my_callback(msg: str):
|
||||
"""Calback function to store the messages."""
|
||||
"""Callback function to store the messages."""
|
||||
container.append(msg)
|
||||
|
||||
dap_combobox.x_axis_updated.connect(my_callback)
|
||||
@@ -49,7 +51,7 @@ def test_dap_combobox_select_fit(dap_combobox):
|
||||
container = []
|
||||
|
||||
def my_callback(msg: str):
|
||||
"""Calback function to store the messages."""
|
||||
"""Callback function to store the messages."""
|
||||
container.append(msg)
|
||||
|
||||
dap_combobox.fit_model_updated.connect(my_callback)
|
||||
@@ -64,10 +66,32 @@ def test_dap_combobox_currentTextchanged(dap_combobox):
|
||||
container = []
|
||||
|
||||
def my_callback(msg: str):
|
||||
"""Calback function to store the messages."""
|
||||
"""Callback function to store the messages."""
|
||||
container.append(msg)
|
||||
|
||||
assert dap_combobox.fit_model_combobox.currentText() == "GaussianModel"
|
||||
dap_combobox.fit_model_updated.connect(my_callback)
|
||||
dap_combobox.fit_model_combobox.setCurrentText("SineModel")
|
||||
assert container[0] == "SineModel"
|
||||
|
||||
|
||||
def test_dap_combobox_init_without_available_models(qtbot, mocked_client):
|
||||
mocked_client.dap._available_dap_plugins = {}
|
||||
|
||||
widget = create_widget(qtbot, DapComboBox, client=mocked_client)
|
||||
|
||||
assert widget.available_models == []
|
||||
assert widget.fit_model_combobox.count() == 0
|
||||
assert widget.fit_model_combobox.currentText() == ""
|
||||
|
||||
|
||||
def test_dap_combobox_invalid_manual_entry_highlighted(dap_combobox):
|
||||
dap_combobox.setCurrentText("not-a-model")
|
||||
|
||||
assert dap_combobox.is_valid_input is False
|
||||
assert "red" in dap_combobox.styleSheet()
|
||||
|
||||
dap_combobox.setCurrentText("GaussianModel")
|
||||
|
||||
assert dap_combobox.is_valid_input is True
|
||||
assert "transparent" in dap_combobox.styleSheet()
|
||||
|
||||
@@ -1422,7 +1422,7 @@ class TestDeviceConfigTemplate:
|
||||
qtbot.waitExposed(template)
|
||||
yield template
|
||||
|
||||
def test_device_config_teamplate_default_init(
|
||||
def test_device_config_template_default_init(
|
||||
self, device_config_template: DeviceConfigTemplate, qtbot
|
||||
):
|
||||
"""Test DeviceConfigTemplate default initialization."""
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_client_generator_with_black_formatting():
|
||||
|
||||
|
||||
class _WidgetsEnumType(str, enum.Enum):
|
||||
"""Enum for the available widgets, to be generated programatically"""
|
||||
"""Enum for the available widgets, to be generated programmatically"""
|
||||
|
||||
...
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ class PositionerWithoutPrecision(Positioner):
|
||||
def positioner_box(qtbot, mocked_client):
|
||||
"""Fixture for PositionerBox widget"""
|
||||
with mock.patch(
|
||||
"bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4"
|
||||
"bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4"
|
||||
) as mock_uuid:
|
||||
mock_uuid.return_value = "fake_uuid"
|
||||
with mock.patch(
|
||||
"bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.PositionerBoxBase._check_device_is_valid",
|
||||
"bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.PositionerBoxBase._check_device_is_valid",
|
||||
return_value=True,
|
||||
):
|
||||
db = create_widget(qtbot, PositionerBox, device="samx", client=mocked_client)
|
||||
@@ -141,7 +141,7 @@ def test_positioner_control_line(qtbot, mocked_client):
|
||||
Inherits from PositionerBox, but the layout is changed. Check dimensions only
|
||||
"""
|
||||
with mock.patch(
|
||||
"bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4"
|
||||
"bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4"
|
||||
) as mock_uuid:
|
||||
mock_uuid.return_value = "fake_uuid"
|
||||
with mock.patch(
|
||||
@@ -151,7 +151,8 @@ def test_positioner_control_line(qtbot, mocked_client):
|
||||
db = PositionerControlLine(device="samx", client=mocked_client)
|
||||
qtbot.addWidget(db)
|
||||
|
||||
assert db.ui.device_box.height() == 60
|
||||
assert db.ui.device_box.height() == db.height()
|
||||
assert db.ui.device_box.height() >= db.dimensions[0]
|
||||
assert db.ui.device_box.width() == 600
|
||||
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ from .conftest import create_widget
|
||||
def positioner_box_2d(qtbot, mocked_client):
|
||||
"""Fixture for PositionerBox widget"""
|
||||
with mock.patch(
|
||||
"bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4"
|
||||
"bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4"
|
||||
) as mock_uuid:
|
||||
mock_uuid.return_value = "fake_uuid"
|
||||
with mock.patch(
|
||||
"bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.PositionerBoxBase._check_device_is_valid",
|
||||
"bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.PositionerBoxBase._check_device_is_valid",
|
||||
return_value=True,
|
||||
):
|
||||
db = create_widget(
|
||||
|
||||
@@ -113,7 +113,7 @@ def metadata_widget(empty_metadata_widget: ScanMetadata):
|
||||
)
|
||||
|
||||
|
||||
def fill_commponents(components: dict[str, DynamicFormItem]):
|
||||
def fill_components(components: dict[str, DynamicFormItem]):
|
||||
components["sample_name"].setValue("test name")
|
||||
components["str_optional"].setValue(None)
|
||||
components["str_required"].setValue("something")
|
||||
@@ -147,7 +147,7 @@ def test_griditems_are_correct_class(
|
||||
|
||||
def test_grid_to_dict(metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]]):
|
||||
widget, components = metadata_widget = metadata_widget
|
||||
fill_commponents(components)
|
||||
fill_components(components)
|
||||
|
||||
assert widget._dict_from_grid() == TEST_DICT
|
||||
assert widget.get_form_data() == TEST_DICT | {"extra_field": "extra_data"}
|
||||
@@ -159,7 +159,7 @@ def test_validation(metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormIt
|
||||
widget._validity.compact_status.default_led[:114]
|
||||
)
|
||||
|
||||
fill_commponents(components)
|
||||
fill_components(components)
|
||||
widget.validate_form()
|
||||
assert widget._validity_message.text() == "No errors!"
|
||||
|
||||
@@ -178,7 +178,7 @@ def test_numbers_clipped_to_limits(
|
||||
metadata_widget: tuple[ScanMetadata, dict[str, DynamicFormItem]],
|
||||
):
|
||||
widget, components = metadata_widget = metadata_widget
|
||||
fill_commponents(components)
|
||||
fill_components(components)
|
||||
|
||||
components["decimal_dp_limits_nodefault"].setValue(-56)
|
||||
assert components["decimal_dp_limits_nodefault"].getValue() == pytest.approx(1.01)
|
||||
|
||||
@@ -637,7 +637,7 @@ def test_fetch_scan_data_and_access(qtbot, mocked_client, monkeypatch):
|
||||
wf._fetch_scan_data_and_access()
|
||||
hist_mock.assert_called_once_with(-1)
|
||||
|
||||
# Ckeck live mode
|
||||
# Check live mode
|
||||
dummy_scan = create_dummy_scan_item()
|
||||
wf.scan_item = dummy_scan
|
||||
data_dict, access_key = wf._fetch_scan_data_and_access()
|
||||
|
||||
Reference in New Issue
Block a user