1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-04 16:02:51 +01:00

fix(image): combobox for device selection is repopulated with config update

This commit is contained in:
2025-12-02 16:50:18 +01:00
parent 3e4c5e9ab1
commit 3bd33b93cd
2 changed files with 48 additions and 22 deletions

View File

@@ -124,6 +124,9 @@ class Image(ImageBase):
self.async_update = False
self.bec_dispatcher.connect_slot(self.on_scan_status, MessageEndpoints.scan_status())
self.bec_dispatcher.connect_slot(self.on_scan_progress, MessageEndpoints.scan_progress())
self.bec_dispatcher.connect_slot(
self._populate_signals, MessageEndpoints.device_config_update()
)
##################################
### Toolbar Initialization
@@ -189,15 +192,21 @@ class Image(ImageBase):
Has to be done with QTimer.singleShot to ensure the UI is fully initialized, needed for testing.
"""
self._populate_signals()
self._reverse_device_items()
self.device_combo_box.setCurrentText("") # set again default to empty string
def _populate_signals(self) -> None:
@SafeSlot(dict, dict)
def _populate_signals(self, data: dict | None = None, meta: dict | None = None) -> None:
"""
Populate the device combo box with preview-signal devices in the
format '<device>_<signal>' and store the tuple(device, signal) in
the item's userData for later use.
(Re)populate the device combo box with preview/async signals,
matching the initial setup logic.
"""
self.device_combo_box.blockSignals(True)
self.device_combo_box.clear()
# Rebuild base device list via the combobox' own filtering logic
self.device_combo_box.update_devices_from_filters()
base_count = self.device_combo_box.count()
# Place an empty default entry between base devices and signal entries
self.device_combo_box.insertItem(base_count, "", None)
preview_signals = self.client.device_manager.get_bec_signals("PreviewSignal")
async_signals = self.client.device_manager.get_bec_signals("AsyncSignal")
all_signals = preview_signals + async_signals
@@ -209,6 +218,8 @@ class Image(ImageBase):
continue
label = signal_config.get("obj_name", f"{device}_{signal}")
self.device_combo_box.addItem(label, (device, signal, signal_config))
self.device_combo_box.setCurrentText("")
self.device_combo_box.blockSignals(False)
def _reverse_device_items(self) -> None:
"""

View File

@@ -730,21 +730,32 @@ def test_monitor_selection_populate_signals(qtbot, mocked_client, monkeypatch):
monkeypatch.setattr(view.client, "device_manager", _FakeDM())
initial_count = view.device_combo_box.count()
view._populate_signals()
# PreviewSignal + AsyncSignal entries were added
assert view.device_combo_box.count() == initial_count + 3
# Base devices first, then empty separator, then signal entries
signal_texts = []
separator_seen = False
for i in range(view.device_combo_box.count()):
data = view.device_combo_box.itemData(i)
text = view.device_combo_box.itemText(i)
if data is None and text == "":
separator_seen = True
continue
if separator_seen is False:
# base device entries
continue
# After separator we expect signal tuples
assert isinstance(data, tuple)
signal_texts.append(text)
# The first newly added item should carry tuple userData describing the device/signal
data = view.device_combo_box.itemData(initial_count)
assert isinstance(data, tuple) and data[0] == "eiger"
texts = [
view.device_combo_box.itemText(i)
for i in range(initial_count, view.device_combo_box.count())
]
assert "async_device_img_async" in texts
assert {"eiger_img", "eiger_img2", "async_device_img_async"}.issubset(set(signal_texts))
first_signal_idx = next(
i
for i in range(view.device_combo_box.count())
if isinstance(view.device_combo_box.itemData(i), tuple)
)
data = view.device_combo_box.itemData(first_signal_idx)
assert isinstance(data, tuple) and data[0] in ["eiger", "eiger2", "async_device"]
def test_monitor_selection_adjust_and_connect(qtbot, mocked_client, monkeypatch):
@@ -774,9 +785,13 @@ def test_monitor_selection_adjust_and_connect(qtbot, mocked_client, monkeypatch)
# Execute the method under test
view._adjust_and_connect()
# Expect exactly two items: preview label followed by the empty default
assert combo.count() == 2
# Because of the reversal, the preview label comes first
assert combo.itemText(0) == "eiger_img"
# Base devices should appear first, then empty separator, then signals
sep_idx = next(
i for i in range(combo.count()) if combo.itemData(i) is None and combo.itemText(i) == ""
)
first_signal_idx = sep_idx + 1
assert isinstance(combo.itemData(first_signal_idx), tuple)
assert combo.itemText(first_signal_idx) == "eiger_img"
assert combo.itemText(sep_idx) == ""
# Current selection remains empty
assert combo.currentText() == ""