mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
feat: add filter i/o utility class
This commit is contained in:
@ -38,6 +38,8 @@ class Widgets(str, enum.Enum):
|
||||
ResumeButton = "ResumeButton"
|
||||
RingProgressBar = "RingProgressBar"
|
||||
ScanControl = "ScanControl"
|
||||
SignalComboBox = "SignalComboBox"
|
||||
SignalLineEdit = "SignalLineEdit"
|
||||
StopButton = "StopButton"
|
||||
TextBox = "TextBox"
|
||||
VSCodeEditor = "VSCodeEditor"
|
||||
@ -2591,6 +2593,24 @@ class DeviceLineEdit(RPCBase):
|
||||
"""
|
||||
|
||||
|
||||
class DeviceSignalInputBase(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
|
||||
class LMFitDialog(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
@ -3003,6 +3023,42 @@ class ScanControl(RPCBase):
|
||||
"""
|
||||
|
||||
|
||||
class SignalComboBox(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
|
||||
class SignalLineEdit(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def _config_dict(self) -> "dict":
|
||||
"""
|
||||
Get the configuration of the widget.
|
||||
|
||||
Returns:
|
||||
dict: The configuration of the widget.
|
||||
"""
|
||||
|
||||
@rpc_call
|
||||
def _get_all_rpc(self) -> "dict":
|
||||
"""
|
||||
Get all registered RPC objects.
|
||||
"""
|
||||
|
||||
|
||||
class StopButton(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
|
0
bec_widgets/test_utils/__init__.py
Normal file
0
bec_widgets/test_utils/__init__.py
Normal file
225
bec_widgets/test_utils/client_mocks.py
Normal file
225
bec_widgets/test_utils/client_mocks.py
Normal file
@ -0,0 +1,225 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.device import Device as BECDevice
|
||||
from bec_lib.device import Positioner as BECPositioner
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from bec_lib.devicemanager import DeviceContainer
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
|
||||
|
||||
class FakeDevice(BECDevice):
|
||||
"""Fake minimal positioner class for testing."""
|
||||
|
||||
def __init__(self, name, enabled=True, readout_priority=ReadoutPriority.MONITORED):
|
||||
super().__init__(name=name)
|
||||
self._enabled = enabled
|
||||
self.signals = {self.name: {"value": 1.0}}
|
||||
self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
|
||||
self._readout_priority = readout_priority
|
||||
self._config = {
|
||||
"readoutPriority": "baseline",
|
||||
"deviceClass": "ophyd.Device",
|
||||
"deviceConfig": {},
|
||||
"deviceTags": ["user device"],
|
||||
"enabled": enabled,
|
||||
"readOnly": False,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
@property
|
||||
def readout_priority(self):
|
||||
return self._readout_priority
|
||||
|
||||
@readout_priority.setter
|
||||
def readout_priority(self, value):
|
||||
self._readout_priority = value
|
||||
|
||||
@property
|
||||
def limits(self) -> tuple[float, float]:
|
||||
return self._limits
|
||||
|
||||
@limits.setter
|
||||
def limits(self, value: tuple[float, float]):
|
||||
self._limits = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return item == self.name
|
||||
|
||||
@property
|
||||
def _hints(self):
|
||||
return [self.name]
|
||||
|
||||
def set_value(self, fake_value: float = 1.0) -> None:
|
||||
"""
|
||||
Setup fake value for device readout
|
||||
Args:
|
||||
fake_value(float): Desired fake value
|
||||
"""
|
||||
self.signals[self.name]["value"] = fake_value
|
||||
|
||||
def describe(self) -> dict:
|
||||
"""
|
||||
Get the description of the device
|
||||
Returns:
|
||||
dict: Description of the device
|
||||
"""
|
||||
return self.description
|
||||
|
||||
|
||||
class FakePositioner(BECPositioner):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
enabled=True,
|
||||
limits=None,
|
||||
read_value=1.0,
|
||||
readout_priority=ReadoutPriority.MONITORED,
|
||||
):
|
||||
super().__init__(name=name)
|
||||
# self.limits = limits if limits is not None else [0.0, 0.0]
|
||||
self.read_value = read_value
|
||||
self._enabled = enabled
|
||||
self._limits = limits
|
||||
self._readout_priority = readout_priority
|
||||
self.signals = {self.name: {"value": 1.0}}
|
||||
self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
|
||||
self._config = {
|
||||
"readoutPriority": "baseline",
|
||||
"deviceClass": "ophyd_devices.SimPositioner",
|
||||
"deviceConfig": {"delay": 1, "tolerance": 0.01, "update_frequency": 400},
|
||||
"deviceTags": ["user motors"],
|
||||
"enabled": enabled,
|
||||
"readOnly": False,
|
||||
"name": self.name,
|
||||
}
|
||||
self._info = {
|
||||
"signals": {
|
||||
"readback": {"kind_str": "5"}, # hinted
|
||||
"setpoint": {"kind_str": "1"}, # normal
|
||||
"velocity": {"kind_str": "2"}, # config
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def readout_priority(self):
|
||||
return self._readout_priority
|
||||
|
||||
@readout_priority.setter
|
||||
def readout_priority(self, value):
|
||||
self._readout_priority = value
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, value: bool):
|
||||
self._enabled = value
|
||||
|
||||
@property
|
||||
def limits(self) -> tuple[float, float]:
|
||||
return self._limits
|
||||
|
||||
@limits.setter
|
||||
def limits(self, value: tuple[float, float]):
|
||||
self._limits = value
|
||||
|
||||
def __contains__(self, item):
|
||||
return item == self.name
|
||||
|
||||
@property
|
||||
def _hints(self):
|
||||
return [self.name]
|
||||
|
||||
def set_value(self, fake_value: float = 1.0) -> None:
|
||||
"""
|
||||
Setup fake value for device readout
|
||||
Args:
|
||||
fake_value(float): Desired fake value
|
||||
"""
|
||||
self.signals[self.name]["value"] = fake_value
|
||||
|
||||
def describe(self) -> dict:
|
||||
"""
|
||||
Get the description of the device
|
||||
Returns:
|
||||
dict: Description of the device
|
||||
"""
|
||||
return self.description
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
return 3
|
||||
|
||||
def set_read_value(self, value):
|
||||
self.read_value = value
|
||||
|
||||
def read(self):
|
||||
return {
|
||||
self.name: {"value": self.read_value},
|
||||
f"{self.name}_setpoint": {"value": self.read_value},
|
||||
f"{self.name}_motor_is_moving": {"value": 0},
|
||||
}
|
||||
|
||||
def set_limits(self, limits):
|
||||
self.limits = limits
|
||||
|
||||
def move(self, value, relative=False):
|
||||
"""Simulates moving the device to a new position."""
|
||||
if relative:
|
||||
self.read_value += value
|
||||
else:
|
||||
self.read_value = value
|
||||
# Respect the limits
|
||||
self.read_value = max(min(self.read_value, self.limits[1]), self.limits[0])
|
||||
|
||||
@property
|
||||
def readback(self):
|
||||
return MagicMock(get=MagicMock(return_value=self.read_value))
|
||||
|
||||
|
||||
class Positioner(FakePositioner):
|
||||
"""just placeholder for testing embedded isinstance check in DeviceCombobox"""
|
||||
|
||||
def __init__(self, name="test", limits=None, read_value=1.0):
|
||||
super().__init__(name, limits, read_value)
|
||||
|
||||
|
||||
class Device(FakeDevice):
|
||||
"""just placeholder for testing embedded isinstance check in DeviceCombobox"""
|
||||
|
||||
def __init__(self, name, enabled=True):
|
||||
super().__init__(name, enabled)
|
||||
|
||||
|
||||
class DMMock:
|
||||
def __init__(self):
|
||||
self.devices = DeviceContainer()
|
||||
self.enabled_devices = [device for device in self.devices if device.enabled]
|
||||
|
||||
def add_devives(self, devices: list):
|
||||
for device in devices:
|
||||
self.devices[device.name] = device
|
||||
|
||||
|
||||
DEVICES = [
|
||||
FakePositioner("samx", limits=[-10, 10], read_value=2.0),
|
||||
FakePositioner("samy", limits=[-5, 5], read_value=3.0),
|
||||
FakePositioner("samz", limits=[-8, 8], read_value=4.0),
|
||||
FakePositioner("aptrx", limits=None, read_value=4.0),
|
||||
FakePositioner("aptry", limits=None, read_value=5.0),
|
||||
FakeDevice("gauss_bpm"),
|
||||
FakeDevice("gauss_adc1"),
|
||||
FakeDevice("gauss_adc2"),
|
||||
FakeDevice("gauss_adc3"),
|
||||
FakeDevice("bpm4i"),
|
||||
FakeDevice("bpm3a"),
|
||||
FakeDevice("bpm3i"),
|
||||
FakeDevice("eiger"),
|
||||
FakeDevice("waveform1d"),
|
||||
FakeDevice("async_device", readout_priority=ReadoutPriority.ASYNC),
|
||||
Positioner("test", limits=[-10, 10], read_value=2.0),
|
||||
Device("test_device"),
|
||||
]
|
156
bec_widgets/utils/filter_io.py
Normal file
156
bec_widgets/utils/filter_io.py
Normal file
@ -0,0 +1,156 @@
|
||||
"""Module for handling filter I/O operations in BEC Widgets for input fields.
|
||||
These operations include filtering device/signal names and/or device types.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QStringListModel
|
||||
from qtpy.QtWidgets import QComboBox, QCompleter, QLineEdit
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class WidgetFilterHandler(ABC):
|
||||
"""Abstract base class for widget filter handlers"""
|
||||
|
||||
@abstractmethod
|
||||
def set_selection(self, widget, selection: list) -> None:
|
||||
"""Set the filtered_selection for the widget
|
||||
|
||||
Args:
|
||||
selection (list): Filtered selection of items
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def check_input(self, widget, text: str) -> bool:
|
||||
"""Check if the input text is in the filtered selection
|
||||
|
||||
Args:
|
||||
widget: Widget instance
|
||||
text (str): Input text
|
||||
|
||||
Returns:
|
||||
bool: True if the input text is in the filtered selection
|
||||
"""
|
||||
|
||||
|
||||
class LineEditFilterHandler(WidgetFilterHandler):
|
||||
"""Handler for QLineEdit widget"""
|
||||
|
||||
def set_selection(self, widget: QLineEdit, selection: list) -> None:
|
||||
"""Set the selection for the widget to the completer model
|
||||
|
||||
Args:
|
||||
widget (QLineEdit): The QLineEdit widget
|
||||
selection (list): Filtered selection of items
|
||||
"""
|
||||
if not isinstance(widget.completer, QCompleter):
|
||||
completer = QCompleter(widget)
|
||||
widget.setCompleter(completer)
|
||||
widget.completer.setModel(QStringListModel(selection, widget))
|
||||
|
||||
def check_input(self, widget: QLineEdit, text: str) -> bool:
|
||||
"""Check if the input text is in the filtered selection
|
||||
|
||||
Args:
|
||||
widget (QLineEdit): The QLineEdit widget
|
||||
text (str): Input text
|
||||
|
||||
Returns:
|
||||
bool: True if the input text is in the filtered selection
|
||||
"""
|
||||
model = widget.completer.model()
|
||||
model_data = [model.data(model.index(i)) for i in range(model.rowCount())]
|
||||
return text in model_data
|
||||
|
||||
|
||||
class ComboBoxFilterHandler(WidgetFilterHandler):
|
||||
"""Handler for QComboBox widget"""
|
||||
|
||||
def set_selection(self, widget: QComboBox, selection: list) -> None:
|
||||
"""Set the selection for the widget to the completer model
|
||||
|
||||
Args:
|
||||
widget (QComboBox): The QComboBox widget
|
||||
selection (list): Filtered selection of items
|
||||
"""
|
||||
widget.clear()
|
||||
widget.addItems(selection)
|
||||
|
||||
def check_input(self, widget: QComboBox, text: str) -> bool:
|
||||
"""Check if the input text is in the filtered selection
|
||||
|
||||
Args:
|
||||
widget (QComboBox): The QComboBox widget
|
||||
text (str): Input text
|
||||
|
||||
Returns:
|
||||
bool: True if the input text is in the filtered selection
|
||||
"""
|
||||
return text in [widget.itemText(i) for i in range(widget.count())]
|
||||
|
||||
|
||||
class FilterIO:
|
||||
"""Public interface to set filters for input widgets.
|
||||
It supports the list of widgets stored in class attribute _handlers.
|
||||
"""
|
||||
|
||||
_handlers = {QLineEdit: LineEditFilterHandler, QComboBox: ComboBoxFilterHandler}
|
||||
|
||||
@staticmethod
|
||||
def set_selection(widget, selection: list, ignore_errors=False):
|
||||
"""
|
||||
Retrieve value from the widget instance.
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
selection(list): List of filtered selection items.
|
||||
ignore_errors(bool, optional): Whether to ignore if no handler is found.
|
||||
"""
|
||||
handler_class = FilterIO._find_handler(widget)
|
||||
if handler_class:
|
||||
return handler_class().set_selection(widget=widget, selection=selection)
|
||||
if not ignore_errors:
|
||||
raise ValueError(
|
||||
f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def check_input(widget, text: str, ignore_errors=False):
|
||||
"""
|
||||
Check if the input text is in the filtered selection.
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
text(str): Input text.
|
||||
ignore_errors(bool, optional): Whether to ignore if no handler is found.
|
||||
|
||||
Returns:
|
||||
bool: True if the input text is in the filtered selection.
|
||||
"""
|
||||
handler_class = FilterIO._find_handler(widget)
|
||||
if handler_class:
|
||||
return handler_class().check_input(widget=widget, text=text)
|
||||
if not ignore_errors:
|
||||
raise ValueError(
|
||||
f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _find_handler(widget):
|
||||
"""
|
||||
Find the appropriate handler for the widget by checking its base classes.
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
|
||||
Returns:
|
||||
handler_class: The handler class if found, otherwise None.
|
||||
"""
|
||||
for base in type(widget).__mro__:
|
||||
if base in FilterIO._handlers:
|
||||
return FilterIO._handlers[base]
|
||||
return None
|
@ -1,22 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
|
||||
from bec_lib.device import ComputedSignal, Device, Positioner, ReadoutPriority, Signal
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Property, Slot
|
||||
from typeguard import typechecked
|
||||
|
||||
from bec_widgets.utils import ConnectionConfig
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.filter_io import FilterIO
|
||||
from bec_widgets.utils.widget_io import WidgetIO
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class BECDeviceFilter(enum.Enum):
|
||||
"""Filter for the device classes."""
|
||||
|
||||
DEVICE = "Device"
|
||||
POSITIONER = "Positioner"
|
||||
SIGNAL = "Signal"
|
||||
COMPUTED_SIGNAL = "ComputedSignal"
|
||||
|
||||
|
||||
class DeviceInputConfig(ConnectionConfig):
|
||||
device_filter: str | list[str] | None = None
|
||||
device_filter: list[BECDeviceFilter] | None = None
|
||||
readout_filter: list[ReadoutPriority] | None = None
|
||||
devices: list[str] | None = None
|
||||
default: str | None = None
|
||||
arg_name: str | None = None
|
||||
|
||||
|
||||
class DeviceInputBase(BECWidget):
|
||||
"""
|
||||
Mixin class for device input widgets. This class provides methods to get the device list and device object based
|
||||
on the current text of the widget.
|
||||
Mixin base class for device input widgets.
|
||||
It allows to filter devices from BEC based on
|
||||
device class and readout priority.
|
||||
"""
|
||||
|
||||
def __init__(self, client=None, config=None, gui_id=None):
|
||||
_device_handler = {
|
||||
BECDeviceFilter.DEVICE: Device,
|
||||
BECDeviceFilter.POSITIONER: Positioner,
|
||||
BECDeviceFilter.SIGNAL: Signal,
|
||||
BECDeviceFilter.COMPUTED_SIGNAL: ComputedSignal,
|
||||
}
|
||||
|
||||
_filter_handler = {
|
||||
BECDeviceFilter.DEVICE: "include_device",
|
||||
BECDeviceFilter.POSITIONER: "include_positioner",
|
||||
BECDeviceFilter.SIGNAL: "include_signal",
|
||||
BECDeviceFilter.COMPUTED_SIGNAL: "include_computed_signal",
|
||||
ReadoutPriority.MONITORED: "readout_monitored",
|
||||
ReadoutPriority.BASELINE: "readout_baseline",
|
||||
ReadoutPriority.ASYNC: "readout_async",
|
||||
ReadoutPriority.CONTINUOUS: "readout_continuous",
|
||||
ReadoutPriority.ON_REQUEST: "readout_on_request",
|
||||
}
|
||||
|
||||
def __init__(self, client=None, config=None, gui_id: str = None):
|
||||
|
||||
if config is None:
|
||||
config = DeviceInputConfig(widget_class=self.__class__.__name__)
|
||||
else:
|
||||
@ -24,15 +67,192 @@ class DeviceInputBase(BECWidget):
|
||||
config = DeviceInputConfig(**config)
|
||||
self.config = config
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
|
||||
self.get_bec_shortcuts()
|
||||
self._device_filter = None
|
||||
self._device_filter = []
|
||||
self._readout_filter = []
|
||||
self._devices = []
|
||||
|
||||
### QtSlots ###
|
||||
|
||||
@Slot(str)
|
||||
def set_device(self, device: str):
|
||||
"""
|
||||
Set the device.
|
||||
|
||||
Args:
|
||||
device (str): Default name.
|
||||
"""
|
||||
if self.validate_device(device, raise_on_false=True) is True:
|
||||
WidgetIO.set_value(widget=self, value=device)
|
||||
self.config.default = device
|
||||
else:
|
||||
logger.warning(f"Device {device} is not in the filtered selection.")
|
||||
|
||||
@Slot()
|
||||
def update_devices_from_filters(self):
|
||||
"""Update the devices based on the current filter selection
|
||||
in self.device_filter and self.readout_filter."""
|
||||
self.config.device_filter = self.device_filter
|
||||
self.config.readout_filter = self.readout_filter
|
||||
all_dev = self.dev.enabled_devices
|
||||
# Filter based on device class
|
||||
devs = [dev for dev in all_dev if self._check_device_filter(dev)]
|
||||
# Filter based on readout priority
|
||||
devs = [dev for dev in devs if self._check_readout_filter(dev)]
|
||||
self.devices = [device.name for device in devs]
|
||||
|
||||
@Slot(list)
|
||||
def set_available_devices(self, devices: list[str]):
|
||||
"""
|
||||
Set the devices. If a device in the list is not valid, it will not be considered.
|
||||
|
||||
Args:
|
||||
devices (list[str]): List of devices.
|
||||
"""
|
||||
valid_dev = []
|
||||
all_dev_names = [dev.name for dev in self.dev.enabled_devices]
|
||||
for device in devices:
|
||||
if device not in all_dev_names:
|
||||
continue
|
||||
valid_dev.append(device)
|
||||
self.devices = valid_dev
|
||||
|
||||
### QtProperties ###
|
||||
|
||||
@Property(str)
|
||||
def default(self):
|
||||
"""Get the default device name. If set through this property, it will update only if the device is within the filtered selection."""
|
||||
return self.config.default
|
||||
|
||||
@default.setter
|
||||
def default(self, value: str):
|
||||
if self.validate_device(value, raise_on_false=False) is False:
|
||||
return
|
||||
self.set_device(value)
|
||||
|
||||
@Property(bool)
|
||||
def include_device(self):
|
||||
"""Include devices in filters."""
|
||||
return BECDeviceFilter.DEVICE in self.device_filter
|
||||
|
||||
@include_device.setter
|
||||
def include_device(self, value: bool):
|
||||
if value is True and BECDeviceFilter.DEVICE not in self.device_filter:
|
||||
self._device_filter.append(BECDeviceFilter.DEVICE)
|
||||
if value is False and BECDeviceFilter.DEVICE in self.device_filter:
|
||||
self._device_filter.remove(BECDeviceFilter.DEVICE)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_positioner(self):
|
||||
"""Include devices of type Positioner in filters."""
|
||||
return BECDeviceFilter.POSITIONER in self.device_filter
|
||||
|
||||
@include_positioner.setter
|
||||
def include_positioner(self, value: bool):
|
||||
if value is True and BECDeviceFilter.POSITIONER not in self.device_filter:
|
||||
self._device_filter.append(BECDeviceFilter.POSITIONER)
|
||||
if value is False and BECDeviceFilter.POSITIONER in self.device_filter:
|
||||
self._device_filter.remove(BECDeviceFilter.POSITIONER)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_signal(self):
|
||||
"""Include devices of type Signal in filters."""
|
||||
return BECDeviceFilter.SIGNAL in self.device_filter
|
||||
|
||||
@include_signal.setter
|
||||
def include_signal(self, value: bool):
|
||||
if value is True and BECDeviceFilter.SIGNAL not in self.device_filter:
|
||||
self._device_filter.append(BECDeviceFilter.SIGNAL)
|
||||
if value is False and BECDeviceFilter.SIGNAL in self.device_filter:
|
||||
self._device_filter.remove(BECDeviceFilter.SIGNAL)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_computed_signal(self):
|
||||
"""Include devices of type ComputedSignal in filters."""
|
||||
return BECDeviceFilter.COMPUTED_SIGNAL in self.device_filter
|
||||
|
||||
@include_computed_signal.setter
|
||||
def include_computed_signal(self, value: bool):
|
||||
if value is True and BECDeviceFilter.COMPUTED_SIGNAL not in self.device_filter:
|
||||
self._device_filter.append(BECDeviceFilter.COMPUTED_SIGNAL)
|
||||
if value is False and BECDeviceFilter.COMPUTED_SIGNAL in self.device_filter:
|
||||
self._device_filter.remove(BECDeviceFilter.COMPUTED_SIGNAL)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def readout_monitored(self):
|
||||
"""Include devices with readout priority Monitored in filters."""
|
||||
return ReadoutPriority.MONITORED in self.readout_filter
|
||||
|
||||
@readout_monitored.setter
|
||||
def readout_monitored(self, value: bool):
|
||||
if value is True and ReadoutPriority.MONITORED not in self.readout_filter:
|
||||
self._readout_filter.append(ReadoutPriority.MONITORED)
|
||||
if value is False and ReadoutPriority.MONITORED in self.readout_filter:
|
||||
self._readout_filter.remove(ReadoutPriority.MONITORED)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def readout_baseline(self):
|
||||
"""Include devices with readout priority Baseline in filters."""
|
||||
return ReadoutPriority.BASELINE in self.readout_filter
|
||||
|
||||
@readout_baseline.setter
|
||||
def readout_baseline(self, value: bool):
|
||||
if value is True and ReadoutPriority.BASELINE not in self.readout_filter:
|
||||
self._readout_filter.append(ReadoutPriority.BASELINE)
|
||||
if value is False and ReadoutPriority.BASELINE in self.readout_filter:
|
||||
self._readout_filter.remove(ReadoutPriority.BASELINE)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def readout_async(self):
|
||||
"""Include devices with readout priority Async in filters."""
|
||||
return ReadoutPriority.ASYNC in self.readout_filter
|
||||
|
||||
@readout_async.setter
|
||||
def readout_async(self, value: bool):
|
||||
if value is True and ReadoutPriority.ASYNC not in self.readout_filter:
|
||||
self._readout_filter.append(ReadoutPriority.ASYNC)
|
||||
if value is False and ReadoutPriority.ASYNC in self.readout_filter:
|
||||
self._readout_filter.remove(ReadoutPriority.ASYNC)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def readout_continuous(self):
|
||||
"""Include devices with readout priority continuous in filters."""
|
||||
return ReadoutPriority.CONTINUOUS in self.readout_filter
|
||||
|
||||
@readout_continuous.setter
|
||||
def readout_continuous(self, value: bool):
|
||||
if value is True and ReadoutPriority.CONTINUOUS not in self.readout_filter:
|
||||
self._readout_filter.append(ReadoutPriority.CONTINUOUS)
|
||||
if value is False and ReadoutPriority.CONTINUOUS in self.readout_filter:
|
||||
self._readout_filter.remove(ReadoutPriority.CONTINUOUS)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def readout_on_request(self):
|
||||
"""Include devices with readout priority OnRequest in filters."""
|
||||
return ReadoutPriority.ON_REQUEST in self.readout_filter
|
||||
|
||||
@readout_on_request.setter
|
||||
def readout_on_request(self, value: bool):
|
||||
if value is True and ReadoutPriority.ON_REQUEST not in self.readout_filter:
|
||||
self._readout_filter.append(ReadoutPriority.ON_REQUEST)
|
||||
if value is False and ReadoutPriority.ON_REQUEST in self.readout_filter:
|
||||
self._readout_filter.remove(ReadoutPriority.ON_REQUEST)
|
||||
self.update_devices_from_filters()
|
||||
|
||||
### Python Methods and Properties ###
|
||||
|
||||
@property
|
||||
def devices(self) -> list[str]:
|
||||
"""
|
||||
Get the list of devices.
|
||||
Get the list of devices for the applied filters.
|
||||
|
||||
Returns:
|
||||
list[str]: List of devices.
|
||||
@ -41,83 +261,116 @@ class DeviceInputBase(BECWidget):
|
||||
|
||||
@devices.setter
|
||||
def devices(self, value: list[str]):
|
||||
"""
|
||||
Set the list of devices.
|
||||
|
||||
Args:
|
||||
value: List of devices.
|
||||
"""
|
||||
self._devices = value
|
||||
self.config.devices = value
|
||||
FilterIO.set_selection(widget=self, selection=value)
|
||||
|
||||
def set_device_filter(self, device_filter: str | list[str]):
|
||||
@property
|
||||
def device_filter(self) -> list[object]:
|
||||
"""Get the list of filters to apply on the devices."""
|
||||
return self._device_filter
|
||||
|
||||
@property
|
||||
def readout_filter(self) -> list[str]:
|
||||
"""Get the list of filters to apply on the devices"""
|
||||
return self._readout_filter
|
||||
|
||||
def get_available_filters(self) -> list:
|
||||
"""Get the available filters."""
|
||||
return [entry for entry in BECDeviceFilter]
|
||||
|
||||
def get_readout_priority_filters(self) -> list:
|
||||
"""Get the available readout priority filters."""
|
||||
return [entry for entry in ReadoutPriority]
|
||||
|
||||
@typechecked
|
||||
def set_device_filter(
|
||||
self, filter_selection: str | BECDeviceFilter | list[str] | list[BECDeviceFilter]
|
||||
):
|
||||
"""
|
||||
Set the device filter.
|
||||
Set the device filter. If None is passed, no filters are applied and all devices included.
|
||||
|
||||
Args:
|
||||
device_filter(str): Device filter, name of the device class.
|
||||
filter_selection (str | list[str]): Device filters. It is recommended to make an enum for the filters.
|
||||
"""
|
||||
self.validate_device_filter(device_filter)
|
||||
self.config.device_filter = device_filter
|
||||
self._device_filter = device_filter
|
||||
filters = None
|
||||
if isinstance(filter_selection, list):
|
||||
filters = [self._filter_handler.get(entry) for entry in filter_selection]
|
||||
if isinstance(filter_selection, str) or isinstance(filter_selection, BECDeviceFilter):
|
||||
filters = [self._filter_handler.get(filter_selection)]
|
||||
if filters is None or any([entry is None for entry in filters]):
|
||||
raise ValueError(f"Device filter {filter_selection} is not in the device list.")
|
||||
for entry in filters:
|
||||
setattr(self, entry, True)
|
||||
|
||||
def set_default_device(self, default_device: str):
|
||||
@typechecked
|
||||
def set_readout_priority_filter(
|
||||
self, filter_selection: str | ReadoutPriority | list[str] | list[ReadoutPriority]
|
||||
):
|
||||
"""
|
||||
Set the default device.
|
||||
Set the readout priority filter. If None is passed, all filters are included.
|
||||
|
||||
Args:
|
||||
default_device(str): Default device name.
|
||||
filter_selection (str | list[str]): Readout priority filters.
|
||||
"""
|
||||
self.validate_device(default_device)
|
||||
self.config.default = default_device
|
||||
filters = None
|
||||
if isinstance(filter_selection, list):
|
||||
filters = [self._filter_handler.get(entry) for entry in filter_selection]
|
||||
if isinstance(filter_selection, str) or isinstance(filter_selection, ReadoutPriority):
|
||||
filters = [self._filter_handler.get(filter_selection)]
|
||||
if filters is None or any([entry is None for entry in filters]):
|
||||
raise ValueError(
|
||||
f"Readout priority filter {filter_selection} is not in the readout priority list."
|
||||
)
|
||||
for entry in filters:
|
||||
setattr(self, entry, True)
|
||||
|
||||
def get_device_list(self, filter: str | list[str] | None = None) -> list[str]:
|
||||
"""
|
||||
Get the list of device names based on the filter of current BEC client.
|
||||
def _check_device_filter(self, device: Device | Signal | ComputedSignal | Positioner) -> bool:
|
||||
"""If filters are defined, return True. Else return if the device complies to all active filters.
|
||||
|
||||
Args:
|
||||
filter(str|None): Class name filter to apply on the device list.
|
||||
device(Device | Signal | ComputedSignal | Positioner): Device object.
|
||||
"""
|
||||
if len(self.device_filter) == 0:
|
||||
return True
|
||||
return all(isinstance(device, self._device_handler[entry]) for entry in self.device_filter)
|
||||
|
||||
def _check_readout_filter(self, device: Device | Signal | ComputedSignal | Positioner) -> bool:
|
||||
"""If filters are defined, return True. Else return if the device complies to all active filters.
|
||||
|
||||
Args:
|
||||
device(Device | Signal | ComputedSignal | Positioner): Device object.
|
||||
"""
|
||||
if len(self.readout_filter) == 0:
|
||||
return True
|
||||
return device.readout_priority in self.readout_filter
|
||||
|
||||
def get_device_object(self, device: str) -> object:
|
||||
"""
|
||||
Get the device object based on the device name.
|
||||
|
||||
Args:
|
||||
device(str): Device name.
|
||||
|
||||
Returns:
|
||||
devices(list[str]): List of device names.
|
||||
object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
|
||||
"""
|
||||
all_devices = self.dev.enabled_devices
|
||||
if filter is not None:
|
||||
self.validate_device_filter(filter)
|
||||
if isinstance(filter, str):
|
||||
filter = [filter]
|
||||
devices = [device.name for device in all_devices if device.__class__.__name__ in filter]
|
||||
else:
|
||||
devices = [device.name for device in all_devices]
|
||||
return devices
|
||||
self.validate_device(device)
|
||||
dev = getattr(self.dev, device.lower(), None)
|
||||
if dev is None:
|
||||
raise ValueError(
|
||||
f"Device {device} is not found in devicemanager {self.dev} as enabled device."
|
||||
)
|
||||
return dev
|
||||
|
||||
def get_available_filters(self):
|
||||
def validate_device(self, device: str, raise_on_false: bool = False) -> bool:
|
||||
"""
|
||||
Get the available device classes which can be used as filters.
|
||||
"""
|
||||
all_devices = self.dev.enabled_devices
|
||||
filters = {device.__class__.__name__ for device in all_devices}
|
||||
return filters
|
||||
|
||||
def validate_device_filter(self, filter: str | list[str]) -> None:
|
||||
"""
|
||||
Validate the device filter if the class name is present in the current BEC instance.
|
||||
|
||||
Args:
|
||||
filter(str|list[str]): Class name to use as a device filter.
|
||||
"""
|
||||
if isinstance(filter, str):
|
||||
filter = [filter]
|
||||
available_filters = self.get_available_filters()
|
||||
for f in filter:
|
||||
if f not in available_filters:
|
||||
raise ValueError(f"Device filter {f} is not valid.")
|
||||
|
||||
def validate_device(self, device: str) -> None:
|
||||
"""
|
||||
Validate the device if it is present in current BEC instance.
|
||||
Validate the device if it is present in the filtered device selection.
|
||||
|
||||
Args:
|
||||
device(str): Device to validate.
|
||||
"""
|
||||
if device not in self.get_device_list(self.config.device_filter):
|
||||
raise ValueError(f"Device {device} is not valid.")
|
||||
if device in self.devices:
|
||||
return True
|
||||
if raise_on_false is True:
|
||||
raise ValueError(f"Device {device} is not in filtered selection.")
|
||||
|
277
bec_widgets/widgets/base_classes/device_signal_input_base.py
Normal file
277
bec_widgets/widgets/base_classes/device_signal_input_base.py
Normal file
@ -0,0 +1,277 @@
|
||||
import enum
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import Property, Slot
|
||||
|
||||
from bec_widgets.utils import ConnectionConfig
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.filter_io import FilterIO
|
||||
from bec_widgets.utils.widget_io import WidgetIO
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class BECSignalFilter(str, enum.Enum):
|
||||
"""Filter for the device signals."""
|
||||
|
||||
HINTED = "5"
|
||||
NORMAL = "1"
|
||||
CONFIG = "2"
|
||||
|
||||
|
||||
class DeviceSignalInputBaseConfig(ConnectionConfig):
|
||||
"""Configuration class for DeviceSignalInputBase."""
|
||||
|
||||
signal_filter: str | list[str] | None = None
|
||||
default: str | None = None
|
||||
arg_name: str | None = None
|
||||
device: str | None = None
|
||||
signals: list[str] | None = None
|
||||
|
||||
|
||||
class DeviceSignalInputBase(BECWidget):
|
||||
"""
|
||||
Mixin base class for device signal input widgets.
|
||||
Mixin class for device signal input widgets. This class provides methods to get the device signal list and device
|
||||
signal object based on the current text of the widget.
|
||||
"""
|
||||
|
||||
_filter_handler = {
|
||||
BECSignalFilter.HINTED: "include_hinted_signals",
|
||||
BECSignalFilter.NORMAL: "include_normal_signals",
|
||||
BECSignalFilter.CONFIG: "include_config_signals",
|
||||
}
|
||||
|
||||
def __init__(self, client=None, config=None, gui_id: str = None):
|
||||
if config is None:
|
||||
config = DeviceSignalInputBaseConfig(widget_class=self.__class__.__name__)
|
||||
else:
|
||||
if isinstance(config, dict):
|
||||
config = DeviceSignalInputBaseConfig(**config)
|
||||
self.config = config
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
|
||||
self._device = None
|
||||
self.get_bec_shortcuts()
|
||||
self._signal_filter = []
|
||||
self._signals = []
|
||||
self._hinted_signals = []
|
||||
self._normal_signals = []
|
||||
self._config_signals = []
|
||||
|
||||
### Qt Slots ###
|
||||
|
||||
@Slot(str)
|
||||
def set_signal(self, signal: str):
|
||||
"""
|
||||
Set the signal.
|
||||
|
||||
Args:
|
||||
signal (str): signal name.
|
||||
"""
|
||||
if self.validate_signal(signal, raise_on_false=False) is True:
|
||||
WidgetIO.set_value(widget=self, value=signal)
|
||||
self.config.default = signal
|
||||
else:
|
||||
logger.warning(
|
||||
f"Signal {signal} not found for device {self.device} and filtered selection {self.signal_filter}."
|
||||
)
|
||||
|
||||
@Slot(str)
|
||||
def set_device(self, device: str | None):
|
||||
"""
|
||||
Set the device.
|
||||
|
||||
Args:
|
||||
device(str): device name.
|
||||
"""
|
||||
if device is None:
|
||||
self._device = None
|
||||
if self.validate_device(device, raise_on_false=False) is True:
|
||||
self._device = device
|
||||
self.update_signals_from_filters()
|
||||
else:
|
||||
logger.warning(f"Device {device} not found in device_manager.")
|
||||
|
||||
@Slot()
|
||||
def update_signals_from_filters(self):
|
||||
"""Update the filters for the device signals based on list in self.signal_filter.
|
||||
In addition, store the hinted, normal and config signals in separate lists to allow
|
||||
customisation within QLineEdit."""
|
||||
self.config.signal_filter = self.signal_filter
|
||||
# pylint: disable=protected-access
|
||||
self._hinted_signals = []
|
||||
self._normal_signals = []
|
||||
self._config_signals = []
|
||||
if self._device is None:
|
||||
return
|
||||
device = self.get_device_object(self._device)
|
||||
device_info = device._info["signals"]
|
||||
if BECSignalFilter.HINTED in self.signal_filter or len(self.signal_filter) == 0:
|
||||
hinted_signals = [
|
||||
signal
|
||||
for signal, signal_info in device_info.items()
|
||||
if (signal_info.get("kind_str", None) == BECSignalFilter.HINTED)
|
||||
]
|
||||
self._hinted_signals = hinted_signals
|
||||
if BECSignalFilter.NORMAL in self.signal_filter or len(self.signal_filter) == 0:
|
||||
normal_signals = [
|
||||
signal
|
||||
for signal, signal_info in device_info.items()
|
||||
if (signal_info.get("kind_str", None) == BECSignalFilter.NORMAL)
|
||||
]
|
||||
self._normal_signals = normal_signals
|
||||
if BECSignalFilter.CONFIG in self.signal_filter or len(self.signal_filter) == 0:
|
||||
config_signals = [
|
||||
signal
|
||||
for signal, signal_info in device_info.items()
|
||||
if (signal_info.get("kind_str", None) == BECSignalFilter.CONFIG)
|
||||
]
|
||||
self._config_signals = config_signals
|
||||
self._signals = self._hinted_signals + self._normal_signals + self._config_signals
|
||||
FilterIO.set_selection(widget=self, selection=self.signals)
|
||||
|
||||
### Qt Properties ###
|
||||
|
||||
@Property(str)
|
||||
def device(self) -> str:
|
||||
"""Get the selected device."""
|
||||
if self._device is None:
|
||||
return ""
|
||||
return self._device
|
||||
|
||||
@device.setter
|
||||
def device(self, value: str):
|
||||
"""Set the device and update the filters, only allow devices present in the devicemanager."""
|
||||
if self.validate_device(value) is False:
|
||||
return
|
||||
self._device = value
|
||||
self.config.device = value
|
||||
self.update_signals_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_hinted_signals(self):
|
||||
"""Include hinted signals in filters."""
|
||||
return BECSignalFilter.HINTED in self.signal_filter
|
||||
|
||||
@include_hinted_signals.setter
|
||||
def include_hinted_signals(self, value: bool):
|
||||
if value:
|
||||
self._signal_filter.append(BECSignalFilter.HINTED)
|
||||
else:
|
||||
self._signal_filter.remove(BECSignalFilter.HINTED)
|
||||
self.update_signals_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_normal_signals(self):
|
||||
"""Include normal signals in filters."""
|
||||
return BECSignalFilter.NORMAL in self.signal_filter
|
||||
|
||||
@include_normal_signals.setter
|
||||
def include_normal_signals(self, value: bool):
|
||||
if value:
|
||||
self._signal_filter.append(BECSignalFilter.NORMAL)
|
||||
else:
|
||||
self._signal_filter.remove(BECSignalFilter.NORMAL)
|
||||
self.update_signals_from_filters()
|
||||
|
||||
@Property(bool)
|
||||
def include_config_signals(self):
|
||||
"""Include config signals in filters."""
|
||||
return BECSignalFilter.CONFIG in self.signal_filter
|
||||
|
||||
@include_config_signals.setter
|
||||
def include_config_signals(self, value: bool):
|
||||
if value:
|
||||
self._signal_filter.append(BECSignalFilter.CONFIG)
|
||||
else:
|
||||
self._signal_filter.remove(BECSignalFilter.CONFIG)
|
||||
self.update_signals_from_filters()
|
||||
|
||||
### Properties and Methods ###
|
||||
|
||||
@property
|
||||
def signals(self) -> list[str]:
|
||||
"""
|
||||
Get the list of device signals for the applied filters.
|
||||
|
||||
Returns:
|
||||
list[str]: List of device signals.
|
||||
"""
|
||||
return self._signals
|
||||
|
||||
@signals.setter
|
||||
def signals(self, value: list[str]):
|
||||
self._signals = value
|
||||
self.config.signals = value
|
||||
FilterIO.set_selection(widget=self, selection=value)
|
||||
|
||||
@property
|
||||
def signal_filter(self) -> list[str]:
|
||||
"""Get the list of filters to apply on the device signals."""
|
||||
return self._signal_filter
|
||||
|
||||
def get_available_filters(self) -> list[str]:
|
||||
"""Get the available filters."""
|
||||
return [entry for entry in self._filter_handler]
|
||||
|
||||
def set_filter(self, filter_selection: str | list[str]):
|
||||
"""
|
||||
Set the device filter. If None, all devices are included.
|
||||
|
||||
Args:
|
||||
filter_selection (str | list[str]): Device filters from BECDeviceFilter and BECReadoutPriority.
|
||||
"""
|
||||
filters = None
|
||||
if isinstance(filter_selection, list):
|
||||
filters = [self._filter_handler.get(entry) for entry in filter_selection]
|
||||
if isinstance(filter_selection, str):
|
||||
filters = [self._filter_handler.get(filter_selection)]
|
||||
if filters is None:
|
||||
return
|
||||
for entry in filters:
|
||||
setattr(self, entry, True)
|
||||
|
||||
def get_device_object(self, device: str) -> object:
|
||||
"""
|
||||
Get the device object based on the device name.
|
||||
|
||||
Args:
|
||||
device(str): Device name.
|
||||
|
||||
Returns:
|
||||
object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
|
||||
"""
|
||||
self.validate_device(device)
|
||||
dev = getattr(self.dev, device.lower(), None)
|
||||
if dev is None:
|
||||
raise ValueError(f"Device {device} is not found in devicemanager {self.dev}.")
|
||||
return dev
|
||||
|
||||
def validate_device(self, device: str, raise_on_false: bool = False) -> bool:
|
||||
"""
|
||||
Validate the device if it is present in current BEC instance.
|
||||
|
||||
Args:
|
||||
device(str): Device to validate.
|
||||
"""
|
||||
if device in self.dev:
|
||||
return True
|
||||
if raise_on_false is True:
|
||||
raise ValueError(f"Device {device} not found in devicemanager.")
|
||||
return False
|
||||
|
||||
def validate_signal(self, signal: str, raise_on_false: bool = False) -> bool:
|
||||
"""
|
||||
Validate the signal if it is present in the device signals.
|
||||
|
||||
Args:
|
||||
signal(str): Signal to validate.
|
||||
"""
|
||||
if signal in self.signals:
|
||||
return True
|
||||
if raise_on_false is True:
|
||||
raise ValueError(
|
||||
f"Signal {signal} not found for device {self.device} and filtered selection {self.signal_filter}."
|
||||
)
|
||||
return False
|
@ -27,7 +27,7 @@ class DapComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return "BEC Selection Widgets"
|
||||
return "BEC Input Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(DapComboBox.ICON_NAME)
|
||||
|
@ -31,7 +31,7 @@ class DeviceComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return "Device Control"
|
||||
return "BEC Input Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(DeviceComboBox.ICON_NAME)
|
||||
|
@ -1,23 +1,25 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtWidgets import QComboBox, QSizePolicy
|
||||
|
||||
from qtpy.QtWidgets import QComboBox
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputConfig
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.base_classes.device_input_base import (
|
||||
BECDeviceFilter,
|
||||
DeviceInputBase,
|
||||
DeviceInputConfig,
|
||||
)
|
||||
|
||||
|
||||
class DeviceComboBox(DeviceInputBase, QComboBox):
|
||||
"""
|
||||
Line edit widget for device input with autocomplete for device names.
|
||||
Combobox widget for device input with autocomplete for device names.
|
||||
|
||||
Args:
|
||||
parent: Parent widget.
|
||||
client: BEC client object.
|
||||
config: Device input configuration.
|
||||
gui_id: GUI ID.
|
||||
device_filter: Device filter, name of the device class.
|
||||
device_filter: Device filter, name of the device class from BECDeviceFilter and BECReadoutPriority. Check DeviceInputBase for more details.
|
||||
default: Default device name.
|
||||
arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names.
|
||||
"""
|
||||
@ -30,57 +32,68 @@ class DeviceComboBox(DeviceInputBase, QComboBox):
|
||||
client=None,
|
||||
config: DeviceInputConfig = None,
|
||||
gui_id: str | None = None,
|
||||
device_filter: str | None = None,
|
||||
device_filter: BECDeviceFilter | list[BECDeviceFilter] | None = None,
|
||||
readout_priority_filter: (
|
||||
str | ReadoutPriority | list[str] | list[ReadoutPriority] | None
|
||||
) = None,
|
||||
device_list: list[str] | None = None,
|
||||
default: str | None = None,
|
||||
arg_name: str | None = None,
|
||||
):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QComboBox.__init__(self, parent=parent)
|
||||
self.setMinimumSize(125, 26)
|
||||
self.populate_combobox()
|
||||
|
||||
if arg_name is not None:
|
||||
self.config.arg_name = arg_name
|
||||
self.arg_name = arg_name
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
self.setMinimumSize(QSize(100, 0))
|
||||
self._is_valid_input = False
|
||||
self._accent_colors = get_accent_colors()
|
||||
# Set readout priority filter and device filter.
|
||||
# If value is set directly in init, this overrules value from the config
|
||||
readout_priority_filter = (
|
||||
readout_priority_filter
|
||||
if readout_priority_filter is not None
|
||||
else self.config.readout_filter
|
||||
)
|
||||
if readout_priority_filter is not None:
|
||||
self.set_readout_priority_filter(readout_priority_filter)
|
||||
device_filter = device_filter if device_filter is not None else self.config.device_filter
|
||||
if device_filter is not None:
|
||||
self.set_device_filter(device_filter)
|
||||
device_list = device_list if device_list is not None else self.config.devices
|
||||
if device_list is not None:
|
||||
self.set_available_devices(device_list)
|
||||
else:
|
||||
self.update_devices_from_filters()
|
||||
default = default if default is not None else self.config.default
|
||||
if default is not None:
|
||||
self.set_default_device(default)
|
||||
self.set_device(default)
|
||||
|
||||
def set_device_filter(self, device_filter: str):
|
||||
def get_current_device(self) -> object:
|
||||
"""
|
||||
Set the device filter.
|
||||
|
||||
Args:
|
||||
device_filter(str): Device filter, name of the device class.
|
||||
"""
|
||||
super().set_device_filter(device_filter)
|
||||
self.populate_combobox()
|
||||
|
||||
def set_default_device(self, default_device: str):
|
||||
"""
|
||||
Set the default device.
|
||||
|
||||
Args:
|
||||
default_device(str): Default device name.
|
||||
"""
|
||||
super().set_default_device(default_device)
|
||||
self.setCurrentText(default_device)
|
||||
|
||||
def populate_combobox(self):
|
||||
"""Populate the combobox with the devices."""
|
||||
self.devices = self.get_device_list(self.config.device_filter)
|
||||
self.clear()
|
||||
self.addItems(self.devices)
|
||||
|
||||
def get_device(self) -> object:
|
||||
"""
|
||||
Get the selected device object.
|
||||
Get the current device object based on the current value.
|
||||
|
||||
Returns:
|
||||
object: Device object.
|
||||
object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
|
||||
"""
|
||||
device_name = self.currentText()
|
||||
device_obj = getattr(self.dev, device_name.lower(), None)
|
||||
if device_obj is None:
|
||||
raise ValueError(f"Device {device_name} is not found.")
|
||||
return device_obj
|
||||
dev_name = self.currentText()
|
||||
return self.get_device_object(dev_name)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
|
||||
app = QApplication([])
|
||||
set_theme("dark")
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(200, 200)
|
||||
layout = QVBoxLayout()
|
||||
widget.setLayout(layout)
|
||||
combo = DeviceComboBox()
|
||||
layout.addWidget(combo)
|
||||
widget.show()
|
||||
app.exec_()
|
||||
|
@ -1,13 +1,14 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from qtpy.QtCore import QSize, Signal, Slot
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QPainter, QPaintEvent, QPen
|
||||
from qtpy.QtWidgets import QCompleter, QLineEdit, QSizePolicy
|
||||
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase, DeviceInputConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputConfig
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.base_classes.device_input_base import (
|
||||
BECDeviceFilter,
|
||||
DeviceInputBase,
|
||||
DeviceInputConfig,
|
||||
)
|
||||
|
||||
|
||||
class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
||||
@ -19,7 +20,7 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
||||
client: BEC client object.
|
||||
config: Device input configuration.
|
||||
gui_id: GUI ID.
|
||||
device_filter: Device filter, name of the device class.
|
||||
device_filter: Device filter, name of the device class from BECDeviceFilter and ReadoutPriority. Check DeviceInputBase for more details.
|
||||
default: Default device name.
|
||||
arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names.
|
||||
"""
|
||||
@ -34,80 +35,99 @@ class DeviceLineEdit(DeviceInputBase, QLineEdit):
|
||||
client=None,
|
||||
config: DeviceInputConfig = None,
|
||||
gui_id: str | None = None,
|
||||
device_filter: str | list[str] | None = None,
|
||||
device_filter: BECDeviceFilter | list[BECDeviceFilter] | None = None,
|
||||
readout_priority_filter: (
|
||||
str | ReadoutPriority | list[str] | list[ReadoutPriority] | None
|
||||
) = None,
|
||||
device_list: list[str] | None = None,
|
||||
default: str | None = None,
|
||||
arg_name: str | None = None,
|
||||
):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QLineEdit.__init__(self, parent=parent)
|
||||
|
||||
self._is_valid_input = False
|
||||
self.completer = QCompleter(self)
|
||||
self.setCompleter(self.completer)
|
||||
self.populate_completer()
|
||||
|
||||
if arg_name is not None:
|
||||
self.config.arg_name = arg_name
|
||||
self.arg_name = arg_name
|
||||
if device_filter is not None:
|
||||
self.set_device_filter(device_filter)
|
||||
if default is not None:
|
||||
self.set_default_device(default)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
self.setMinimumSize(QSize(100, 0))
|
||||
self._accent_colors = get_accent_colors()
|
||||
# Set readout priority filter and device filter.
|
||||
# If value is set directly in init, this overrules value from the config
|
||||
readout_priority_filter = (
|
||||
readout_priority_filter
|
||||
if readout_priority_filter is not None
|
||||
else self.config.readout_filter
|
||||
)
|
||||
if readout_priority_filter is not None:
|
||||
self.set_readout_priority_filter(readout_priority_filter)
|
||||
device_filter = device_filter if device_filter is not None else self.config.device_filter
|
||||
if device_filter is not None:
|
||||
self.set_device_filter(device_filter)
|
||||
device_list = device_list if device_list is not None else self.config.devices
|
||||
if device_list is not None:
|
||||
self.set_available_devices(device_list)
|
||||
else:
|
||||
self.update_devices_from_filters()
|
||||
default = default if default is not None else self.config.default
|
||||
if default is not None:
|
||||
self.set_device(default)
|
||||
self.textChanged.connect(self.check_validity)
|
||||
|
||||
self.editingFinished.connect(self.emit_device_selected)
|
||||
|
||||
@Slot()
|
||||
def emit_device_selected(self):
|
||||
def get_current_device(self) -> object:
|
||||
"""
|
||||
Editing finished, let's see which device is selected and emit signal
|
||||
"""
|
||||
device_name = self.text().lower()
|
||||
device_obj = getattr(self.dev, device_name, None)
|
||||
if device_obj is not None:
|
||||
self.device_selected.emit(device_name)
|
||||
|
||||
def set_device_filter(self, device_filter: str | list[str]):
|
||||
"""
|
||||
Set the device filter.
|
||||
|
||||
Args:
|
||||
device_filter (str | list[str]): Device filter, name of the device class.
|
||||
"""
|
||||
super().set_device_filter(device_filter)
|
||||
self.populate_completer()
|
||||
|
||||
def set_default_device(self, default_device: str):
|
||||
"""
|
||||
Set the default device.
|
||||
|
||||
Args:
|
||||
default_device (str): Default device name.
|
||||
"""
|
||||
super().set_default_device(default_device)
|
||||
self.setText(default_device)
|
||||
|
||||
def populate_completer(self):
|
||||
"""Populate the completer with the devices."""
|
||||
self.devices = self.get_device_list(self.config.device_filter)
|
||||
self.completer.setModel(self.create_completer_model(self.devices))
|
||||
|
||||
def create_completer_model(self, devices: list[str]):
|
||||
"""Create a model for the completer."""
|
||||
from qtpy.QtCore import QStringListModel
|
||||
|
||||
return QStringListModel(devices, self)
|
||||
|
||||
def get_device(self) -> object:
|
||||
"""
|
||||
Get the selected device object.
|
||||
Get the current device object based on the current value.
|
||||
|
||||
Returns:
|
||||
object: Device object.
|
||||
object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
|
||||
"""
|
||||
device_name = self.text()
|
||||
device_obj = getattr(self.dev, device_name.lower(), None)
|
||||
if device_obj is None:
|
||||
raise ValueError(f"Device {device_name} is not found.")
|
||||
return device_obj
|
||||
dev_name = self.text()
|
||||
return self.get_device_object(dev_name)
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Extend the paint event to set the border color based on the validity of the input.
|
||||
|
||||
Args:
|
||||
event (PySide6.QtGui.QPaintEvent) : Paint event.
|
||||
"""
|
||||
super().paintEvent(event)
|
||||
painter = QPainter(self)
|
||||
pen = QPen()
|
||||
pen.setWidth(2)
|
||||
|
||||
if self._is_valid_input is False and self.isEnabled() is True:
|
||||
pen.setColor(self._accent_colors.emergency)
|
||||
painter.setPen(pen)
|
||||
painter.drawRect(self.rect().adjusted(1, 1, -1, -1))
|
||||
|
||||
def check_validity(self, input_text: str) -> None:
|
||||
"""
|
||||
Check if the current value is a valid device name.
|
||||
"""
|
||||
if self.validate_device(input_text) is True:
|
||||
self._is_valid_input = True
|
||||
self.device_selected.emit(input_text.lower())
|
||||
else:
|
||||
self._is_valid_input = False
|
||||
self.update()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
|
||||
app = QApplication([])
|
||||
set_theme("dark")
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(200, 200)
|
||||
layout = QVBoxLayout()
|
||||
widget.setLayout(layout)
|
||||
line_edit = DeviceLineEdit()
|
||||
line_edit.include_positioner = True
|
||||
layout.addWidget(line_edit)
|
||||
widget.show()
|
||||
app.exec_()
|
||||
|
@ -31,7 +31,7 @@ class DeviceLineEditPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return "Device Control"
|
||||
return "BEC Input Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(DeviceLineEdit.ICON_NAME)
|
||||
|
@ -16,6 +16,7 @@ from bec_widgets.qt_utils.toolbar import (
|
||||
WidgetAction,
|
||||
)
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings
|
||||
@ -69,7 +70,7 @@ class BECImageWidget(BECWidget, QWidget):
|
||||
self.toolbar = ModularToolBar(
|
||||
actions={
|
||||
"monitor": DeviceSelectionAction(
|
||||
"Monitor:", DeviceComboBox(device_filter="Device")
|
||||
"Monitor:", DeviceComboBox(device_filter=BECDeviceFilter.DEVICE)
|
||||
),
|
||||
"monitor_type": WidgetAction(widget=self.dim_combo_box),
|
||||
"connect": MaterialIconAction(icon_name="link", tooltip="Connect Device"),
|
||||
|
@ -2,11 +2,13 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from qtpy.QtWidgets import QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.qt_utils.settings_dialog import SettingsDialog
|
||||
from bec_widgets.qt_utils.toolbar import DeviceSelectionAction, MaterialIconAction, ModularToolBar
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
from bec_widgets.widgets.figure.plots.motor_map.motor_map import MotorMapConfig
|
||||
@ -50,10 +52,10 @@ class BECMotorMapWidget(BECWidget, QWidget):
|
||||
self.toolbar = ModularToolBar(
|
||||
actions={
|
||||
"motor_x": DeviceSelectionAction(
|
||||
"Motor X:", DeviceComboBox(device_filter="Positioner")
|
||||
"Motor X:", DeviceComboBox(device_filter=[BECDeviceFilter.POSITIONER])
|
||||
),
|
||||
"motor_y": DeviceSelectionAction(
|
||||
"Motor Y:", DeviceComboBox(device_filter="Positioner")
|
||||
"Motor Y:", DeviceComboBox(device_filter=[BECDeviceFilter.POSITIONER])
|
||||
),
|
||||
"connect": MaterialIconAction(icon_name="link", tooltip="Connect Motors"),
|
||||
"history": MaterialIconAction(icon_name="history", tooltip="Reset Trace History"),
|
||||
|
@ -18,6 +18,7 @@ from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.colors import get_accent_colors, set_theme
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
|
||||
logger = bec_logger.logger
|
||||
@ -97,7 +98,9 @@ class PositionerBox(BECWidget, CompactPopupWidget):
|
||||
self._dialog = QDialog(self)
|
||||
self._dialog.setWindowTitle("Positioner Selection")
|
||||
layout = QVBoxLayout()
|
||||
line_edit = DeviceLineEdit(self, client=self.client, device_filter="Positioner")
|
||||
line_edit = DeviceLineEdit(
|
||||
self, client=self.client, device_filter=[BECDeviceFilter.POSITIONER]
|
||||
)
|
||||
line_edit.textChanged.connect(self.set_positioner)
|
||||
layout.addWidget(line_edit)
|
||||
close_button = QPushButton("Close")
|
||||
|
@ -306,7 +306,7 @@ class ScanGroupBox(QGroupBox):
|
||||
try: # In case that the bundle size changes
|
||||
widget = self.layout.itemAtPosition(i, j).widget()
|
||||
if isinstance(widget, DeviceLineEdit) and device_object:
|
||||
value = widget.get_device()
|
||||
value = widget.get_current_device()
|
||||
else:
|
||||
value = WidgetIO.get_value(widget)
|
||||
args.append(value)
|
||||
|
0
bec_widgets/widgets/signal_combobox/__init__.py
Normal file
0
bec_widgets/widgets/signal_combobox/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
def main(): # pragma: no cover
|
||||
from qtpy import PYSIDE6
|
||||
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.signal_combobox.signal_combobox_plugin import SignalComboBoxPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(SignalComboBoxPlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
90
bec_widgets/widgets/signal_combobox/signal_combobox.py
Normal file
90
bec_widgets/widgets/signal_combobox/signal_combobox.py
Normal file
@ -0,0 +1,90 @@
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtWidgets import QComboBox, QSizePolicy
|
||||
|
||||
from bec_widgets.utils.filter_io import ComboBoxFilterHandler, FilterIO
|
||||
from bec_widgets.widgets.base_classes.device_signal_input_base import DeviceSignalInputBase
|
||||
|
||||
|
||||
class SignalComboBox(DeviceSignalInputBase, QComboBox):
|
||||
"""
|
||||
Line edit widget for device input with autocomplete for device names.
|
||||
|
||||
Args:
|
||||
parent: Parent widget.
|
||||
client: BEC client object.
|
||||
config: Device input configuration.
|
||||
gui_id: GUI ID.
|
||||
device_filter: Device filter, name of the device class from BECDeviceFilter and BECReadoutPriority. Check DeviceInputBase for more details.
|
||||
default: Default device name.
|
||||
arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names.
|
||||
"""
|
||||
|
||||
ICON_NAME = "list_alt"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
client=None,
|
||||
config: DeviceSignalInputBase = None,
|
||||
gui_id: str | None = None,
|
||||
device: str | None = None,
|
||||
signal_filter: str | list[str] | None = None,
|
||||
default: str | None = None,
|
||||
arg_name: str | None = None,
|
||||
):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QComboBox.__init__(self, parent=parent)
|
||||
if arg_name is not None:
|
||||
self.config.arg_name = arg_name
|
||||
self.arg_name = arg_name
|
||||
if default is not None:
|
||||
self.set_device(default)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
self.setMinimumSize(QSize(100, 0))
|
||||
signal_filter = signal_filter if not None else self.config.signal_filter
|
||||
if signal_filter is not None:
|
||||
self.set_filter(signal_filter)
|
||||
device = device if not None else self.config.device
|
||||
if device is not None:
|
||||
self.set_device(device)
|
||||
default = default if not None else self.config.default
|
||||
if default is not None:
|
||||
self.set_signal(default)
|
||||
|
||||
def update_signals_from_filters(self):
|
||||
"""Update the filters for the combobox"""
|
||||
super().update_signals_from_filters()
|
||||
# pylint: disable=protected-access
|
||||
if FilterIO._find_handler(self) is ComboBoxFilterHandler:
|
||||
if len(self._config_signals) > 0:
|
||||
self.insertItem(
|
||||
len(self._hinted_signals) + len(self._normal_signals), "Config Signals"
|
||||
)
|
||||
self.model().item(len(self._hinted_signals) + len(self._normal_signals)).setEnabled(
|
||||
False
|
||||
)
|
||||
if len(self._normal_signals) > 0:
|
||||
self.insertItem(len(self._hinted_signals), "Normal Signals")
|
||||
self.model().item(len(self._hinted_signals)).setEnabled(False)
|
||||
if len(self._hinted_signals) > 0:
|
||||
self.insertItem(0, "Hinted Signals")
|
||||
self.model().item(0).setEnabled(False)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
|
||||
app = QApplication([])
|
||||
set_theme("dark")
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(200, 200)
|
||||
layout = QVBoxLayout()
|
||||
widget.setLayout(layout)
|
||||
box = SignalComboBox(device="samx")
|
||||
layout.addWidget(box)
|
||||
widget.show()
|
||||
app.exec_()
|
@ -0,0 +1 @@
|
||||
{'files': ['signal_combobox.py']}
|
@ -0,0 +1,54 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.signal_combobox.signal_combobox import SignalComboBox
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='SignalComboBox' name='signal_combobox'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class SignalComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
t = SignalComboBox(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return "BEC Input Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(SignalComboBox.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "signal_combobox"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "SignalComboBox"
|
||||
|
||||
def toolTip(self):
|
||||
return ""
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
0
bec_widgets/widgets/signal_line_edit/__init__.py
Normal file
0
bec_widgets/widgets/signal_line_edit/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
def main(): # pragma: no cover
|
||||
from qtpy import PYSIDE6
|
||||
|
||||
if not PYSIDE6:
|
||||
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||
return
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
from bec_widgets.widgets.signal_line_edit.signal_line_edit_plugin import SignalLineEditPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(SignalLineEditPlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
114
bec_widgets/widgets/signal_line_edit/signal_line_edit.py
Normal file
114
bec_widgets/widgets/signal_line_edit/signal_line_edit.py
Normal file
@ -0,0 +1,114 @@
|
||||
from qtpy.QtCore import QSize, Slot
|
||||
from qtpy.QtGui import QPainter, QPaintEvent, QPen
|
||||
from qtpy.QtWidgets import QCompleter, QLineEdit, QSizePolicy
|
||||
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_widgets.widgets.base_classes.device_signal_input_base import DeviceSignalInputBase
|
||||
|
||||
|
||||
class SignalLineEdit(DeviceSignalInputBase, QLineEdit):
|
||||
"""
|
||||
Line edit widget for device input with autocomplete for device names.
|
||||
|
||||
Args:
|
||||
parent: Parent widget.
|
||||
client: BEC client object.
|
||||
config: Device input configuration.
|
||||
gui_id: GUI ID.
|
||||
device_filter: Device filter, name of the device class from BECDeviceFilter and BECReadoutPriority. Check DeviceInputBase for more details.
|
||||
default: Default device name.
|
||||
arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names.
|
||||
"""
|
||||
|
||||
ICON_NAME = "vital_signs"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
client=None,
|
||||
config: DeviceSignalInputBase = None,
|
||||
gui_id: str | None = None,
|
||||
device: str | None = None,
|
||||
signal_filter: str | list[str] | None = None,
|
||||
default: str | None = None,
|
||||
arg_name: str | None = None,
|
||||
):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QLineEdit.__init__(self, parent=parent)
|
||||
self._is_valid_input = False
|
||||
self._accent_colors = get_accent_colors()
|
||||
self.completer = QCompleter(self)
|
||||
self.setCompleter(self.completer)
|
||||
if arg_name is not None:
|
||||
self.config.arg_name = arg_name
|
||||
self.arg_name = arg_name
|
||||
if default is not None:
|
||||
self.set_device(default)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
||||
self.setMinimumSize(QSize(100, 0))
|
||||
signal_filter = signal_filter if not None else self.config.signal_filter
|
||||
if signal_filter is not None:
|
||||
self.set_filter(signal_filter)
|
||||
device = device if not None else self.config.device
|
||||
if device is not None:
|
||||
self.set_device(device)
|
||||
default = default if not None else self.config.default
|
||||
if default is not None:
|
||||
self.set_signal(default)
|
||||
self.textChanged.connect(self.check_validity)
|
||||
|
||||
def get_current_device(self) -> object:
|
||||
"""
|
||||
Get the current device object based on the current value.
|
||||
|
||||
Returns:
|
||||
object: Device object, can be device of type Device, Positioner, Signal or ComputedSignal.
|
||||
"""
|
||||
dev_name = self.text()
|
||||
return self.get_device_object(dev_name)
|
||||
|
||||
def paintEvent(self, event: QPaintEvent) -> None:
|
||||
"""Extend the paint event to set the border color based on the validity of the input.
|
||||
|
||||
Args:
|
||||
event (PySide6.QtGui.QPaintEvent) : Paint event.
|
||||
"""
|
||||
super().paintEvent(event)
|
||||
painter = QPainter(self)
|
||||
pen = QPen()
|
||||
pen.setWidth(2)
|
||||
|
||||
if self._is_valid_input is False and self.isEnabled() is True:
|
||||
pen.setColor(self._accent_colors.emergency)
|
||||
painter.setPen(pen)
|
||||
painter.drawRect(self.rect().adjusted(1, 1, -1, -1))
|
||||
|
||||
@Slot(str)
|
||||
def check_validity(self, input_text: str) -> None:
|
||||
"""
|
||||
Check if the current value is a valid device name.
|
||||
"""
|
||||
# i
|
||||
if self.validate_signal(input_text) is True:
|
||||
self._is_valid_input = True
|
||||
else:
|
||||
self._is_valid_input = False
|
||||
self.update()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils.colors import set_theme
|
||||
|
||||
app = QApplication([])
|
||||
set_theme("dark")
|
||||
widget = QWidget()
|
||||
widget.setFixedSize(200, 200)
|
||||
layout = QVBoxLayout()
|
||||
widget.setLayout(layout)
|
||||
layout.addWidget(SignalLineEdit(device="samx"))
|
||||
widget.show()
|
||||
app.exec_()
|
@ -0,0 +1 @@
|
||||
{'files': ['signal_line_edit.py']}
|
@ -0,0 +1,54 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from bec_widgets.widgets.signal_line_edit.signal_line_edit import SignalLineEdit
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='SignalLineEdit' name='signal_line_edit'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class SignalLineEditPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
t = SignalLineEdit(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return "BEC Input Widgets"
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(SignalLineEdit.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "signal_line_edit"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "SignalLineEdit"
|
||||
|
||||
def toolTip(self):
|
||||
return ""
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
Binary file not shown.
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/widget_screenshots/signal_inputs.png
Normal file
BIN
docs/assets/widget_screenshots/signal_inputs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
docs/user/widgets/device_input/QProperties_DeviceInput.png
Normal file
BIN
docs/user/widgets/device_input/QProperties_DeviceInput.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
@ -13,9 +13,11 @@ The `DeviceLineEdit` widget provides a line edit interface with autocomplete fun
|
||||
The `DeviceComboBox` widget offers a dropdown interface for device selection, providing a more visual way to browse through available devices.
|
||||
|
||||
## Key Features:
|
||||
- **Device Filtering**: Both widgets allow users to filter devices by their class names, ensuring that only relevant devices are shown.
|
||||
- **Device Filtering**: Both widgets allow users to filter devices by device type and readout priority, ensuring that only relevant devices are shown.
|
||||
- **Default Device Setting**: Users can set a default device to be pre-selected when the widget is initialized.
|
||||
- **Set Device Selection**: Both widgets allow users to set the available devices to be displayed independent of the applied filters.
|
||||
- **Real-Time Autocomplete (LineEdit)**: The `DeviceLineEdit` widget supports real-time autocomplete, helping users find devices faster.
|
||||
- **Real-Time Input Validation (LineEdit)**: User input is validated in real-time with a red border around the `DeviceLineEdit` indicating an invalid input.
|
||||
- **Dropdown Selection (ComboBox)**: The `DeviceComboBox` widget displays devices in a dropdown list, making selection straightforward.
|
||||
- **QtDesigner Integration**: Both widgets can be added as custom widgets in `QtDesigner` or instantiated directly in code.
|
||||
|
||||
@ -28,11 +30,15 @@ Both `DeviceLineEdit` and `DeviceComboBox` can be integrated within a GUI applic
|
||||
|
||||
## Example 1 - Creating a DeviceLineEdit in Code
|
||||
|
||||
In this example, we demonstrate how to create a `DeviceLineEdit` widget in code and customize its behavior.
|
||||
In this example, we demonstrate how to create a `DeviceLineEdit` widget in code and customize its behavior.
|
||||
We filter down to Positioners with readout_priority Baseline.
|
||||
Note, if we do not specify a device_filter or readout_filter, all enabled devices will be included.
|
||||
|
||||
```python
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
from bec_widgets.widgets.device_line_edit import DeviceLineEdit
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
|
||||
class MyGui(QWidget):
|
||||
def __init__(self):
|
||||
@ -40,7 +46,7 @@ class MyGui(QWidget):
|
||||
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
||||
|
||||
# Create and add the DeviceLineEdit to the layout
|
||||
self.device_line_edit = DeviceLineEdit(device_filter="Motor")
|
||||
self.device_line_edit = DeviceLineEdit(device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE)
|
||||
self.layout().addWidget(self.device_line_edit)
|
||||
|
||||
# Example of how this custom GUI might be used:
|
||||
@ -56,7 +62,9 @@ Similarly, here is an example of creating a `DeviceComboBox` widget in code and
|
||||
|
||||
```python
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
from bec_widgets.widgets.device_combo_box import DeviceComboBox
|
||||
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
|
||||
class MyGui(QWidget):
|
||||
def __init__(self):
|
||||
@ -64,8 +72,8 @@ class MyGui(QWidget):
|
||||
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
||||
|
||||
# Create and add the DeviceComboBox to the layout
|
||||
self.device_combo_box = DeviceComboBox(device_filter="Motor")
|
||||
self.layout().addWidget(self.device_combo_box)
|
||||
self.device_combobox = DeviceComboBox(device_filter=BECDeviceFilter.POSITIONER, readout_priority_filter=ReadoutPriority.BASELINE)
|
||||
self.layout().addWidget(self.device_combobox)
|
||||
|
||||
# Example of how this custom GUI might be used:
|
||||
app = QApplication([])
|
||||
@ -80,11 +88,24 @@ Both `DeviceLineEdit` and `DeviceComboBox` allow you to set a default device tha
|
||||
|
||||
```python
|
||||
# Set default device for DeviceLineEdit
|
||||
self.device_line_edit.set_default_device("motor1")
|
||||
self.device_line_edit.set_device("motor1")
|
||||
|
||||
# Set default device for DeviceComboBox
|
||||
self.device_combo_box.set_default_device("motor2")
|
||||
self.device_combo_box.set_device("motor2")
|
||||
|
||||
# Set the available devices to be displayed independent of the applied filters
|
||||
self.device_combo_box.set_available_devices(["motor1", "motor2", "motor3"])
|
||||
```
|
||||
````
|
||||
````{tab} BEC Designer
|
||||
Both widgets are also available as plugins for the BEC Designer. We have included Qt properties for both widgets, allowing customization of filtering and default device settings directly from the designer. In addition to the common signals and slots for `DeviceLineEdit` and `DeviceComboBox`, the following slots are available:
|
||||
- `set_device(str)` to set the default device
|
||||
- `update_devices()` to refresh the devices list
|
||||
|
||||
The following Qt properties are also included:
|
||||
```{figure} ./QProperties_DeviceInput.png
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
````{tab} API - ComboBox
|
||||
|
114
docs/user/widgets/signal_input/signal_input.md
Normal file
114
docs/user/widgets/signal_input/signal_input.md
Normal file
@ -0,0 +1,114 @@
|
||||
(user.widgets.signal_input)=
|
||||
|
||||
# Signal Input Widgets
|
||||
|
||||
````{tab} Overview
|
||||
The `Signal Input Widgets` consist of two primary widgets: `SignalLineEdit` and `SignalComboBox`. Both widgets are designed to facilitate the selection of the available signals for a selected device within the current BEC session. These widgets allow users to filter, search, and select signals dynamically. The widgets can either be integrated into a GUI through direct code instantiation or by using `QtDesigner`.
|
||||
|
||||
## SignalLineEdit
|
||||
The `SignalLineEdit` widget provides a line edit interface with autocomplete functionality for the available of signals associated with the selected device. This widget is ideal for users who prefer to type in the signal name directly. If no device is selected, the autocomplete will be empty. In addition, the widget will display a red border around the line edit if the input signal is invalid.
|
||||
|
||||
## SignalComboBox
|
||||
The `SignalComboBox` widget offers a dropdown interface for choosing a signal from the available signals of a device. It will further categorise the signals according to its `kind`: `hinted`, `normal` and `config`. For more information about `kind`, please check the [ophyd documentation](https://nsls-ii.github.io/ophyd/signals.html#kind). This widget is ideal for users who prefer to select signals from a list.
|
||||
|
||||
## Key Features:
|
||||
- **Signal Filtering**: Both widgets allow users to filter devices by signal types(`kind`). No selected filter will show all signals.
|
||||
- **Real-Time Autocomplete (LineEdit)**: The `SignalLineEdit` widget supports real-time autocomplete, helping users find devices faster.
|
||||
- **Real-Time Input Validation (LineEdit)**: User input is validated in real-time with a red border around the `SignalLineEdit` indicating an invalid input.
|
||||
- **Dropdown Selection (SignalComboBox)**: The `SignalComboBox` widget displays the sorted signals of the device
|
||||
- **QtDesigner Integration**: Both widgets can be added as custom widgets in `QtDesigner` or instantiated directly in code.
|
||||
|
||||
````
|
||||
|
||||
````{tab} Examples
|
||||
|
||||
Both `SignalLineEdit` and `SignalComboBox` can be integrated within a GUI application through direct code instantiation or by using `QtDesigner`. Below are examples demonstrating how to create and use these widgets.
|
||||
|
||||
|
||||
## Example 1 - Creating a SignalLineEdit in Code
|
||||
|
||||
In this example, we demonstrate how to create a `SignalLineEdit` widget in code and customize its behavior.
|
||||
We will select `samx`, which is a motor in the BEC simulation device config, and filter the signals to `normal` and `hinted`.
|
||||
Note, not specifying signal_filter will include all signals.
|
||||
|
||||
```python
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
from bec_widgets.widgets.signal_line_edit.signal_line_edit import SignalLineEdit
|
||||
from bec_widgets.widgets.base_classes.device_signal_input_base import BECSignalFilter
|
||||
|
||||
class MyGui(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
||||
|
||||
# Create and add the DeviceLineEdit to the layout
|
||||
self.signal_line_edit = SignalLineEdit(device="samx", signal_filter=[BECSignalFilter.NORMAL, BECSignalFilter.HINTED])
|
||||
self.layout().addWidget(self.signal_line_edit)
|
||||
|
||||
# Example of how this custom GUI might be used:
|
||||
app = QApplication([])
|
||||
my_gui = MyGui()
|
||||
my_gui.show()
|
||||
app.exec_()
|
||||
```
|
||||
|
||||
## Example 2 - Creating a DeviceComboBox in Code
|
||||
|
||||
Similarly, here is an example of creating a `DeviceComboBox` widget in code and customizing its behavior.
|
||||
|
||||
```python
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
from bec_widgets.widgets.signal_combobox.signal_combobox import SignalComboBox
|
||||
from bec_widgets.widgets.base_classes.device_signal_input_base import BECSignalFilter
|
||||
|
||||
class MyGui(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setLayout(QVBoxLayout(self)) # Initialize the layout for the widget
|
||||
|
||||
# Create and add the DeviceLineEdit to the layout
|
||||
self.signal_combobox = SignalComboBox(device="samx", signal_filter=[BECSignalFilter.NORMAL, BECSignalFilter.HINTED])
|
||||
self.layout().addWidget(self.signal_combobox)
|
||||
|
||||
# Example of how this custom GUI might be used:
|
||||
app = QApplication([])
|
||||
my_gui = MyGui()
|
||||
my_gui.show()
|
||||
app.exec_()
|
||||
```
|
||||
|
||||
## Example 3 - Setting Default Device
|
||||
|
||||
Both `SignalLineEdit` and `SignalComboBox` allow you to set a default device that will be selected when the widget is initialized.
|
||||
|
||||
```python
|
||||
# Set default device for DeviceLineEdit
|
||||
self.signal_line_edit.set_device("motor1")
|
||||
|
||||
# Set default device for DeviceComboBox
|
||||
self.signal_combobox.set_device("motor2")
|
||||
```
|
||||
````
|
||||
````{tab} BEC Designer
|
||||
Both widgets are also available as plugins for the BEC Designer. We have included Qt properties for both widgets, allowing customization of filtering and default device settings directly from the designer. In addition to the common signals and slots for `SignalLineEdit` and `SignalComboBox`, the following slots are available:
|
||||
- `set_device(str)` to set the default device
|
||||
- `set_signal(str)` to set the default signal
|
||||
- `update_signals_from_filters()` to refresh the devices list based on the current filters
|
||||
|
||||
The following Qt properties are also included:
|
||||
```{figure} ./signal_input_qproperties.png
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
````{tab} API - ComboBox
|
||||
```{eval-rst}
|
||||
.. include:: /api_reference/_autosummary/bec_widgets.cli.client.SignalComboBox.rst
|
||||
```
|
||||
````
|
||||
|
||||
````{tab} API - LineEdit
|
||||
```{eval-rst}
|
||||
.. include:: /api_reference/_autosummary/bec_widgets.cli.client.SignalLineEdit.rst
|
||||
```
|
||||
````
|
BIN
docs/user/widgets/signal_input/signal_input_qproperties.png
Normal file
BIN
docs/user/widgets/signal_input/signal_input_qproperties.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
@ -159,6 +159,14 @@ Various buttons which manage the control of the BEC Queue.
|
||||
Choose individual device from current session.
|
||||
```
|
||||
|
||||
```{grid-item-card} Signal Input Widgets
|
||||
:link: user.widgets.signal_input
|
||||
:link-type: ref
|
||||
:img-top: /assets/widget_screenshots/signal_inputs.png
|
||||
|
||||
Choose individual signals available for a selected device.
|
||||
```
|
||||
|
||||
```{grid-item-card} Text Box Widget
|
||||
:link: user.widgets.text_box
|
||||
:link-type: ref
|
||||
|
@ -3,147 +3,9 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import fakeredis
|
||||
import pytest
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.device import Positioner, ReadoutPriority
|
||||
from bec_lib.devicemanager import DeviceContainer
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
|
||||
|
||||
class FakeDevice:
|
||||
"""Fake minimal positioner class for testing."""
|
||||
|
||||
def __init__(self, name, enabled=True, readout_priority=ReadoutPriority.MONITORED):
|
||||
self.name = name
|
||||
self.enabled = enabled
|
||||
self.signals = {self.name: {"value": 1.0}}
|
||||
self.description = {self.name: {"source": self.name, "dtype": "number", "shape": []}}
|
||||
self.readout_priority = readout_priority
|
||||
self._config = {
|
||||
"readoutPriority": "baseline",
|
||||
"deviceClass": "ophyd_devices.SimPositioner",
|
||||
"deviceConfig": {
|
||||
"delay": 1,
|
||||
"limits": [-50, 50],
|
||||
"tolerance": 0.01,
|
||||
"update_frequency": 400,
|
||||
},
|
||||
"deviceTags": ["user motors"],
|
||||
"enabled": enabled,
|
||||
"readOnly": False,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
def __contains__(self, item):
|
||||
return item == self.name
|
||||
|
||||
@property
|
||||
def _hints(self):
|
||||
return [self.name]
|
||||
|
||||
def set_value(self, fake_value: float = 1.0) -> None:
|
||||
"""
|
||||
Setup fake value for device readout
|
||||
Args:
|
||||
fake_value(float): Desired fake value
|
||||
"""
|
||||
self.signals[self.name]["value"] = fake_value
|
||||
|
||||
def describe(self) -> dict:
|
||||
"""
|
||||
Get the description of the device
|
||||
Returns:
|
||||
dict: Description of the device
|
||||
"""
|
||||
return self.description
|
||||
|
||||
|
||||
class FakePositioner(FakeDevice):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
enabled=True,
|
||||
limits=None,
|
||||
read_value=1.0,
|
||||
readout_priority=ReadoutPriority.MONITORED,
|
||||
):
|
||||
super().__init__(name, enabled, readout_priority)
|
||||
self.limits = limits if limits is not None else [0, 0]
|
||||
self.read_value = read_value
|
||||
self.name = name
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
return 3
|
||||
|
||||
def set_read_value(self, value):
|
||||
self.read_value = value
|
||||
|
||||
def read(self):
|
||||
return {
|
||||
self.name: {"value": self.read_value},
|
||||
f"{self.name}_setpoint": {"value": self.read_value},
|
||||
f"{self.name}_motor_is_moving": {"value": 0},
|
||||
}
|
||||
|
||||
def set_limits(self, limits):
|
||||
self.limits = limits
|
||||
|
||||
def move(self, value, relative=False):
|
||||
"""Simulates moving the device to a new position."""
|
||||
if relative:
|
||||
self.read_value += value
|
||||
else:
|
||||
self.read_value = value
|
||||
# Respect the limits
|
||||
self.read_value = max(min(self.read_value, self.limits[1]), self.limits[0])
|
||||
|
||||
@property
|
||||
def readback(self):
|
||||
return MagicMock(get=MagicMock(return_value=self.read_value))
|
||||
|
||||
|
||||
class Positioner(FakePositioner):
|
||||
"""just placeholder for testing embedded isinstance check in DeviceCombobox"""
|
||||
|
||||
def __init__(self, name="test", limits=None, read_value=1.0):
|
||||
super().__init__(name, limits, read_value)
|
||||
|
||||
|
||||
class Device(FakeDevice):
|
||||
"""just placeholder for testing embedded isinstance check in DeviceCombobox"""
|
||||
|
||||
def __init__(self, name, enabled=True):
|
||||
super().__init__(name, enabled)
|
||||
|
||||
|
||||
class DMMock:
|
||||
def __init__(self):
|
||||
self.devices = DeviceContainer()
|
||||
|
||||
def add_devives(self, devices: list):
|
||||
for device in devices:
|
||||
self.devices[device.name] = device
|
||||
|
||||
|
||||
DEVICES = [
|
||||
FakePositioner("samx", limits=[-10, 10], read_value=2.0),
|
||||
FakePositioner("samy", limits=[-5, 5], read_value=3.0),
|
||||
FakePositioner("samz", limits=[-8, 8], read_value=4.0),
|
||||
FakePositioner("aptrx", limits=None, read_value=4.0),
|
||||
FakePositioner("aptry", limits=None, read_value=5.0),
|
||||
FakeDevice("gauss_bpm"),
|
||||
FakeDevice("gauss_adc1"),
|
||||
FakeDevice("gauss_adc2"),
|
||||
FakeDevice("gauss_adc3"),
|
||||
FakeDevice("bpm4i"),
|
||||
FakeDevice("bpm3a"),
|
||||
FakeDevice("bpm3i"),
|
||||
FakeDevice("eiger"),
|
||||
FakeDevice("waveform1d"),
|
||||
FakeDevice("async_device", readout_priority=ReadoutPriority.ASYNC),
|
||||
Positioner("test", limits=[-10, 10], read_value=2.0),
|
||||
Device("test_device"),
|
||||
]
|
||||
from bec_widgets.test_utils.client_mocks import DEVICES, DMMock, FakePositioner, Positioner
|
||||
|
||||
|
||||
def fake_redis_server(host, port):
|
||||
|
@ -3,6 +3,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pyqtgraph as pg
|
||||
import pytest
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.image.image_widget import BECImageWidget
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
@ -11,7 +12,6 @@ from .client_mocks import mocked_client
|
||||
@pytest.fixture
|
||||
def image_widget(qtbot, mocked_client):
|
||||
widget = BECImageWidget(client=mocked_client())
|
||||
widget.toolbar.widgets["monitor"].device_combobox.set_device_filter("FakeDevice")
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
@ -32,7 +32,8 @@ def test_image_widget_init(image_widget):
|
||||
assert image_widget._image is not None
|
||||
|
||||
assert (
|
||||
image_widget.toolbar.widgets["monitor"].device_combobox.config.device_filter == "FakeDevice"
|
||||
BECDeviceFilter.DEVICE
|
||||
in image_widget.toolbar.widgets["monitor"].device_combobox.config.device_filter
|
||||
)
|
||||
assert image_widget.toolbar.widgets["drag_mode"].action.isChecked() == True
|
||||
assert image_widget.toolbar.widgets["rectangle_mode"].action.isChecked() == False
|
||||
|
@ -4,8 +4,7 @@ import pytest
|
||||
|
||||
from bec_widgets.cli.client import BECFigure
|
||||
from bec_widgets.cli.client_utils import BECGuiClientMixin, _start_plot_process
|
||||
|
||||
from .client_mocks import FakeDevice
|
||||
from bec_widgets.test_utils.client_mocks import FakeDevice
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -1,13 +1,21 @@
|
||||
import pytest
|
||||
from qtpy.QtWidgets import QWidget
|
||||
from unittest import mock
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_input_base import DeviceInputBase
|
||||
import pytest
|
||||
from bec_lib.device import ReadoutPriority
|
||||
from qtpy.QtWidgets import QWidget
|
||||
from typeguard import TypeCheckError
|
||||
|
||||
from bec_widgets.test_utils.client_mocks import FakePositioner
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter, DeviceInputBase
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
from .conftest import create_widget
|
||||
|
||||
|
||||
# DeviceInputBase is meant to be mixed in a QWidget
|
||||
class DeviceInputWidget(DeviceInputBase, QWidget):
|
||||
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
||||
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QWidget.__init__(self, parent=parent)
|
||||
@ -15,13 +23,15 @@ class DeviceInputWidget(DeviceInputBase, QWidget):
|
||||
|
||||
@pytest.fixture
|
||||
def device_input_base(qtbot, mocked_client):
|
||||
widget = DeviceInputWidget(client=mocked_client)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
"""Fixture with mocked FilterIO and WidgetIO"""
|
||||
with mock.patch("bec_widgets.utils.filter_io.FilterIO.set_selection"):
|
||||
with mock.patch("bec_widgets.utils.widget_io.WidgetIO.set_value"):
|
||||
widget = create_widget(qtbot=qtbot, widget=DeviceInputWidget, client=mocked_client)
|
||||
yield widget
|
||||
|
||||
|
||||
def test_device_input_base_init(device_input_base):
|
||||
"""Test init"""
|
||||
assert device_input_base is not None
|
||||
assert device_input_base.client is not None
|
||||
assert isinstance(device_input_base, DeviceInputBase)
|
||||
@ -32,45 +42,95 @@ def test_device_input_base_init(device_input_base):
|
||||
|
||||
|
||||
def test_device_input_base_init_with_config(mocked_client):
|
||||
"""Test init with Config"""
|
||||
config = {
|
||||
"widget_class": "DeviceInputWidget",
|
||||
"gui_id": "test_gui_id",
|
||||
"device_filter": "FakePositioner",
|
||||
"device_filter": [BECDeviceFilter.POSITIONER],
|
||||
"default": "samx",
|
||||
}
|
||||
widget = DeviceInputWidget(client=mocked_client, config=config)
|
||||
assert widget.config.gui_id == "test_gui_id"
|
||||
assert widget.config.device_filter == "FakePositioner"
|
||||
assert widget.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
assert widget.config.default == "samx"
|
||||
|
||||
|
||||
def test_device_input_base_set_device_filter(device_input_base):
|
||||
device_input_base.set_device_filter("FakePositioner")
|
||||
assert device_input_base.config.device_filter == "FakePositioner"
|
||||
"""Test device filter setter."""
|
||||
device_input_base.set_device_filter(BECDeviceFilter.POSITIONER)
|
||||
assert device_input_base.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
|
||||
|
||||
def test_device_input_base_set_device_filter_error(device_input_base):
|
||||
"""Test set_device_filter with Noneexisting class"""
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
device_input_base.set_device_filter("NonExistingClass")
|
||||
assert "Device filter NonExistingClass is not in the device list." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_device_input_base_set_default_device(device_input_base):
|
||||
device_input_base.set_default_device("samx")
|
||||
"""Test setting the default device. Also tests the update_devices method."""
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
device_input_base.set_device("samx")
|
||||
assert "Device samx is not in filtered selection." in str(excinfo.value)
|
||||
device_input_base.set_device_filter(BECDeviceFilter.POSITIONER)
|
||||
device_input_base.set_readout_priority_filter(ReadoutPriority.MONITORED)
|
||||
device_input_base.set_device("samx")
|
||||
assert device_input_base.config.default == "samx"
|
||||
|
||||
|
||||
def test_device_input_base_set_default_device_error(device_input_base):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
device_input_base.set_default_device("NonExistingDevice")
|
||||
assert "Default device NonExistingDevice is not in the device list." in str(excinfo.value)
|
||||
|
||||
|
||||
def test_device_input_base_get_device_list(device_input_base):
|
||||
devices = device_input_base.get_device_list("FakePositioner")
|
||||
assert devices == ["samx", "samy", "samz", "aptrx", "aptry"]
|
||||
|
||||
|
||||
def test_device_input_base_get_filters(device_input_base):
|
||||
"""Test getting the available filters."""
|
||||
filters = device_input_base.get_available_filters()
|
||||
assert filters == {"FakePositioner", "FakeDevice", "Positioner", "Device"}
|
||||
selection = [
|
||||
BECDeviceFilter.POSITIONER,
|
||||
BECDeviceFilter.DEVICE,
|
||||
BECDeviceFilter.COMPUTED_SIGNAL,
|
||||
BECDeviceFilter.SIGNAL,
|
||||
] + [
|
||||
ReadoutPriority.MONITORED,
|
||||
ReadoutPriority.BASELINE,
|
||||
ReadoutPriority.ASYNC,
|
||||
ReadoutPriority.ON_REQUEST,
|
||||
]
|
||||
assert [entry for entry in filters if entry in selection]
|
||||
|
||||
|
||||
def test_device_input_base_properties(device_input_base):
|
||||
"""Test setting the properties of the device input base."""
|
||||
assert device_input_base.device_filter == []
|
||||
device_input_base.include_device = True
|
||||
assert device_input_base.device_filter == [BECDeviceFilter.DEVICE]
|
||||
device_input_base.include_positioner = True
|
||||
assert device_input_base.device_filter == [BECDeviceFilter.DEVICE, BECDeviceFilter.POSITIONER]
|
||||
device_input_base.include_computed_signal = True
|
||||
assert device_input_base.device_filter == [
|
||||
BECDeviceFilter.DEVICE,
|
||||
BECDeviceFilter.POSITIONER,
|
||||
BECDeviceFilter.COMPUTED_SIGNAL,
|
||||
]
|
||||
device_input_base.include_signal = True
|
||||
assert device_input_base.device_filter == [
|
||||
BECDeviceFilter.DEVICE,
|
||||
BECDeviceFilter.POSITIONER,
|
||||
BECDeviceFilter.COMPUTED_SIGNAL,
|
||||
BECDeviceFilter.SIGNAL,
|
||||
]
|
||||
assert device_input_base.readout_filter == []
|
||||
device_input_base.readout_async = True
|
||||
assert device_input_base.readout_filter == [ReadoutPriority.ASYNC]
|
||||
device_input_base.readout_baseline = True
|
||||
assert device_input_base.readout_filter == [ReadoutPriority.ASYNC, ReadoutPriority.BASELINE]
|
||||
device_input_base.readout_monitored = True
|
||||
assert device_input_base.readout_filter == [
|
||||
ReadoutPriority.ASYNC,
|
||||
ReadoutPriority.BASELINE,
|
||||
ReadoutPriority.MONITORED,
|
||||
]
|
||||
device_input_base.readout_on_request = True
|
||||
assert device_input_base.readout_filter == [
|
||||
ReadoutPriority.ASYNC,
|
||||
ReadoutPriority.BASELINE,
|
||||
ReadoutPriority.MONITORED,
|
||||
ReadoutPriority.ON_REQUEST,
|
||||
]
|
||||
|
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
from bec_lib.device import ReadoutPriority
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
|
||||
@ -19,7 +21,7 @@ def device_input_combobox_with_config(qtbot, mocked_client):
|
||||
config = {
|
||||
"widget_class": "DeviceComboBox",
|
||||
"gui_id": "test_gui_id",
|
||||
"device_filter": "FakePositioner",
|
||||
"device_filter": [BECDeviceFilter.POSITIONER],
|
||||
"default": "samx",
|
||||
"arg_name": "test_arg_name",
|
||||
}
|
||||
@ -34,7 +36,7 @@ def device_input_combobox_with_kwargs(qtbot, mocked_client):
|
||||
widget = DeviceComboBox(
|
||||
client=mocked_client,
|
||||
gui_id="test_gui_id",
|
||||
device_filter="FakePositioner",
|
||||
device_filter=[BECDeviceFilter.POSITIONER],
|
||||
default="samx",
|
||||
arg_name="test_arg_name",
|
||||
)
|
||||
@ -48,7 +50,6 @@ def test_device_input_combobox_init(device_input_combobox):
|
||||
assert device_input_combobox.client is not None
|
||||
assert isinstance(device_input_combobox, DeviceComboBox)
|
||||
assert device_input_combobox.config.widget_class == "DeviceComboBox"
|
||||
assert device_input_combobox.config.device_filter is None
|
||||
assert device_input_combobox.config.default is None
|
||||
assert device_input_combobox.devices == [
|
||||
"samx",
|
||||
@ -73,14 +74,14 @@ def test_device_input_combobox_init(device_input_combobox):
|
||||
|
||||
def test_device_input_combobox_init_with_config(device_input_combobox_with_config):
|
||||
assert device_input_combobox_with_config.config.gui_id == "test_gui_id"
|
||||
assert device_input_combobox_with_config.config.device_filter == "FakePositioner"
|
||||
assert device_input_combobox_with_config.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
assert device_input_combobox_with_config.config.default == "samx"
|
||||
assert device_input_combobox_with_config.config.arg_name == "test_arg_name"
|
||||
|
||||
|
||||
def test_device_input_combobox_init_with_kwargs(device_input_combobox_with_kwargs):
|
||||
assert device_input_combobox_with_kwargs.config.gui_id == "test_gui_id"
|
||||
assert device_input_combobox_with_kwargs.config.device_filter == "FakePositioner"
|
||||
assert device_input_combobox_with_kwargs.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
assert device_input_combobox_with_kwargs.config.default == "samx"
|
||||
assert device_input_combobox_with_kwargs.config.arg_name == "test_arg_name"
|
||||
|
||||
@ -88,7 +89,7 @@ def test_device_input_combobox_init_with_kwargs(device_input_combobox_with_kwarg
|
||||
def test_get_device_from_input_combobox_init(device_input_combobox):
|
||||
device_input_combobox.setCurrentIndex(0)
|
||||
device_text = device_input_combobox.currentText()
|
||||
current_device = device_input_combobox.get_device()
|
||||
current_device = device_input_combobox.get_current_device()
|
||||
|
||||
assert current_device.name == device_text
|
||||
|
||||
@ -106,7 +107,7 @@ def device_input_line_edit_with_config(qtbot, mocked_client):
|
||||
config = {
|
||||
"widget_class": "DeviceLineEdit",
|
||||
"gui_id": "test_gui_id",
|
||||
"device_filter": "FakePositioner",
|
||||
"device_filter": [BECDeviceFilter.POSITIONER],
|
||||
"default": "samx",
|
||||
"arg_name": "test_arg_name",
|
||||
}
|
||||
@ -121,7 +122,7 @@ def device_input_line_edit_with_kwargs(qtbot, mocked_client):
|
||||
widget = DeviceLineEdit(
|
||||
client=mocked_client,
|
||||
gui_id="test_gui_id",
|
||||
device_filter="FakePositioner",
|
||||
device_filter=[BECDeviceFilter.POSITIONER],
|
||||
default="samx",
|
||||
arg_name="test_arg_name",
|
||||
)
|
||||
@ -135,7 +136,8 @@ def test_device_input_line_edit_init(device_input_line_edit):
|
||||
assert device_input_line_edit.client is not None
|
||||
assert isinstance(device_input_line_edit, DeviceLineEdit)
|
||||
assert device_input_line_edit.config.widget_class == "DeviceLineEdit"
|
||||
assert device_input_line_edit.config.device_filter is None
|
||||
assert device_input_line_edit.config.device_filter == []
|
||||
assert device_input_line_edit.config.readout_filter == []
|
||||
assert device_input_line_edit.config.default is None
|
||||
assert device_input_line_edit.devices == [
|
||||
"samx",
|
||||
@ -160,14 +162,14 @@ def test_device_input_line_edit_init(device_input_line_edit):
|
||||
|
||||
def test_device_input_line_edit_init_with_config(device_input_line_edit_with_config):
|
||||
assert device_input_line_edit_with_config.config.gui_id == "test_gui_id"
|
||||
assert device_input_line_edit_with_config.config.device_filter == "FakePositioner"
|
||||
assert device_input_line_edit_with_config.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
assert device_input_line_edit_with_config.config.default == "samx"
|
||||
assert device_input_line_edit_with_config.config.arg_name == "test_arg_name"
|
||||
|
||||
|
||||
def test_device_input_line_edit_init_with_kwargs(device_input_line_edit_with_kwargs):
|
||||
assert device_input_line_edit_with_kwargs.config.gui_id == "test_gui_id"
|
||||
assert device_input_line_edit_with_kwargs.config.device_filter == "FakePositioner"
|
||||
assert device_input_line_edit_with_kwargs.config.device_filter == [BECDeviceFilter.POSITIONER]
|
||||
assert device_input_line_edit_with_kwargs.config.default == "samx"
|
||||
assert device_input_line_edit_with_kwargs.config.arg_name == "test_arg_name"
|
||||
|
||||
@ -175,6 +177,6 @@ def test_device_input_line_edit_init_with_kwargs(device_input_line_edit_with_kwa
|
||||
def test_get_device_from_input_line_edit_init(device_input_line_edit):
|
||||
device_input_line_edit.setText("samx")
|
||||
device_text = device_input_line_edit.text()
|
||||
current_device = device_input_line_edit.get_device()
|
||||
current_device = device_input_line_edit.get_current_device()
|
||||
|
||||
assert current_device.name == device_text
|
||||
|
103
tests/unit_tests/test_device_signal_input.py
Normal file
103
tests/unit_tests/test_device_signal_input.py
Normal file
@ -0,0 +1,103 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_signal_input_base import (
|
||||
BECSignalFilter,
|
||||
DeviceSignalInputBase,
|
||||
)
|
||||
from bec_widgets.widgets.signal_combobox.signal_combobox import SignalComboBox
|
||||
from bec_widgets.widgets.signal_line_edit.signal_line_edit import SignalLineEdit
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
from .conftest import create_widget
|
||||
|
||||
|
||||
class DeviceInputWidget(DeviceSignalInputBase, QWidget):
|
||||
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
|
||||
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QWidget.__init__(self, parent=parent)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_signal_base(qtbot, mocked_client):
|
||||
"""Fixture with mocked FilterIO and WidgetIO"""
|
||||
with mock.patch("bec_widgets.utils.filter_io.FilterIO.set_selection"):
|
||||
with mock.patch("bec_widgets.utils.widget_io.WidgetIO.set_value"):
|
||||
widget = create_widget(qtbot=qtbot, widget=DeviceInputWidget, client=mocked_client)
|
||||
yield widget
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_signal_combobox(qtbot, mocked_client):
|
||||
"""Fixture with mocked FilterIO and WidgetIO"""
|
||||
widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client)
|
||||
yield widget
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def device_signal_line_edit(qtbot, mocked_client):
|
||||
"""Fixture with mocked FilterIO and WidgetIO"""
|
||||
widget = create_widget(qtbot=qtbot, widget=SignalLineEdit, client=mocked_client)
|
||||
yield widget
|
||||
|
||||
|
||||
def test_device_signal_base_init(device_signal_base):
|
||||
"""Test if the DeviceSignalInputBase is initialized correctly"""
|
||||
assert device_signal_base._device is None
|
||||
assert device_signal_base._signal_filter == []
|
||||
assert device_signal_base._signals == []
|
||||
assert device_signal_base._hinted_signals == []
|
||||
assert device_signal_base._normal_signals == []
|
||||
assert device_signal_base._config_signals == []
|
||||
|
||||
|
||||
def test_device_signal_qproperties(device_signal_base):
|
||||
"""Test if the DeviceSignalInputBase has the correct QProperties"""
|
||||
device_signal_base.include_config_signals = True
|
||||
assert device_signal_base._signal_filter == [BECSignalFilter.CONFIG]
|
||||
device_signal_base.include_normal_signals = True
|
||||
assert device_signal_base._signal_filter == [BECSignalFilter.CONFIG, BECSignalFilter.NORMAL]
|
||||
device_signal_base.include_hinted_signals = True
|
||||
assert device_signal_base._signal_filter == [
|
||||
BECSignalFilter.CONFIG,
|
||||
BECSignalFilter.NORMAL,
|
||||
BECSignalFilter.HINTED,
|
||||
]
|
||||
|
||||
|
||||
def test_device_signal_set_device(device_signal_base):
|
||||
"""Test if the set_device method works correctly"""
|
||||
device_signal_base.include_hinted_signals = True
|
||||
device_signal_base.set_device("samx")
|
||||
assert device_signal_base.device == "samx"
|
||||
assert device_signal_base.signals == ["readback"]
|
||||
device_signal_base.include_normal_signals = True
|
||||
assert device_signal_base.signals == ["readback", "setpoint"]
|
||||
device_signal_base.include_config_signals = True
|
||||
assert device_signal_base.signals == ["readback", "setpoint", "velocity"]
|
||||
|
||||
|
||||
def test_signal_combobox(device_signal_combobox):
|
||||
"""Test the signal_combobox"""
|
||||
device_signal_combobox._signals == []
|
||||
device_signal_combobox.include_normal_signals = True
|
||||
device_signal_combobox.include_hinted_signals = True
|
||||
device_signal_combobox.include_config_signals = True
|
||||
device_signal_combobox.signals == []
|
||||
device_signal_combobox.set_device("samx")
|
||||
device_signal_combobox.signals == ["readback", "setpoint", "velocity"]
|
||||
|
||||
|
||||
def test_signal_lineeidt(device_signal_line_edit):
|
||||
"""Test the signal_combobox"""
|
||||
device_signal_line_edit._signals == []
|
||||
device_signal_line_edit.include_normal_signals = True
|
||||
device_signal_line_edit.include_hinted_signals = True
|
||||
device_signal_line_edit.include_config_signals = True
|
||||
device_signal_line_edit.signals == []
|
||||
device_signal_line_edit.set_device("samx")
|
||||
device_signal_line_edit.signals == ["readback", "setpoint", "velocity"]
|
45
tests/unit_tests/test_filter_io.py
Normal file
45
tests/unit_tests/test_filter_io.py
Normal file
@ -0,0 +1,45 @@
|
||||
import pytest
|
||||
|
||||
from bec_widgets.utils.filter_io import FilterIO
|
||||
from bec_widgets.widgets.dap_combo_box.dap_combo_box import DapComboBox
|
||||
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
|
||||
|
||||
from .client_mocks import mocked_client
|
||||
from .conftest import create_widget
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def dap_mock(qtbot, mocked_client):
|
||||
"""Fixture for QLineEdit widget"""
|
||||
models = ["GaussianModel", "LorentzModel", "SineModel"]
|
||||
mocked_client.dap._available_dap_plugins.keys.return_value = models
|
||||
widget = create_widget(qtbot, DapComboBox, client=mocked_client)
|
||||
return widget
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def line_edit_mock(qtbot, mocked_client):
|
||||
"""Fixture for QLineEdit widget"""
|
||||
widget = create_widget(qtbot, DeviceLineEdit, client=mocked_client)
|
||||
return widget
|
||||
|
||||
|
||||
def test_set_selection_combo_box(dap_mock):
|
||||
"""Test set selection for QComboBox using DapComboBox"""
|
||||
assert dap_mock.fit_model_combobox.count() == 3
|
||||
FilterIO.set_selection(dap_mock.fit_model_combobox, selection=["testA", "testB"])
|
||||
assert dap_mock.fit_model_combobox.count() == 2
|
||||
assert FilterIO.check_input(widget=dap_mock.fit_model_combobox, text="testA") is True
|
||||
|
||||
|
||||
def test_set_selection_line_edit(line_edit_mock):
|
||||
"""Test set selection for QComboBox using DapComboBox"""
|
||||
FilterIO.set_selection(line_edit_mock, selection=["testA", "testB"])
|
||||
assert line_edit_mock.completer.model().rowCount() == 2
|
||||
model = line_edit_mock.completer.model()
|
||||
model_data = [model.data(model.index(i)) for i in range(model.rowCount())]
|
||||
assert model_data == ["testA", "testB"]
|
||||
assert FilterIO.check_input(widget=line_edit_mock, text="testA") is True
|
||||
FilterIO.set_selection(line_edit_mock, selection=["testC"])
|
||||
assert FilterIO.check_input(widget=line_edit_mock, text="testA") is False
|
||||
assert FilterIO.check_input(widget=line_edit_mock, text="testC") is True
|
@ -2,6 +2,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from bec_widgets.widgets.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapSettings
|
||||
from bec_widgets.widgets.motor_map.motor_map_widget import BECMotorMapWidget
|
||||
|
||||
@ -11,8 +12,8 @@ from .client_mocks import mocked_client
|
||||
@pytest.fixture
|
||||
def motor_map_widget(qtbot, mocked_client):
|
||||
widget = BECMotorMapWidget(client=mocked_client())
|
||||
widget.toolbar.widgets["motor_x"].device_combobox.set_device_filter("FakePositioner")
|
||||
widget.toolbar.widgets["motor_y"].device_combobox.set_device_filter("FakePositioner")
|
||||
widget.toolbar.widgets["motor_x"].device_combobox.set_device_filter(BECDeviceFilter.POSITIONER)
|
||||
widget.toolbar.widgets["motor_y"].device_combobox.set_device_filter(BECDeviceFilter.POSITIONER)
|
||||
qtbot.addWidget(widget)
|
||||
qtbot.waitExposed(widget)
|
||||
yield widget
|
||||
@ -35,14 +36,12 @@ def test_motor_map_widget_init(motor_map_widget):
|
||||
assert motor_map_widget.toolbar.widgets["connect"].action.isEnabled() == True
|
||||
assert motor_map_widget.toolbar.widgets["config"].action.isEnabled() == False
|
||||
assert motor_map_widget.toolbar.widgets["history"].action.isEnabled() == False
|
||||
assert (
|
||||
motor_map_widget.toolbar.widgets["motor_x"].device_combobox.config.device_filter
|
||||
== "FakePositioner"
|
||||
)
|
||||
assert (
|
||||
motor_map_widget.toolbar.widgets["motor_y"].device_combobox.config.device_filter
|
||||
== "FakePositioner"
|
||||
)
|
||||
assert motor_map_widget.toolbar.widgets["motor_x"].device_combobox.config.device_filter == [
|
||||
BECDeviceFilter.POSITIONER
|
||||
]
|
||||
assert motor_map_widget.toolbar.widgets["motor_y"].device_combobox.config.device_filter == [
|
||||
BECDeviceFilter.POSITIONER
|
||||
]
|
||||
assert motor_map_widget.map.motor_x is None
|
||||
assert motor_map_widget.map.motor_y is None
|
||||
|
||||
|
Reference in New Issue
Block a user