feat: fast shutter open/close row in Beamline setup
Status flag on the left (same rich-text scheme as the status bar, theme-aware via set_theme), Open/Close buttons on the right, wired to the same daq.open_shutter/close_shutter slots staff-gated in main_window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -972,6 +972,8 @@ class MainWindow(QMainWindow):
|
||||
if self._decoded_token.staff:
|
||||
self.monochromator_panel.mono_pitch_scan.connect(self.daq.mono_pitch_scan)
|
||||
self.monochromator_panel.change_energy.connect(self.daq.change_energy)
|
||||
self.monochromator_panel.open_shutter.connect(self.daq.open_shutter)
|
||||
self.monochromator_panel.close_shutter.connect(self.daq.close_shutter)
|
||||
self.abr_tweak.abr_tweak.connect(self.daq.abr_tweak)
|
||||
self.abr_tweak.abr_save.connect(self.daq.abr_save)
|
||||
self.abr_tweak.abr_goto_meas.connect(self.daq.abr_goto_meas)
|
||||
@@ -1899,6 +1901,9 @@ class MainWindow(QMainWindow):
|
||||
# _apply_theme call in __init__, hence the guard.
|
||||
if hasattr(self, "status_bar"):
|
||||
self.status_bar.set_theme(self._theme_mode)
|
||||
# Shutter flag in Beamline setup — staff-only panel, hence the guard.
|
||||
if hasattr(self, "monochromator_panel"):
|
||||
self.monochromator_panel.set_theme(self._theme_mode)
|
||||
self.sample_camera.set_theme(self._theme_mode)
|
||||
if old_look is None:
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ from aarecommon.models.models import DAQStatusModel
|
||||
from PySide6.QtCore import Signal, Slot
|
||||
from PySide6.QtWidgets import QDoubleSpinBox, QGridLayout, QLabel, QPushButton, QWidget
|
||||
|
||||
from aare.gui.styles import THEME_SUNRISE, status_colors
|
||||
from aare.gui.widgets.title_label import TitleLabel
|
||||
|
||||
|
||||
@@ -9,6 +10,8 @@ class MonochromatorPanel(QWidget):
|
||||
mono_pitch_scan = Signal()
|
||||
change_energy = Signal(float)
|
||||
move_beam_to_box = Signal()
|
||||
open_shutter = Signal()
|
||||
close_shutter = Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -49,6 +52,22 @@ class MonochromatorPanel(QWidget):
|
||||
self.change_energy_button.clicked.connect(self._emit_change_energy)
|
||||
grid_layout.addWidget(self.change_energy_button, 3, 2)
|
||||
|
||||
# Fast shutter row: status left, Open/Close buttons right — same
|
||||
# rich-text scheme as the status bar flag so the two readouts match.
|
||||
# Colors are painted in code (set_theme), QSS can't reach the spans.
|
||||
self._colors = status_colors(THEME_SUNRISE)
|
||||
self._shutter_open: bool | None = None
|
||||
self.shutter_status_label = QLabel("Fast Shutter: —", parent=self)
|
||||
grid_layout.addWidget(self.shutter_status_label, 4, 0)
|
||||
|
||||
self.open_shutter_button = QPushButton("Open", parent=self)
|
||||
self.open_shutter_button.clicked.connect(self.open_shutter.emit)
|
||||
grid_layout.addWidget(self.open_shutter_button, 4, 1)
|
||||
|
||||
self.close_shutter_button = QPushButton("Close", parent=self)
|
||||
self.close_shutter_button.clicked.connect(self.close_shutter.emit)
|
||||
grid_layout.addWidget(self.close_shutter_button, 4, 2)
|
||||
|
||||
# TODO(wire backend): no DAQ endpoint exists yet for moving the beam
|
||||
# to the box center — shown disabled as WIP until the operation is
|
||||
# defined server-side; then drop "(WIP)", enable, and connect the
|
||||
@@ -57,7 +76,22 @@ class MonochromatorPanel(QWidget):
|
||||
self.move_beam_to_box_button.setToolTip("Coming soon — not functional yet.")
|
||||
self.move_beam_to_box_button.setEnabled(False)
|
||||
self.move_beam_to_box_button.clicked.connect(self.move_beam_to_box.emit)
|
||||
grid_layout.addWidget(self.move_beam_to_box_button, 4, 0, 1, 3)
|
||||
grid_layout.addWidget(self.move_beam_to_box_button, 5, 0, 1, 3)
|
||||
|
||||
def set_theme(self, theme: str) -> None:
|
||||
"""Adopt the theme's flag colors and re-render the shutter status."""
|
||||
self._colors = status_colors(theme)
|
||||
self._render_shutter()
|
||||
|
||||
def _render_shutter(self) -> None:
|
||||
if self._shutter_open is None:
|
||||
text = "Fast Shutter: —"
|
||||
elif self._shutter_open:
|
||||
text = f"""Fast Shutter: <span style="color: {self._colors["alert"]} ; "> Open ☢️ </span>"""
|
||||
else:
|
||||
text = f"""Fast Shutter: <span style="color: {self._colors["ok"]} ; "> Closed 🚪 </span>"""
|
||||
if self.shutter_status_label.text() != text:
|
||||
self.shutter_status_label.setText(text)
|
||||
|
||||
@Slot()
|
||||
def _emit_change_energy(self):
|
||||
@@ -75,3 +109,6 @@ class MonochromatorPanel(QWidget):
|
||||
# Guarded: runs per DAQ tick (2 Hz), skip the repaint when unchanged.
|
||||
if self.current_energy_label.text() != text:
|
||||
self.current_energy_label.setText(text)
|
||||
|
||||
self._shutter_open = bool(status.bl.shutter_open)
|
||||
self._render_shutter()
|
||||
|
||||
@@ -14,3 +14,25 @@ def test_current_energy_readout(qtbot, daq_status_factory):
|
||||
status.diffraction = status.diffraction.model_copy(update={"energy_keV": 0.0})
|
||||
panel.update_daq_status(status)
|
||||
assert panel.current_energy_label.text() == "— / —"
|
||||
|
||||
|
||||
def test_fast_shutter_row(qtbot, daq_status_factory):
|
||||
panel = MonochromatorPanel()
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
# Placeholder until the first DAQ tick.
|
||||
assert panel.shutter_status_label.text() == "Fast Shutter: —"
|
||||
|
||||
status = daq_status_factory()
|
||||
panel.update_daq_status(status)
|
||||
assert "Closed" in panel.shutter_status_label.text()
|
||||
|
||||
status.bl = status.bl.model_copy(update={"shutter_open": True})
|
||||
panel.update_daq_status(status)
|
||||
assert "Open" in panel.shutter_status_label.text()
|
||||
|
||||
# Buttons relay to the same DAQ signals the status-bar menu uses.
|
||||
with qtbot.waitSignal(panel.open_shutter, timeout=1000):
|
||||
panel.open_shutter_button.click()
|
||||
with qtbot.waitSignal(panel.close_shutter, timeout=1000):
|
||||
panel.close_shutter_button.click()
|
||||
|
||||
Reference in New Issue
Block a user