mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-07-14 15:30:58 +02:00
fix: remove device/signal line edit and abstraction layer for combobox/lineEdit
This commit is contained in:
committed by
Jan Wyzula
co-authored by
Jan Wyzula
parent
ce456572d7
commit
bb6c0bb08f
+57
-321
@@ -1,12 +1,9 @@
|
||||
"""Module for handling filter I/O operations in BEC Widgets for input fields.
|
||||
These operations include filtering device/signal names and/or device types.
|
||||
"""
|
||||
"""Small helpers for populating editable combo boxes used by device inputs."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from __future__ import annotations
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from qtpy.QtCore import QStringListModel
|
||||
from qtpy.QtWidgets import QComboBox, QCompleter, QLineEdit
|
||||
from qtpy.QtWidgets import QComboBox
|
||||
from typeguard import TypeCheckError
|
||||
|
||||
from bec_widgets.utils.ophyd_kind_util import Kind
|
||||
@@ -14,329 +11,68 @@ from bec_widgets.utils.ophyd_kind_util import Kind
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class WidgetFilterHandler(ABC):
|
||||
"""Abstract base class for widget filter handlers"""
|
||||
|
||||
@abstractmethod
|
||||
def set_selection(self, widget, selection: list[str | tuple]) -> None:
|
||||
"""Set the filtered_selection for the widget
|
||||
|
||||
Args:
|
||||
widget: Widget instance
|
||||
selection (list[str | tuple]): Filtered selection of items.
|
||||
If tuple, it contains (text, data) pairs.
|
||||
"""
|
||||
|
||||
@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
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_with_kind(
|
||||
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
||||
) -> list[str | tuple]:
|
||||
"""Update the selection based on the kind of signal.
|
||||
|
||||
Args:
|
||||
kind (Kind): The kind of signal to filter.
|
||||
signal_filter (set): Set of signal kinds to filter.
|
||||
device_info (dict): Dictionary containing device information.
|
||||
device_name (str): Name of the device.
|
||||
|
||||
Returns:
|
||||
list[str | tuple]: A list of filtered signals based on the kind.
|
||||
"""
|
||||
# This method should be implemented in subclasses or extended as needed
|
||||
|
||||
def update_with_bec_signal_class(
|
||||
self,
|
||||
signal_class_filter: str | list[str],
|
||||
client,
|
||||
ndim_filter: int | list[int] | None = None,
|
||||
) -> list[tuple[str, str, dict]]:
|
||||
"""Update the selection based on signal classes using device_manager.get_bec_signals.
|
||||
|
||||
Args:
|
||||
signal_class_filter (str|list[str]): List of signal class names to filter.
|
||||
client: BEC client instance.
|
||||
ndim_filter (int | list[int] | None): Filter signals by dimensionality.
|
||||
If provided, only signals with matching ndim will be included.
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str, dict]]: A list of (device_name, signal_name, signal_config) tuples.
|
||||
"""
|
||||
if not client or not hasattr(client, "device_manager"):
|
||||
return []
|
||||
|
||||
try:
|
||||
signals = client.device_manager.get_bec_signals(signal_class_filter)
|
||||
except TypeCheckError as e:
|
||||
logger.warning(f"Error retrieving signals: {e}")
|
||||
return []
|
||||
|
||||
if ndim_filter is None:
|
||||
return signals
|
||||
|
||||
if isinstance(ndim_filter, int):
|
||||
ndim_filter = [ndim_filter]
|
||||
|
||||
filtered_signals = []
|
||||
for device_name, signal_name, signal_config in signals:
|
||||
ndim = None
|
||||
if isinstance(signal_config, dict):
|
||||
ndim = signal_config.get("describe", {}).get("signal_info", {}).get("ndim")
|
||||
|
||||
if ndim in ndim_filter:
|
||||
filtered_signals.append((device_name, signal_name, signal_config))
|
||||
|
||||
return filtered_signals
|
||||
def replace_combobox_items(combo_box: QComboBox, items: list[str | tuple]) -> None:
|
||||
"""Replace all combobox entries with strings or ``(text, data)`` tuples."""
|
||||
combo_box.clear()
|
||||
for item in items:
|
||||
if isinstance(item, str):
|
||||
combo_box.addItem(item)
|
||||
else:
|
||||
combo_box.addItem(*item)
|
||||
|
||||
|
||||
class LineEditFilterHandler(WidgetFilterHandler):
|
||||
"""Handler for QLineEdit widget"""
|
||||
|
||||
def set_selection(self, widget: QLineEdit, selection: list[str | tuple]) -> None:
|
||||
"""Set the selection for the widget to the completer model
|
||||
|
||||
Args:
|
||||
widget (QLineEdit): The QLineEdit widget
|
||||
selection (list[str | tuple]): Filtered selection of items. If tuple, it contains (text, data) pairs.
|
||||
"""
|
||||
if isinstance(selection, tuple):
|
||||
# If selection is a tuple, it contains (text, data) pairs
|
||||
selection = [text for text, _ in selection]
|
||||
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
|
||||
|
||||
def update_with_kind(
|
||||
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
||||
) -> list[str | tuple]:
|
||||
"""Update the selection based on the kind of signal.
|
||||
|
||||
Args:
|
||||
kind (Kind): The kind of signal to filter.
|
||||
signal_filter (set): Set of signal kinds to filter.
|
||||
device_info (dict): Dictionary containing device information.
|
||||
device_name (str): Name of the device.
|
||||
|
||||
Returns:
|
||||
list[str | tuple]: A list of filtered signals based on the kind.
|
||||
"""
|
||||
|
||||
return [
|
||||
signal
|
||||
for signal, signal_info in device_info.items()
|
||||
if kind in signal_filter and (signal_info.get("kind_str", None) == str(kind.name))
|
||||
]
|
||||
def combobox_contains_text(combo_box: QComboBox, text: str) -> bool:
|
||||
"""Return whether *text* is present as visible combobox text."""
|
||||
return any(combo_box.itemText(i) == text for i in range(combo_box.count()))
|
||||
|
||||
|
||||
class ComboBoxFilterHandler(WidgetFilterHandler):
|
||||
"""Handler for QComboBox widget"""
|
||||
def signal_items_for_kind(
|
||||
*, kind: Kind, signal_filter: set[Kind], device_info: dict, device_name: str
|
||||
) -> list[tuple[str, dict]]:
|
||||
"""Build display entries for signals matching a BEC signal kind."""
|
||||
items: list[tuple[str, dict]] = []
|
||||
for signal_name, signal_info in device_info.items():
|
||||
if kind not in signal_filter or signal_info.get("kind_str") != kind.name:
|
||||
continue
|
||||
|
||||
def set_selection(self, widget: QComboBox, selection: list[str | tuple]) -> None:
|
||||
"""Set the selection for the widget to the completer model
|
||||
obj_name = signal_info.get("obj_name", "")
|
||||
component_name = signal_info.get("component_name", "")
|
||||
signal_without_device = obj_name.removeprefix(f"{device_name}_")
|
||||
if not signal_without_device:
|
||||
signal_without_device = obj_name
|
||||
|
||||
Args:
|
||||
widget (QComboBox): The QComboBox widget
|
||||
selection (list[str | tuple]): Filtered selection of items. If tuple, it contains (text, data) pairs.
|
||||
"""
|
||||
widget.clear()
|
||||
if len(selection) == 0:
|
||||
return
|
||||
for element in selection:
|
||||
if isinstance(element, str):
|
||||
widget.addItem(element)
|
||||
elif isinstance(element, tuple):
|
||||
# If element is a tuple, it contains (text, data) pairs
|
||||
widget.addItem(*element)
|
||||
|
||||
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())]
|
||||
|
||||
def update_with_kind(
|
||||
self, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
||||
) -> list[str | tuple]:
|
||||
"""Update the selection based on the kind of signal.
|
||||
|
||||
Args:
|
||||
kind (Kind): The kind of signal to filter.
|
||||
signal_filter (set): Set of signal kinds to filter.
|
||||
device_info (dict): Dictionary containing device information.
|
||||
device_name (str): Name of the device.
|
||||
|
||||
Returns:
|
||||
list[str | tuple]: A list of filtered signals based on the kind.
|
||||
"""
|
||||
out = []
|
||||
for signal, signal_info in device_info.items():
|
||||
if kind not in signal_filter or (signal_info.get("kind_str", None) != str(kind.name)):
|
||||
continue
|
||||
obj_name = signal_info.get("obj_name", "")
|
||||
component_name = signal_info.get("component_name", "")
|
||||
signal_wo_device = obj_name.removeprefix(f"{device_name}_")
|
||||
if not signal_wo_device:
|
||||
signal_wo_device = obj_name
|
||||
|
||||
if signal_wo_device != signal and component_name.replace(".", "_") != signal_wo_device:
|
||||
# If the object name is not the same as the signal name, we use the object name
|
||||
# to display in the combobox.
|
||||
out.append((f"{signal_wo_device} ({signal})", signal_info))
|
||||
else:
|
||||
# If the object name is the same as the signal name, we do not change it.
|
||||
out.append((signal, signal_info))
|
||||
|
||||
return out
|
||||
if (
|
||||
signal_without_device != signal_name
|
||||
and component_name.replace(".", "_") != signal_without_device
|
||||
):
|
||||
items.append((f"{signal_without_device} ({signal_name})", signal_info))
|
||||
else:
|
||||
items.append((signal_name, signal_info))
|
||||
return items
|
||||
|
||||
|
||||
class FilterIO:
|
||||
"""Public interface to set filters for input widgets.
|
||||
It supports the list of widgets stored in class attribute _handlers.
|
||||
"""
|
||||
def get_bec_signals_for_classes(
|
||||
*, client, signal_class_filter: str | list[str], ndim_filter: int | list[int] | None = None
|
||||
) -> list[tuple[str, str, dict]]:
|
||||
"""Return BEC signals filtered by signal class and optional dimensionality."""
|
||||
if not client or not hasattr(client, "device_manager"):
|
||||
return []
|
||||
|
||||
_handlers = {QLineEdit: LineEditFilterHandler, QComboBox: ComboBoxFilterHandler}
|
||||
try:
|
||||
signals = client.device_manager.get_bec_signals(signal_class_filter)
|
||||
except TypeCheckError as exc:
|
||||
logger.warning(f"Error retrieving signals: {exc}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def set_selection(widget, selection: list[str | tuple], ignore_errors=True):
|
||||
"""
|
||||
Retrieve value from the widget instance.
|
||||
if ndim_filter is None:
|
||||
return signals
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
selection (list[str | tuple]): Filtered selection of items.
|
||||
If tuple, it contains (text, data) pairs.
|
||||
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=True):
|
||||
"""
|
||||
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 update_with_kind(
|
||||
widget, kind: Kind, signal_filter: set, device_info: dict, device_name: str
|
||||
) -> list[str | tuple]:
|
||||
"""
|
||||
Update the selection based on the kind of signal.
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
kind (Kind): The kind of signal to filter.
|
||||
signal_filter (set): Set of signal kinds to filter.
|
||||
device_info (dict): Dictionary containing device information.
|
||||
device_name (str): Name of the device.
|
||||
|
||||
Returns:
|
||||
list[str | tuple]: A list of filtered signals based on the kind.
|
||||
"""
|
||||
handler_class = FilterIO._find_handler(widget)
|
||||
if handler_class:
|
||||
return handler_class().update_with_kind(
|
||||
kind=kind,
|
||||
signal_filter=signal_filter,
|
||||
device_info=device_info,
|
||||
device_name=device_name,
|
||||
)
|
||||
raise ValueError(
|
||||
f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_with_signal_class(
|
||||
widget, signal_class_filter: list[str], client, ndim_filter: int | list[int] | None = None
|
||||
) -> list[tuple[str, str, dict]]:
|
||||
"""
|
||||
Update the selection based on signal classes using device_manager.get_bec_signals.
|
||||
|
||||
Args:
|
||||
widget: Widget instance.
|
||||
signal_class_filter (list[str]): List of signal class names to filter.
|
||||
client: BEC client instance.
|
||||
ndim_filter (int | list[int] | None): Filter signals by dimensionality.
|
||||
If provided, only signals with matching ndim will be included.
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str, dict]]: A list of (device_name, signal_name, signal_config) tuples.
|
||||
"""
|
||||
handler_class = FilterIO._find_handler(widget)
|
||||
if handler_class:
|
||||
return handler_class().update_with_bec_signal_class(
|
||||
signal_class_filter=signal_class_filter, client=client, ndim_filter=ndim_filter
|
||||
)
|
||||
raise ValueError(
|
||||
f"No matching handler for widget type: {type(widget)} in handler list {FilterIO._handlers}"
|
||||
)
|
||||
|
||||
@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
|
||||
accepted_ndim = [ndim_filter] if isinstance(ndim_filter, int) else ndim_filter
|
||||
filtered_signals: list[tuple[str, str, dict]] = []
|
||||
for device_name, signal_name, signal_config in signals:
|
||||
ndim = None
|
||||
if isinstance(signal_config, dict):
|
||||
ndim = signal_config.get("describe", {}).get("signal_info", {}).get("ndim")
|
||||
if ndim in accepted_ndim:
|
||||
filtered_signals.append((device_name, signal_name, signal_config))
|
||||
return filtered_signals
|
||||
|
||||
@@ -27,8 +27,10 @@ from qtpy.QtWidgets import (
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.utils.toolbars.splitter import ResizableSpacer
|
||||
from bec_widgets.widgets.control.device_input.base_classes.device_input_base import BECDeviceFilter
|
||||
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
|
||||
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import (
|
||||
BECDeviceFilter,
|
||||
DeviceComboBox,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
@@ -85,7 +85,11 @@ class ComboBoxHandler(WidgetHandler):
|
||||
|
||||
def set_value(self, widget: QComboBox, value: int | str) -> None:
|
||||
if isinstance(value, str):
|
||||
value = widget.findText(value)
|
||||
index = widget.findText(value)
|
||||
if index < 0 and widget.isEditable():
|
||||
widget.setCurrentText(value)
|
||||
return
|
||||
value = index
|
||||
if isinstance(value, int):
|
||||
widget.setCurrentIndex(value)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user