1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-10 02:37:59 +01:00

feat: display warning for multiple files

This commit is contained in:
2025-08-22 16:06:03 +02:00
parent 578b3ce3b4
commit 2ca661e91a
5 changed files with 85 additions and 40 deletions

View File

@@ -91,8 +91,8 @@ class DeviceManagerView(BECWidget, QWidget):
self.ophyd_test_dock.setWidget(self.ophyd_test)
# Create the dock widgets
self.explorer_dock = QtAds.CDockWidget("Explorer", self)
self.explorer_dock.setWidget(self.available_devices)
self.available_devices_dock = QtAds.CDockWidget("Explorer", self)
self.available_devices_dock.setWidget(self.available_devices)
self.device_table_view_dock = QtAds.CDockWidget("Device Table", self)
self.device_table_view_dock.setWidget(self.device_table_view)
@@ -104,7 +104,9 @@ class DeviceManagerView(BECWidget, QWidget):
self.dm_config_view_dock.setWidget(self.dm_config_view)
# Add the dock widgets to the dock manager
self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, self.explorer_dock)
self.dock_manager.addDockWidget(
QtAds.DockWidgetArea.LeftDockWidgetArea, self.available_devices_dock
)
monaco_yaml_area = self.dock_manager.addDockWidget(
QtAds.DockWidgetArea.RightDockWidgetArea, self.dm_config_view_dock
)
@@ -129,6 +131,9 @@ class DeviceManagerView(BECWidget, QWidget):
# Connect slots
self.device_table_view.selected_device.connect(self.dm_config_view.on_select_config)
self.device_table_view.model.devices_reset.connect(
self.available_devices.update_devices_state
)
####### Default view has to be done with setting up splitters ########
def set_default_view(self, horizontal_weights: list, vertical_weights: list):

View File

@@ -41,16 +41,24 @@ class AvailableDeviceResources(BECWidget, QWidget, Ui_availableDeviceResources):
self.tag_groups_list.clear()
self._items = {}
for tag_group, devices in self._backend.tag_groups.items():
item = QListWidgetItem(self.tag_groups_list)
tag_group_widget = DeviceTagGroup(self.tag_groups_list, tag_group, devices)
self.tag_groups_list.setItemWidget(item, tag_group_widget)
self.tag_groups_list.addItem(item)
self._items[tag_group] = (item, tag_group_widget)
item.setSizeHint(QSize(tag_group_widget.width(), tag_group_widget.height()))
self._add_tag_group(tag_group, devices)
self._add_tag_group("Untagged devices", self._backend.untagged_devices)
def _add_tag_group(self, tag_group: str, devices: set[HashableDevice]):
item = QListWidgetItem(self.tag_groups_list)
tag_group_widget = DeviceTagGroup(self.tag_groups_list, tag_group, devices)
self.tag_groups_list.setItemWidget(item, tag_group_widget)
self.tag_groups_list.addItem(item)
self._items[tag_group] = (item, tag_group_widget)
item.setSizeHint(QSize(tag_group_widget.width(), tag_group_widget.height()))
def _reset_devices_state(self):
for _, tag_group in self._items.values():
tag_group.reset_devices_state()
def set_devices_state(self, devices: Iterable[HashableDevice], included: bool):
for _, tag_group in self._items.values():
for device in devices:
for device in devices:
for _, tag_group in self._items.values():
tag_group.set_item_state(hash(device), included)
def resizeEvent(self, event):

View File

