diff --git a/bec_widgets/widgets/device_inputs/__init__.py b/bec_widgets/widgets/device_inputs/__init__.py new file mode 100644 index 00000000..9814e434 --- /dev/null +++ b/bec_widgets/widgets/device_inputs/__init__.py @@ -0,0 +1 @@ +from .device_combobox.device_combobox import DeviceComboBox diff --git a/bec_widgets/widgets/device_inputs/device_combobox/__init__.py b/bec_widgets/widgets/device_inputs/device_combobox/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py b/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py new file mode 100644 index 00000000..713dcb25 --- /dev/null +++ b/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py @@ -0,0 +1,88 @@ +from typing import TYPE_CHECKING + +from qtpy.QtWidgets import QComboBox + +from bec_widgets.widgets.device_inputs.device_input_base import DeviceInputBase, DeviceInputConfig + +if TYPE_CHECKING: + from bec_widgets.widgets.device_inputs.device_input_base import DeviceInputConfig + + +class DeviceComboBox(DeviceInputBase, QComboBox): + def __init__( + self, + parent=None, + client=None, + config: DeviceInputConfig = None, + gui_id: str | None = None, + device_filter: str = None, + default_device: str = None, + ): + super().__init__( + client=client, + config=config, + gui_id=gui_id, + device_filter=device_filter, + default_device=default_device, + ) + QComboBox.__init__(self, parent=parent) + + self.populate_combobox() + self._set_defaults() + + def _set_defaults(self): + """Set the default device and device filter.""" + if self.config.default_device is not None: + self.set_default_device(self.config.default_device) + if self.config.device_filter is not None: + self.set_device_filter(self.config.device_filter) + + def set_device_filter(self, device_filter: str): + """ + 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. + + Returns: + object: Device object. + """ + 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 + + +if __name__ == "__main__": # pragma: no cover + import sys + + from qtpy.QtWidgets import QApplication + + app = QApplication(sys.argv) + w = DeviceComboBox(default_device="samx") + w.show() + sys.exit(app.exec_()) diff --git a/bec_widgets/widgets/device_inputs/device_input_base.py b/bec_widgets/widgets/device_inputs/device_input_base.py new file mode 100644 index 00000000..d1f008c9 --- /dev/null +++ b/bec_widgets/widgets/device_inputs/device_input_base.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +from bec_widgets.utils import BECConnector, ConnectionConfig + + +class DeviceInputConfig(ConnectionConfig): + device_filter: str | list[str] | None = None + default_device: str | None = None + + +class DeviceInputBase(BECConnector): + """ + 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. + """ + + def __init__( + self, + parent=None, + client=None, + config=None, + gui_id=None, + device_filter: str = None, + default_device: str = None, + ): + if config is None: + config = DeviceInputConfig(widget_class=self.__class__.__name__) + else: + if isinstance(config, dict): + config = DeviceInputConfig(**config) + self.config = config + super().__init__(client=client, config=config, gui_id=gui_id) + + self.get_bec_shortcuts() + self.config.device_filter = device_filter + self.config.default_device = default_device + self._devices = [] + + @property + def devices(self) -> list[str]: + """ + Get the list of devices. + + Returns: + list[str]: List of devices. + """ + return self._devices + + @devices.setter + def devices(self, value: list[str]): + """ + Set the list of devices. + + Args: + value: List of devices. + """ + self._devices = value + + def set_device_filter(self, device_filter: str | list[str]): + """ + Set the device filter. + + Args: + device_filter(str): Device filter, name of the device class. + """ + self.validate_device_filter(device_filter) + self.config.device_filter = device_filter + + def set_default_device(self, default_device: str): + """ + Set the default device. + + Args: + default_device(str): Default device name. + """ + if default_device not in self.get_device_list(self.config.device_filter): + raise ValueError(f"Default device {default_device} is not in the device list.") + self.config.default_device = default_device + + 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. + + Args: + filter(str|None): Class name filter to apply on the device list. + + Returns: + devices(list[str]): List of device names. + """ + 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 + + def get_available_filters(self): + """ + 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. + + Args: + device(str): Device to validate. + """ + if device not in self.get_device_list(): + raise ValueError(f"Device {device} is not valid.")