wip: interlock gui

This commit is contained in:
David Perl
2026-05-31 16:11:34 +02:00
committed by David Perl
parent c9fc0a82b9
commit 0f4b307fc6
2 changed files with 170 additions and 0 deletions
@@ -0,0 +1,170 @@
from bec_lib.builtin_actor_hli import ScanInterlockHli
from bec_lib.endpoints import MessageEndpoints
from bec_lib.messages import BlStateStatus
from qtpy import QtGui, QtWidgets
from qtpy.QtCore import Qt
from bec_widgets.utils.bec_widget import BECWidget, ConnectionConfig
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
class ScanInterlockToggle(ToggleSwitch):
def __init__(self, interlock: ScanInterlockHli, parent):
super().__init__(parent=parent)
self._interlock = interlock
def mousePressEvent(self, event):
if self.isEnabled() and event.button() == Qt.MouseButton.LeftButton:
self._interlock.enabled = not self.checked
class BlStateList(QtWidgets.QListWidget):
def __init__(self, interlock: ScanInterlockHli, parent):
super().__init__(parent)
self._interlock = interlock
def dropEvent(self, event: QtGui.QDropEvent, /) -> None:
data = event.mimeData()
if not data.hasText():
return
self._interlock.add_state_to_interlock(data.text(), "valid")
class ScanInterlockControl(BECWidget, QtWidgets.QWidget):
"""
ScanInterlockControl can be used to enable/disable the scan interlock actor,
and add/remove beamline states for it to watch.
"""
RPC = False
PLUGIN = True
def __init__(
self,
parent: QtWidgets.QWidget | None = None,
client=None,
config: ConnectionConfig | None = None,
gui_id: str | None = None,
theme_update: bool = False,
**kwargs,
):
"""
Args:
parent (QtWidgets.QWidget, optional): The parent widget.
client: The BEC client.
config (ConnectionConfig, optional): The connection configuration.
gui_id (str, optional): The GUI ID.
theme_update (bool, optional): Whether to subscribe to theme updates. Defaults to False.
"""
super().__init__(
parent=parent,
client=client,
config=config,
gui_id=gui_id,
theme_update=theme_update,
**kwargs,
)
self._interlock = self.client.builtin_actors.scan_interlock
self._layout = QtWidgets.QVBoxLayout(self)
self._setup_control_layout()
self._setup_list_layout()
self.bec_dispatcher.connect_slot(
self._update_all_content,
MessageEndpoints.builtin_actor_update_notif("ScanInterlockActor"),
)
self._update_all_content()
def _setup_control_layout(self):
self._controls_layout = QtWidgets.QHBoxLayout()
self._layout.addLayout(self._controls_layout)
self._enabled_text = QtWidgets.QLabel()
self._enabled_text.setText("Widget Uninitialised")
self._enabled_toggle = ScanInterlockToggle(self._interlock, parent=self)
self._controls_layout.addWidget(self._enabled_text)
self._controls_layout.addWidget(self._enabled_toggle)
self._enabled_toggle
def _set_enabled_text(self, enabled: bool):
self._enabled_text.setText(
"Scan Interlock Enabled" if enabled else "Scan Interlock Disabled"
)
def _setup_list_layout(self):
self._list_layout = QtWidgets.QVBoxLayout()
self._layout.addLayout(self._list_layout)
self._list_layout.addWidget(QtWidgets.QLabel("Beamline states watched:"))
self._bl_states_list = BlStateList(self._interlock, self)
self._bl_states_list.setDragDropMode(QtWidgets.QListWidget.DragDropMode.DropOnly)
self._list_layout.addWidget(self._bl_states_list)
self._delete_button_layout = QtWidgets.QHBoxLayout()
self._list_layout.addLayout(self._delete_button_layout)
self._delete_button = QtWidgets.QPushButton("Remove selected states from interlock")
self._delete_button_layout.addWidget(self._delete_button)
self._delete_button.clicked.connect(self._delete_selected)
self._delete_all_button = QtWidgets.QPushButton("Remove all states")
self._delete_button_layout.addWidget(self._delete_all_button)
self._delete_all_button.clicked.connect(self._delete_all)
@SafeSlot()
def _delete_selected(self):
to_delete = [i.text() for i in self._bl_states_list.selectedItems()]
for state in to_delete:
self._interlock.remove_state_from_interlock(state)
@SafeSlot()
def _delete_all(self):
self._interlock.clear_all()
def _update_list(self, list_content: dict[str, BlStateStatus]):
self._bl_states_list.clear()
for item in list_content:
self._bl_states_list.addItem(item)
@SafeSlot()
def _update_all_content(self, *_, **__):
self._set_enabled_text(self._interlock.enabled)
self._enabled_toggle.setChecked(self._interlock.enabled)
self._update_list(self._interlock.states_watched)
if __name__ == "__main__": # pragma: no cover
# pylint: disable=import-outside-toplevel
from qtpy.QtWidgets import QApplication
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
app = QApplication([])
main_window = QtWidgets.QMainWindow()
central_widget = QtWidgets.QWidget()
button = DarkModeButton()
layout = QtWidgets.QVBoxLayout(central_widget)
main_window.setCentralWidget(central_widget)
scan_interlock_control = ScanInterlockControl() # type: ignore
layout.addWidget(button)
layout.addWidget(scan_interlock_control)
class TestList(QtWidgets.QListWidget):
def mimeData(self, items, /):
mimedata = super().mimeData(items)
text = ",".join([i.text() for i in items])
mimedata.setText(text)
return mimedata
test_list = TestList()
test_list.addItems(["samx_in_limits", "samy_in_limits"])
test_list.setDragEnabled(True)
layout.addWidget(test_list)
lineedit = QtWidgets.QLineEdit()
lineedit.setDragEnabled(True)
layout.addWidget(lineedit)
main_window.setWindowTitle("Scan Interlock Control")
main_window.resize(800, 400)
main_window.show()
app.exec_()