@@ -88,6 +88,11 @@ class DeviceResourceBackend(Protocol):
"""A set of all availble devices. The same device may not appear more than once."""
...
@property
def untagged_devices(self) -> set[HashableDevice]:
"""A set of all untagged devices. The same device may not appear more than once."""
...
def tags(self) -> set[str]:
"""Returns a set of all the tags in all available devices."""
...
@@ -97,26 +102,33 @@ class DeviceResourceBackend(Protocol):
...
def _devices_from_file(file: str, include_source: bool = True):
data = yaml_load(file, process_includes=False)
return _HashableDeviceSet(
HashableDevice.model_validate(
dev | {"name": name, "source_files": {file} if include_source else set()}
)
for name, dev in data.items()
)
class _ConfigFileBackend(DeviceResourceBackend):
def __init__(self) -> None:
self._raw_device_set: set[HashableDevice] = self._get_config_from_files(
self._raw_device_set: set[
HashableDevice
] = self._get_config_from_backup_file() | self._get_configs_from_plugin_files(
Path(plugin_repo_path()) / plugin_package_name() / "device_configs/"
)
self._tag_groups = self._get_tag_groups()
def _get_config_from_files(self, dir: Path):
def _get_config_from_backup_file(self):
return _devices_from_file(
"/home/perl_d/Development/bec/bec/logs/device_configs/recovery_configs/recovery_config_2025-08-22_14-02-29.yaml"
)
def _get_configs_from_plugin_files(self, dir: Path):
files = glob("*.yaml", root_dir=dir, recursive=True)
def devices_from_file(file: str):
data = yaml_load(str(dir / file))
return set(
HashableDevice.model_validate(
dev | {"name": name, "source_files": {str(dir / file)}}
)
for name, dev in data.items()
)
return reduce(operator.or_, map(devices_from_file, files))
return reduce(operator.or_, map(_devices_from_file, (str(dir / f) for f in files)))
def _get_tag_groups(self) -> dict[str, set[HashableDevice]]:
return {
@@ -132,6 +144,10 @@ class _ConfigFileBackend(DeviceResourceBackend):
def all_devices(self):
return self._raw_device_set
@property
def untagged_devices(self):
return {d for d in self._raw_device_set if d.deviceTags == set()}
def tags(self) -> set[str]:
return reduce(operator.or_, (dev.deviceTags for dev in self._raw_device_set))

View File

@@ -1,5 +1,4 @@
from textwrap import dedent
from typing import Callable, NamedTuple
from typing import NamedTuple
from bec_qthemes import material_icon
from qtpy.QtCore import QSize
@@ -15,6 +14,20 @@ from bec_widgets.widgets.control.device_manager.components.available_device_reso
DEVICE_HASH_ROLE = 101
def _warning_string(spec: HashableDevice):
name_warning = (
f"Device defined with multiple names! Please check:\n {'\n '.join(spec.names)}\n"
if len(spec.names) > 1
else ""
)
source_warning = (
f"Device found in multiple source files! Please check:\n {'\n '.join(spec.source_files)}"
if len(spec.source_files) > 1
else ""
)
return f"{name_warning}{source_warning}"
class _DeviceEntryWidget(QFrame):
_grid_size = QSize(120, 80)
@@ -32,6 +45,7 @@ class _DeviceEntryWidget(QFrame):
self.setMinimumSize(self._grid_size)
self.setup_title_layout(device_spec)
self.check_and_display_warning()
self.setToolTip(device_spec.rich_text())
@@ -42,19 +56,18 @@ class _DeviceEntryWidget(QFrame):
def setup_title_layout(self, device_spec: HashableDevice):
self._title_layout = QHBoxLayout()
self._title_layout.setContentsMargins(0, 0, 0, 0)
self._title_container = QWidget(parent=self)
self._title_container.setLayout(self._title_layout)
self._warning_label = QLabel()
self._title_layout.addWidget(self._warning_label)
self.title = QLabel(device_spec.name)
self.title.setToolTip(device_spec.name)
self.title.setStyleSheet(self.title_style("#FF0000"))
self._title_layout.addWidget(self.title)
self._title_layout.addStretch(1)
self._warning_label = QLabel()
self._title_layout.addWidget(self._warning_label)
self._layout.addWidget(self._title_container)
def check_and_display_warning(self):
@@ -62,15 +75,8 @@ class _DeviceEntryWidget(QFrame):
self._warning_label.setText("")
self._warning_label.setToolTip("")
else:
self._warning_label.setPixmap(material_icon("warning", color="#FFAA00"))
self._warning_label.setToolTip(
dedent(
f"""
{f"Device has multiple names! Please check! \n names: {self._device_spec.names}" if len(self._device_spec.names)>1 else ""}
{f"Device found in multiple source files! Please check! \n files: {self._device_spec.names}" if len(self._device_spec.names)>1 else ""}
"""
)
)
self._warning_label.setPixmap(material_icon("warning", size=(12, 12), color="#FFAA00"))
self._warning_label.setToolTip(_warning_string(self._device_spec))
@property
def device_hash(self):
@@ -104,6 +110,7 @@ class DeviceTagGroup(QWidget, Ui_DeviceTagGroup):
for device in data:
self._add_item(device)
self.device_list.sortItems()
self._update_num_included()
self.add_to_composition_button.clicked.connect(self.test)
@@ -115,6 +122,11 @@ class DeviceTagGroup(QWidget, Ui_DeviceTagGroup):
self.device_list.addItem(item)
self._devices[device.name] = _DeviceEntry(item, widget)
def reset_devices_state(self):
for dev in self._devices.values():
dev.widget.set_included(False)
self._update_num_included()
def set_item_state(self, /, device_hash: int, included: bool):
for dev in self._devices.values():
if dev.widget.device_hash == device_hash:

View File

@@ -115,6 +115,9 @@ class DeviceTableModel(QtCore.QAbstractTableModel):
Sort logic is implemented directly on the data of the table view.
"""
device_added = QtCore.Signal(dict)
devices_reset = QtCore.Signal(list)
def __init__(self, device_config: list[dict] | None = None, parent=None):
super().__init__(parent)
self._device_config = device_config or []
@@ -250,6 +253,7 @@ class DeviceTableModel(QtCore.QAbstractTableModel):
self.beginResetModel()
self._device_config = list(device_config)
self.endResetModel()
self.devices_reset.emit(self._device_config)
@SafeSlot(dict)
def add_device(self, device: dict):