From 1a17e260562bf2fac1a45ea706f497d4f9c98706 Mon Sep 17 00:00:00 2001 From: Dawn Date: Mon, 24 Aug 2026 13:14:40 +0200 Subject: [PATCH] fix: no moving to combined view in moving state --- src/aare/gui/main_window.py | 759 ++++++++++++++++++++++++++---------- 1 file changed, 544 insertions(+), 215 deletions(-) diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index cb1688b7..76c4ef87 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -1,65 +1,6 @@ import time import jwt -from aarecommon.config.logger import setup_logger -from aarecommon.math.coordinate import Coordinate, SmargonCoordinate -from aarecommon.math.diffraction_geometry import DiffractionGeometry -from aarecommon.math.sample_geometry import SampleGeometryModel - -# Common imports -from aarecommon.models.auth import BatonStatus -from aarecommon.models.models import ( - BeamlineStateEnum, - DAQStatusModel, - SampleShortInfoList, - SessionsStateEnum, - TokenData, -) -from PySide6.QtCore import ( - QByteArray, - QEvent, - QObject, - QPropertyAnimation, - QSettings, - Qt, - QTimer, - Signal, - Slot, -) -from PySide6.QtGui import ( - QAction, - QActionGroup, - QColor, - QCursor, - QGuiApplication, - QKeySequence, - QPalette, -) -from PySide6.QtWidgets import ( - QAbstractButton, - QApplication, - QCheckBox, - QComboBox, - QDockWidget, - QFrame, - QGraphicsColorizeEffect, - QGraphicsOpacityEffect, - QHBoxLayout, - QLabel, - QMainWindow, - QMessageBox, - QPushButton, - QScrollArea, - QSizePolicy, - QSlider, - QStackedWidget, - QTabWidget, - QToolBar, - QToolTip, - QVBoxLayout, - QWidget, -) - from aare.gui.about import about_text from aare.gui.constants import LOGGER_NAME @@ -135,6 +76,64 @@ from aare.gui.widgets.status_bar import StatusBar from aare.gui.widgets.title_label import TitleLabel, tighten_column from aare.gui.widgets.video_image import VideoGraphicsView from aare.gui.widgets.wheel_value_guard import WheelValueGuard +from aarecommon.config.logger import setup_logger +from aarecommon.math.coordinate import Coordinate, SmargonCoordinate +from aarecommon.math.diffraction_geometry import DiffractionGeometry +from aarecommon.math.sample_geometry import SampleGeometryModel + +# Common imports +from aarecommon.models.auth import BatonStatus +from aarecommon.models.models import ( + BeamlineStateEnum, + DAQStatusModel, + SampleShortInfoList, + SessionsStateEnum, + TokenData, +) +from PySide6.QtCore import ( + QByteArray, + QEvent, + QObject, + QPropertyAnimation, + QSettings, + Qt, + QTimer, + Signal, + Slot, +) +from PySide6.QtGui import ( + QAction, + QActionGroup, + QColor, + QCursor, + QGuiApplication, + QKeySequence, + QPalette, +) +from PySide6.QtWidgets import ( + QAbstractButton, + QApplication, + QCheckBox, + QComboBox, + QDockWidget, + QFrame, + QGraphicsColorizeEffect, + QGraphicsOpacityEffect, + QHBoxLayout, + QLabel, + QMainWindow, + QMessageBox, + QPushButton, + QScrollArea, + QSizePolicy, + QSlider, + QStackedWidget, + QTabWidget, + QToolBar, + QToolTip, + QVBoxLayout, + QWidget, +) logger = setup_logger(LOGGER_NAME) @@ -290,7 +289,9 @@ class MainWindow(QMainWindow): self._separator_hint_timer = QTimer(self) self._separator_hint_timer.setSingleShot(True) self._separator_hint_timer.setInterval(SEPARATOR_HINT_DELAY_MS) - self._separator_hint_timer.timeout.connect(lambda: self._set_separator_hint(True)) + self._separator_hint_timer.timeout.connect( + lambda: self._set_separator_hint(True) + ) root_widget = _AlertBannerHost(parent=self) root_widget.setObjectName("mainContentRoot") @@ -346,12 +347,17 @@ class MainWindow(QMainWindow): self.left_column_layout.setContentsMargins(0, 0, 0, 0) self.data_collection = DataCollectionSettings( - s=geom, parent=self.left_column, raster_mgr=self.raster, diffraction=diffraction + s=geom, + parent=self.left_column, + raster_mgr=self.raster, + diffraction=diffraction, ) # The beamline state strip lives in a bottom toolbar row (created # after the docks), not in the left column. Always visible. - self.beamline_state_panel = BeamlineStatePanel(staff=self._decoded_token.staff, parent=self) + self.beamline_state_panel = BeamlineStatePanel( + staff=self._decoded_token.staff, parent=self + ) # Anchor for _show_admin_tip: QToolTip inherits QSS from the widget # it is shown for, so this hidden label carries the red warning wash @@ -422,7 +428,9 @@ class MainWindow(QMainWindow): for i in range(self.left_column_tabs.count()): page = self.left_column_tabs.widget(i) vertical = ( - QSizePolicy.Policy.Preferred if i == index else QSizePolicy.Policy.Ignored + QSizePolicy.Policy.Preferred + if i == index + else QSizePolicy.Policy.Ignored ) page.setSizePolicy(QSizePolicy.Policy.Preferred, vertical) @@ -440,7 +448,11 @@ class MainWindow(QMainWindow): else self._locked_beamline_banners[0].parentWidget() ) assert beamline_first is not None # the locked banner always has its holder - for first in (beamline_first, self.data_collection, self.data_collection.file_path_panel): + for first in ( + beamline_first, + self.data_collection, + self.data_collection.file_path_panel, + ): first_layout = first.layout() assert first_layout is not None # panels build their layouts in __init__ m = first_layout.contentsMargins() @@ -467,12 +479,17 @@ class MainWindow(QMainWindow): # scrollbar pops into spare space and nothing moves. self.collection_controls_scroll.setFrameShape(QFrame.Shape.NoFrame) self.left_column.setFixedWidth(self.data_collection.set_width) - self.collection_controls_scroll.setFixedWidth(self.data_collection.set_width + 10) + self.collection_controls_scroll.setFixedWidth( + self.data_collection.set_width + 10 + ) self.video_tab = QTabWidget(parent=top_widget) self.sample_camera = SampleCameraImageLabel( - geom=geom, raster=self.raster, parent=top_widget, default_image=default_image + geom=geom, + raster=self.raster, + parent=top_widget, + default_image=default_image, ) # Baton toasts sit as a compact pill under the camera view instead of # a full-width bar at the top of the window. @@ -485,7 +502,9 @@ class MainWindow(QMainWindow): self.beamline_view_panel.refresh_requested.connect(self.refresh_axis_cameras) self.gonio_view = VideoGraphicsView() - self.gonio_view_panel = AxisVideoPanel("Gonio camera", self.gonio_view, parent=top_widget) + self.gonio_view_panel = AxisVideoPanel( + "Gonio camera", self.gonio_view, parent=top_widget + ) self.gonio_view_panel.refresh_requested.connect(self.refresh_axis_cameras) self.beamline_view_container = QWidget(parent=top_widget) @@ -501,7 +520,9 @@ class MainWindow(QMainWindow): self.beamline_combined_panel = AxisVideoPanel( "Beamline combined view", self.beamline_view_container, parent=top_widget ) - self.beamline_combined_panel.refresh_requested.connect(self.refresh_axis_cameras) + self.beamline_combined_panel.refresh_requested.connect( + self.refresh_axis_cameras + ) self.video_tab.addTab(self.sample_camera, "Sample camera") self.video_tab.addTab(self.gonio_view_panel, "Gonio camera") @@ -516,7 +537,10 @@ class MainWindow(QMainWindow): # self.video_tab.addTab(self.secondary_beamline_view_panel, "Secondary view") self.compact_sample_camera = SampleCameraImageLabel( - geom=geom, raster=self.raster, parent=root_widget, default_image=default_image + geom=geom, + raster=self.raster, + parent=root_widget, + default_image=default_image, ) self.compact_automation_panel = CompactAutomationPanel( self.compact_sample_camera, parent=root_widget @@ -531,7 +555,10 @@ class MainWindow(QMainWindow): # ── Portrait mode page ────────────────────────────────────────── self.portrait_sample_camera = SampleCameraImageLabel( - geom=geom, raster=self.raster, parent=root_widget, default_image=default_image + geom=geom, + raster=self.raster, + parent=root_widget, + default_image=default_image, ) self.portrait_mode_panel = PortraitModePanel( sample_camera_widget=self.portrait_sample_camera, parent=root_widget @@ -542,7 +569,9 @@ class MainWindow(QMainWindow): portrait_page_layout = QHBoxLayout(self.portrait_mode_page) portrait_page_layout.setContentsMargins(0, 0, 0, 0) portrait_page_layout.setSpacing(0) - self.portrait_mode_page.setFixedWidth(self.portrait_mode_panel.PORTRAIT_WIDTH + 24) + self.portrait_mode_page.setFixedWidth( + self.portrait_mode_panel.PORTRAIT_WIDTH + 24 + ) portrait_page_layout.addWidget( self.portrait_mode_panel, alignment=Qt.AlignmentFlag.AlignHCenter ) @@ -569,15 +598,21 @@ class MainWindow(QMainWindow): self.ref_tools_panel = ReferenceToolsPanel(samples=SampleShortInfoList(s=[])) self.job_list_panel = SampleQueuePanel(show_user=self._decoded_token.staff) - self.compact_automation_panel.play_pause_clicked.connect(self.job_list_panel.run) - self.compact_automation_panel.skip_clicked.connect(self.job_list_panel.skip_current_sample) + self.compact_automation_panel.play_pause_clicked.connect( + self.job_list_panel.run + ) + self.compact_automation_panel.skip_clicked.connect( + self.job_list_panel.skip_current_sample + ) self.compact_automation_panel.step_through_toggled.connect( self.job_list_panel.set_step_through ) self.compact_automation_panel.show_full_view_requested.connect( self._return_from_compact_automation_view ) - self.compact_automation_panel.annotation_selected.connect(self._handle_compact_annotation) + self.compact_automation_panel.annotation_selected.connect( + self._handle_compact_annotation + ) # One dock for both lists: the old tabified Reference Tools dock was # staff-only and hid behind the Sample List tab, so it "sometimes" @@ -595,7 +630,9 @@ class MainWindow(QMainWindow): dewar_layout.addWidget(self.tell_samples) self.quick_unmount_button = QPushButton("⏏ Unmount", dewar_tab) - self.quick_unmount_button.clicked.connect(lambda: self._on_manual_unmount_requested()) + self.quick_unmount_button.clicked.connect( + lambda: self._on_manual_unmount_requested() + ) automation_row = QHBoxLayout() for w in ( @@ -614,7 +651,9 @@ class MainWindow(QMainWindow): self.job_list_panel.remove_button.clicked.disconnect( self.job_list_panel.remove_selected_samples ) - self.job_list_panel.remove_button.clicked.connect(self._remove_selected_from_queue) + self.job_list_panel.remove_button.clicked.connect( + self._remove_selected_from_queue + ) self.job_list_panel.hide() # Queue controls act on the queue, whose order only the Queued chip @@ -667,7 +706,9 @@ class MainWindow(QMainWindow): self.tell_samples_dock.setObjectName("tell_samples_dock") self.tell_samples_dock.setWidget(sample_lists_wrap) self.tell_samples_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea) - self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.tell_samples_dock) + self.addDockWidget( + Qt.DockWidgetArea.BottomDockWidgetArea, self.tell_samples_dock + ) # No floating: popping the dock out ripped the panel from the row and # reshuffled the rest. The ⤢ button (in the title bar, next to ✕) @@ -720,7 +761,9 @@ class MainWindow(QMainWindow): self.information_dock.setObjectName("information_dock") self.information_dock.setWidget(information_wrap) self.information_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea) - self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.information_dock) + self.addDockWidget( + Qt.DockWidgetArea.BottomDockWidgetArea, self.information_dock + ) self.information_dock.setFeatures( QDockWidget.DockWidgetFeature.DockWidgetMovable | QDockWidget.DockWidgetFeature.DockWidgetClosable @@ -750,7 +793,9 @@ class MainWindow(QMainWindow): | Qt.DockWidgetArea.BottomDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea ) - self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.fluor_panel_dock) + self.addDockWidget( + Qt.DockWidgetArea.BottomDockWidgetArea, self.fluor_panel_dock + ) self.fluor_panel_dock.hide() self.job_list_panel.samples_in_queue_changed.connect( @@ -759,7 +804,9 @@ class MainWindow(QMainWindow): self.job_list_panel.automation_running_changed.connect( self.automation_progress_panel.set_running ) - self.job_list_panel.automation_running_changed.connect(self._on_automation_running_changed) + self.job_list_panel.automation_running_changed.connect( + self._on_automation_running_changed + ) self.job_list_panel.automation_running_changed.connect( self.compact_automation_panel.set_running ) @@ -769,20 +816,28 @@ class MainWindow(QMainWindow): self.job_list_panel.samples_in_queue_changed.connect( self.compact_automation_panel.set_samples_in_queue ) - self.job_list_panel.samples_in_queue_changed.connect(self._refresh_compact_queue_preview) + self.job_list_panel.samples_in_queue_changed.connect( + self._refresh_compact_queue_preview + ) # Portrait mode: queue size + running state + preview - self.job_list_panel.automation_running_changed.connect(self.portrait_mode_panel.set_running) + self.job_list_panel.automation_running_changed.connect( + self.portrait_mode_panel.set_running + ) self.job_list_panel.samples_in_queue_changed.connect( self.portrait_mode_panel.set_samples_in_queue ) - self.job_list_panel.samples_in_queue_changed.connect(self._refresh_portrait_queue_preview) + self.job_list_panel.samples_in_queue_changed.connect( + self._refresh_portrait_queue_preview + ) self.automation_progress_panel.set_samples_in_queue( len(self.job_list_panel.table_model.samples) ) self.automation_progress_panel.set_running(self.job_list_panel.is_running()) self.compact_automation_panel.set_running(self.job_list_panel.is_running()) - self.compact_automation_panel.set_step_through(self.job_list_panel.is_step_through()) + self.compact_automation_panel.set_step_through( + self.job_list_panel.is_step_through() + ) self.compact_automation_panel.set_samples_in_queue( len(self.job_list_panel.table_model.samples) ) @@ -798,7 +853,9 @@ class MainWindow(QMainWindow): | Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea ) - self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.smargon_trace_dock) + self.addDockWidget( + Qt.DockWidgetArea.RightDockWidgetArea, self.smargon_trace_dock + ) self.smargon_trace_dock.hide() # Target stability panel @@ -811,7 +868,9 @@ class MainWindow(QMainWindow): | Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea ) - self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.target_stability_dock) + self.addDockWidget( + Qt.DockWidgetArea.RightDockWidgetArea, self.target_stability_dock + ) self.target_stability_dock.hide() # Prediction Metrics Panel @@ -824,7 +883,9 @@ class MainWindow(QMainWindow): | Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea ) - self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.prediction_metrics_dock) + self.addDockWidget( + Qt.DockWidgetArea.RightDockWidgetArea, self.prediction_metrics_dock + ) self.prediction_metrics_dock.hide() # Full-width state strip directly above the status bar: the bottom @@ -863,7 +924,11 @@ class MainWindow(QMainWindow): def _only_current_page_counts(index: int) -> None: for i in range(self.content_stack.count()): page = self.content_stack.widget(i) - policy = QSizePolicy.Policy.Preferred if i == index else QSizePolicy.Policy.Ignored + policy = ( + QSizePolicy.Policy.Preferred + if i == index + else QSizePolicy.Policy.Ignored + ) page.setSizePolicy(policy, policy) self.content_stack.currentChanged.connect(_only_current_page_counts) @@ -882,7 +947,9 @@ class MainWindow(QMainWindow): # the default-state capture so "reset layout" gets it too; a saved # user layout (restored below) still wins. self.resizeDocks( - [self.tell_samples_dock, self.information_dock], [240, 240], Qt.Orientation.Vertical + [self.tell_samples_dock, self.information_dock], + [240, 240], + Qt.Orientation.Vertical, ) # Equal oversized requests -> Qt distributes proportionally = 50/50. self.resizeDocks( @@ -933,15 +1000,21 @@ class MainWindow(QMainWindow): self._remote_close_timer.timeout.connect(self._check_remote_close_deadline) self._axis_camera_refresh_timer = QTimer(self) - self._axis_camera_refresh_timer.setInterval(self._axis_camera_refresh_interval_ms) + self._axis_camera_refresh_timer.setInterval( + self._axis_camera_refresh_interval_ms + ) self._axis_camera_refresh_timer.timeout.connect(self.refresh_axis_cameras) self._axis_camera_refresh_timer.start() self.portrait_mode_panel.wire_to_main_window( job_list_panel=self.job_list_panel, tell_samples=self.tell_samples ) - self.portrait_mode_panel._back_btn.clicked.connect(self._return_from_portrait_mode) - self.portrait_mode_panel.grab_session_requested.connect(self.status_bar.request_baton) + self.portrait_mode_panel._back_btn.clicked.connect( + self._return_from_portrait_mode + ) + self.portrait_mode_panel.grab_session_requested.connect( + self.status_bar.request_baton + ) # Route alert banner signals through portrait-aware interceptors self.daq.polled_devices_status.connect(self._portrait_alert_primary) @@ -957,7 +1030,9 @@ class MainWindow(QMainWindow): self.daq.baton_request_result.connect(self._on_baton_request_result) self.daq.baton_response_result.connect(self._on_baton_response_result) self.daq.baton_timeout_checked.connect(self._on_baton_timeout_checked) - self.daq.automation_progress.connect(self.automation_progress_panel.set_progress) + self.daq.automation_progress.connect( + self.automation_progress_panel.set_progress + ) self.daq.automation_progress.connect(self.compact_automation_panel.set_progress) self.daq.automation_progress.connect(self.portrait_mode_panel.set_progress) @@ -971,7 +1046,9 @@ class MainWindow(QMainWindow): self.samcam.changed.connect(self.daq.samcam_settings) self.samcam.screenshot_requested.connect(self.daq.send_screenshot_db) - self.samcam.save_beam_location_setting.connect(self.daq.save_beam_location_camera_setting) + self.samcam.save_beam_location_setting.connect( + self.daq.save_beam_location_camera_setting + ) self.data_collection.find_tip.clicked.connect(self.daq.center_loop) self.data_collection.bounding_box.clicked.connect(self.daq.ml_bounding_box) self.daq.raster_generated_by_ml.connect(self.raster.update_active_grid_request) @@ -1016,16 +1093,24 @@ class MainWindow(QMainWindow): self.beam_size.beam_size.connect(self.daq.beam_size_mm) self.sample_camera.load_image.connect(self.raster.load_image) - self.sample_camera.switch_raster_grid.connect(self.data_collection.switch_to_raster) - self.samcam.show_detections_changed.connect(self.sample_camera.set_show_detections) + self.sample_camera.switch_raster_grid.connect( + self.data_collection.switch_to_raster + ) + self.samcam.show_detections_changed.connect( + self.sample_camera.set_show_detections + ) self.samcam.show_detection_polygons_changed.connect( self.sample_camera.set_show_detection_polygons ) - self.samcam.show_target_point_changed.connect(self.sample_camera.set_show_target_point) + self.samcam.show_target_point_changed.connect( + self.sample_camera.set_show_target_point + ) self.samcam.show_target_coordinates_changed.connect( self.sample_camera.set_show_target_coordinates ) - self.samcam.show_overlay_legend_changed.connect(self.sample_camera.set_show_overlay_legend) + self.samcam.show_overlay_legend_changed.connect( + self.sample_camera.set_show_overlay_legend + ) self.samcam.compact_overlay_legend_changed.connect( self.sample_camera.set_compact_overlay_legend ) @@ -1036,17 +1121,31 @@ class MainWindow(QMainWindow): if sample_feed_addr is not None: logger.debug(f"Starting prediction subscriber thread {sample_feed_addr}") - self.prediction_thread = PredictionSubscriber(pred_zmq_url=sample_feed_addr, topic=b"") + self.prediction_thread = PredictionSubscriber( + pred_zmq_url=sample_feed_addr, topic=b"" + ) self.prediction_thread.image.connect(self.sample_camera.update_pixmap) - self.prediction_thread.image.connect(self.compact_sample_camera.update_pixmap) - self.prediction_thread.image.connect(self.portrait_sample_camera.update_pixmap) - self.prediction_thread.prediction.connect(self.sample_camera.update_detections) - self.prediction_thread.prediction.connect(self.compact_sample_camera.update_detections) - self.prediction_thread.prediction.connect(self.portrait_sample_camera.update_detections) + self.prediction_thread.image.connect( + self.compact_sample_camera.update_pixmap + ) + self.prediction_thread.image.connect( + self.portrait_sample_camera.update_pixmap + ) + self.prediction_thread.prediction.connect( + self.sample_camera.update_detections + ) + self.prediction_thread.prediction.connect( + self.compact_sample_camera.update_detections + ) + self.prediction_thread.prediction.connect( + self.portrait_sample_camera.update_detections + ) self.prediction_thread.prediction.connect( self.prediction_metrics_panel.update_from_prediction ) - self.prediction_thread.target_point.connect(self.sample_camera.update_target_point) + self.prediction_thread.target_point.connect( + self.sample_camera.update_target_point + ) self.prediction_thread.target_point.connect( self.compact_sample_camera.update_target_point ) @@ -1056,8 +1155,12 @@ class MainWindow(QMainWindow): self.prediction_thread.target_point.connect( self.target_stability_panel.update_target_point ) - self.prediction_thread.focus_measure.connect(self.status_bar.update_sharpness) - self.prediction_thread.fps_measure.connect(self.status_bar.update_samcam_fps) + self.prediction_thread.focus_measure.connect( + self.status_bar.update_sharpness + ) + self.prediction_thread.fps_measure.connect( + self.status_bar.update_samcam_fps + ) self.prediction_thread.camera_availability_changed.connect( self.sample_camera.set_camera_available ) @@ -1077,7 +1180,9 @@ class MainWindow(QMainWindow): self.sample_camera.set_camera_available(False) self.compact_sample_camera.set_camera_available(False) self.portrait_sample_camera.set_camera_available(False) - self._show_samcam_feed_banner("Sample camera feed unavailable: no stream configured") + self._show_samcam_feed_banner( + "Sample camera feed unavailable: no stream configured" + ) # # self.data_collection.helical.helical_scan.connect(self.worker.helical_scan) @@ -1090,9 +1195,13 @@ class MainWindow(QMainWindow): self.sample_camera.evaluate_grid.connect(self.raster.run_grid_scan) self.data_collection.raster.evaluate_grid.connect(self.raster.run_grid_scan) - self.data_collection.raster.evaluate_grid_auto.connect(self.raster.run_grid_scan_auto) + self.data_collection.raster.evaluate_grid_auto.connect( + self.raster.run_grid_scan_auto + ) - self.sample_camera.clear_evaluated_grids.connect(self.raster.clear_completed_grids) + self.sample_camera.clear_evaluated_grids.connect( + self.raster.clear_completed_grids + ) self.sample_camera.clear_grid.connect(self.raster.clear_active_grid) self.daq.run_number_incremented.connect( self.data_collection.file_path_panel.increment_run_number @@ -1120,18 +1229,28 @@ class MainWindow(QMainWindow): ) self.job_list_panel.table_model.modelReset.connect(self._sync_queue_row_tints) - self.data_collection.raster.grid_size_updated.connect(self.raster.update_grid_size) - self.data_collection.raster.exp_time_updated.connect(self.raster.update_exposure_time) - self.data_collection.raster.transmission_updated.connect(self.raster.update_transmission) + self.data_collection.raster.grid_size_updated.connect( + self.raster.update_grid_size + ) + self.data_collection.raster.exp_time_updated.connect( + self.raster.update_exposure_time + ) + self.data_collection.raster.transmission_updated.connect( + self.raster.update_transmission + ) self.data_collection.raster.dtz_updated.connect(self.raster.update_dtz) self.data_collection.raster.grid_metric_updated.connect(self.raster.metric) - self.data_collection.raster.raster_alpha_changed.connect(self.sample_camera.raster_alpha) + self.data_collection.raster.raster_alpha_changed.connect( + self.sample_camera.raster_alpha + ) self.data_collection.cancel.connect(self.daq.cancel) self.raster.grid_scan.connect(self.daq.raster_scan) self.raster.grid_scan_auto.connect(self.daq.raster_scan_auto) self.data_collection.screening.rotation_scan.connect(self.daq.standard_scan) - self.data_collection.simple.rotation_scan.connect(self._on_simple_rotation_requested) + self.data_collection.simple.rotation_scan.connect( + self._on_simple_rotation_requested + ) self.data_collection.simple.parameters_changed.connect(self.daq.smart_params) self.raster.grid_scan_size_changed.connect( @@ -1164,20 +1283,28 @@ class MainWindow(QMainWindow): self.beamline_state_panel.sample_exchange.connect(self.daq.sample_exchange) self.beamline_state_panel.sample_alignment.connect(self.daq.sample_alignment) self.beamline_state_panel.beam_location.connect(self.daq.beam_location) - self.beamline_state_panel.beamstop_alignment.connect(self.daq.beamstop_alignment) + self.beamline_state_panel.beamstop_alignment.connect( + self.daq.beamstop_alignment + ) self.beamline_state_panel.flux_measurement.connect(self.daq.flux_measurement) self.beamline_state_panel.data_collection.connect(self.daq.data_collection) self.beamline_state_panel.xtal_snapshot.connect(self.daq.xtal_snapshot) self.beamline_state_panel.xray_fluorescence.connect(self.daq.xray_fluorescence) - self.beamline_state_panel.robot_sample_exchange.connect(self.daq.robot_sample_exchange) + self.beamline_state_panel.robot_sample_exchange.connect( + self.daq.robot_sample_exchange + ) self.rotation.file_ready.connect(self.viewer.load_image) self.raster.image_selected.connect(self.viewer.load_image) self.raster.viewer_track_online.connect(self.viewer.load_online) - self.data_collection.screening.viewer_track_online.connect(self.viewer.load_online) + self.data_collection.screening.viewer_track_online.connect( + self.viewer.load_online + ) self.job_list_panel.viewer_track_online.connect(self.viewer.load_online) - self.sample_logic.sample_changed.connect(self.data_collection.file_path_panel.update_sample) + self.sample_logic.sample_changed.connect( + self.data_collection.file_path_panel.update_sample + ) self.daq.update.connect(self.beamline.omega_panel.update_daq_status) self.daq.update.connect(self.beamline.smargon_panel.update_daq_status) @@ -1223,7 +1350,9 @@ class MainWindow(QMainWindow): self.daq.automated_scan_done.connect(self.job_list_panel.automated_scan_done) self.daq.automated_scan_done.connect(self._mark_scan_result) - self.daq.automation_critical_failure.connect(self._on_automation_critical_failure) + self.daq.automation_critical_failure.connect( + self._on_automation_critical_failure + ) self.daq.manual_collection_critical_failure.connect( self._on_manual_collection_critical_failure ) @@ -1240,7 +1369,9 @@ class MainWindow(QMainWindow): self.data_collection.fluo.fluo_scan.connect(self._on_fluo_scan_requested) self.daq.fluorimeter_spectrum_update.connect(self.fluor_panel.update_plot) - self.daq.fluorimeter_spectrum_update.connect(lambda: self.fluor_panel_dock.setVisible(True)) + self.daq.fluorimeter_spectrum_update.connect( + lambda: self.fluor_panel_dock.setVisible(True) + ) # === Alert/Status Message Routing === # Status bar: General status messages (not device connection status) @@ -1281,9 +1412,13 @@ class MainWindow(QMainWindow): # Reference tools now live inside the Sample List dock as the # Auxiliary-puck tab — Ctrl+R raises the dock on that tab. - self._shortcut_raise_reference_tools_list = QAction("Raise reference tools", self) + self._shortcut_raise_reference_tools_list = QAction( + "Raise reference tools", self + ) self._shortcut_raise_reference_tools_list.setShortcut(QKeySequence("Ctrl+R")) - self._shortcut_raise_reference_tools_list.triggered.connect(self._raise_reference_tools) + self._shortcut_raise_reference_tools_list.triggered.connect( + self._raise_reference_tools + ) self.addAction(self._shortcut_raise_reference_tools_list) # The automation controls live under the Dewar samples tab now. @@ -1298,7 +1433,9 @@ class MainWindow(QMainWindow): ) self.addAction(self._shortcut_raise_job_list) - self._shortcut_toggle_target_stability = QAction("Toggle target stability panel", self) + self._shortcut_toggle_target_stability = QAction( + "Toggle target stability panel", self + ) self._shortcut_toggle_target_stability.setShortcut(QKeySequence("Ctrl+Shift+T")) self._shortcut_toggle_target_stability.triggered.connect( lambda: self.target_stability_dock.setVisible( @@ -1307,8 +1444,12 @@ class MainWindow(QMainWindow): ) self.addAction(self._shortcut_toggle_target_stability) - self._shortcut_toggle_prediction_metrics = QAction("Toggle prediction metrics panel", self) - self._shortcut_toggle_prediction_metrics.setShortcut(QKeySequence("Ctrl+Shift+P")) + self._shortcut_toggle_prediction_metrics = QAction( + "Toggle prediction metrics panel", self + ) + self._shortcut_toggle_prediction_metrics.setShortcut( + QKeySequence("Ctrl+Shift+P") + ) self._shortcut_toggle_prediction_metrics.triggered.connect( lambda: self.prediction_metrics_dock.setVisible( not self.prediction_metrics_dock.isVisible() @@ -1316,10 +1457,14 @@ class MainWindow(QMainWindow): ) self.addAction(self._shortcut_toggle_prediction_metrics) - self._shortcut_toggle_smargon_trace = QAction("Toggle smargon trace panel", self) + self._shortcut_toggle_smargon_trace = QAction( + "Toggle smargon trace panel", self + ) self._shortcut_toggle_smargon_trace.setShortcut(QKeySequence("Ctrl+Shift+S")) self._shortcut_toggle_smargon_trace.triggered.connect( - lambda: self.smargon_trace_dock.setVisible(not self.smargon_trace_dock.isVisible()) + lambda: self.smargon_trace_dock.setVisible( + not self.smargon_trace_dock.isVisible() + ) ) self.addAction(self._shortcut_toggle_smargon_trace) @@ -1386,7 +1531,9 @@ class MainWindow(QMainWindow): lambda chip: dewar_panel.set_status_chip(chip.property("status_key")) ) dewar_panel.status_chips.buttonClicked.connect( - lambda chip: self.tell_samples.set_status_chip(chip.property("status_key")) + lambda chip: self.tell_samples.set_status_chip( + chip.property("status_key") + ) ) # Pop-out chips drive the same shared filter — regate the queue # buttons from here too. @@ -1419,7 +1566,9 @@ class MainWindow(QMainWindow): if self._information_popout is None: # Automation mirror wired to the same feeds as the docked panel. panel = AutomationProgressWidget() - self.job_list_panel.samples_in_queue_changed.connect(panel.set_samples_in_queue) + self.job_list_panel.samples_in_queue_changed.connect( + panel.set_samples_in_queue + ) self.job_list_panel.automation_running_changed.connect(panel.set_running) self.daq.automation_progress.connect(panel.set_progress) panel.set_samples_in_queue(len(self.job_list_panel.table_model.samples)) @@ -1454,7 +1603,9 @@ class MainWindow(QMainWindow): lambda running: run_button.setText("⏸ Pause" if running else "▶ Run") ) remove_button = QPushButton("🗑 Remove selected") - remove_button.clicked.connect(lambda: self._unqueue_panel_selection(dewar_panel)) + remove_button.clicked.connect( + lambda: self._unqueue_panel_selection(dewar_panel) + ) clear_button = QPushButton("✖ Clear list") clear_button.clicked.connect(jl.clear) unmount_button = QPushButton("⏏ Unmount") @@ -1531,7 +1682,10 @@ class MainWindow(QMainWindow): current_widget = self.content_stack.currentWidget() - if hasattr(self, "portrait_mode_page") and current_widget is self.portrait_mode_page: + if ( + hasattr(self, "portrait_mode_page") + and current_widget is self.portrait_mode_page + ): self._return_from_portrait_mode() elif bool(getattr(self, "_in_compact_automation_view", False)): self._return_from_compact_automation_view() @@ -1539,23 +1693,31 @@ class MainWindow(QMainWindow): if hasattr(self, "content_stack") and hasattr(self, "_standard_main_page"): self.content_stack.setCurrentWidget(self._standard_main_page) except Exception as e: - logger.warning(f"Failed to return to main view during shutdown: {e}", exc_info=True) + logger.warning( + f"Failed to return to main view during shutdown: {e}", exc_info=True + ) def _restore_samcam_overlay_settings(self) -> None: settings = QSettings("PSI", "AareGUI") # bool()/str() wraps: QSettings.value is typed "object" even with # type=..., so the wraps are runtime no-ops for the pyright gate. - show_detections = bool(settings.value("samcam/show_detections", True, type=bool)) + show_detections = bool( + settings.value("samcam/show_detections", True, type=bool) + ) show_detection_polygons = bool( settings.value("samcam/show_detection_polygons", True, type=bool) ) - show_target_point = bool(settings.value("samcam/show_target_point", True, type=bool)) + show_target_point = bool( + settings.value("samcam/show_target_point", True, type=bool) + ) show_target_coordinates = bool( settings.value("samcam/show_target_coordinates", True, type=bool) ) # Legend defaults off — it covers the camera image; users opt in and # the choice persists via QSettings. - show_overlay_legend = bool(settings.value("samcam/show_overlay_legend", False, type=bool)) + show_overlay_legend = bool( + settings.value("samcam/show_overlay_legend", False, type=bool) + ) compact_overlay_legend = bool( settings.value("samcam/compact_overlay_legend", False, type=bool) ) @@ -1586,9 +1748,13 @@ class MainWindow(QMainWindow): "samcam/show_detection_polygons", overlay["show_detection_polygons"] ) # NEW settings.setValue("samcam/show_target_point", overlay["show_target_point"]) - settings.setValue("samcam/show_target_coordinates", overlay["show_target_coordinates"]) + settings.setValue( + "samcam/show_target_coordinates", overlay["show_target_coordinates"] + ) settings.setValue("samcam/show_overlay_legend", overlay["show_overlay_legend"]) - settings.setValue("samcam/compact_overlay_legend", overlay["compact_overlay_legend"]) + settings.setValue( + "samcam/compact_overlay_legend", overlay["compact_overlay_legend"] + ) settings.setValue("samcam/target_color", overlay["target_color"]) @Slot(bool) @@ -1610,7 +1776,9 @@ class MainWindow(QMainWindow): getattr(self, "portrait_sample_camera", None), ): if cam is not None: - cam.set_camera_error_message(message or "Sample camera feed unavailable") + cam.set_camera_error_message( + message or "Sample camera feed unavailable" + ) def _show_samcam_feed_banner(self, message: str) -> None: self._samcam_feed_banner_message = message @@ -1651,7 +1819,9 @@ class MainWindow(QMainWindow): try: activity_name = tell_state.activity.display_name() except Exception: - logger.debug("Could not derive the axis busy text from status", exc_info=True) + logger.debug( + "Could not derive the axis busy text from status", exc_info=True + ) activity_name = activity_value.capitalize() if activity_value else "Busy" return f"TELL {activity_name}".upper() @@ -1661,7 +1831,9 @@ class MainWindow(QMainWindow): if self._beamline_cam_addr: self.beamline_camera_thread = VideoThread(ip=self._beamline_cam_addr) - self.beamline_camera_thread.frame_ready.connect(self.beamline_view.update_frame) + self.beamline_camera_thread.frame_ready.connect( + self.beamline_view.update_frame + ) self.beamline_camera_thread.frame_ready.connect( self.beamline_view_2_combined.update_frame ) @@ -1672,7 +1844,9 @@ class MainWindow(QMainWindow): ip=self._gonio_cam_addr, camera=self._gonio_cam_id ) self.gonio_camera_thread.frame_ready.connect(self.gonio_view.update_frame) - self.gonio_camera_thread.frame_ready.connect(self.beamline_view_1_combined.update_frame) + self.gonio_camera_thread.frame_ready.connect( + self.beamline_view_1_combined.update_frame + ) self.gonio_camera_thread.start() def _all_tell_samples_in_default_order(self) -> list: @@ -1702,8 +1876,12 @@ class MainWindow(QMainWindow): if not self._in_compact_automation_view: self._pre_automation_window_state = self.saveState() - self._pre_automation_left_column_visible = self.collection_controls_scroll.isVisible() - self._pre_automation_right_column_visible = self.beamline_controls_scroll.isVisible() + self._pre_automation_left_column_visible = ( + self.collection_controls_scroll.isVisible() + ) + self._pre_automation_right_column_visible = ( + self.beamline_controls_scroll.isVisible() + ) self.tell_samples_dock.setVisible(False) self.information_dock.setVisible(False) @@ -1727,8 +1905,12 @@ class MainWindow(QMainWindow): if self._pre_automation_window_state is not None: self.restoreState(self._pre_automation_window_state) - self.collection_controls_scroll.setVisible(self._pre_automation_left_column_visible) - self.beamline_controls_scroll.setVisible(self._pre_automation_right_column_visible) + self.collection_controls_scroll.setVisible( + self._pre_automation_left_column_visible + ) + self.beamline_controls_scroll.setVisible( + self._pre_automation_right_column_visible + ) self._in_compact_automation_view = False self._update_view_mode_actions() @@ -1738,8 +1920,12 @@ class MainWindow(QMainWindow): @Slot() def _refresh_compact_queue_preview(self) -> None: - current_sample, next_sample, next_next_sample = self.job_list_panel.queue_preview() - self.compact_automation_panel.set_samples(current_sample, next_sample, next_next_sample) + current_sample, next_sample, next_next_sample = ( + self.job_list_panel.queue_preview() + ) + self.compact_automation_panel.set_samples( + current_sample, next_sample, next_next_sample + ) @Slot() def enter_portrait_mode(self) -> None: @@ -1747,7 +1933,9 @@ class MainWindow(QMainWindow): self._pre_portrait_geometry = self.saveGeometry() self.portrait_mode_panel.set_running(self.job_list_panel.is_running()) - self.portrait_mode_panel.set_samples_in_queue(len(self.job_list_panel.table_model.samples)) + self.portrait_mode_panel.set_samples_in_queue( + len(self.job_list_panel.table_model.samples) + ) self._refresh_portrait_queue_preview() self.content_stack.setCurrentWidget(self.portrait_mode_page) @@ -1889,7 +2077,9 @@ class MainWindow(QMainWindow): return mapping.get(annotation, str(annotation).strip()) @staticmethod - def _append_annotation_to_comment(existing_comment: str | None, annotation: str) -> str: + def _append_annotation_to_comment( + existing_comment: str | None, annotation: str + ) -> str: token = MainWindow._annotation_token(annotation) current = str(existing_comment or "").strip() @@ -1906,14 +2096,18 @@ class MainWindow(QMainWindow): def _handle_compact_annotation(self, annotation: str) -> None: current_sample, _, _ = self.job_list_panel.queue_preview() if current_sample is None: - self.status_bar.show_connection_message("No sample selected for annotation.", True) + self.status_bar.show_connection_message( + "No sample selected for annotation.", True + ) return updated_comment = self._append_annotation_to_comment( getattr(current_sample, "comment", None), annotation ) - self.job_list_panel.annotate_sample_comment(current_sample.db_id, updated_comment) + self.job_list_panel.annotate_sample_comment( + current_sample.db_id, updated_comment + ) self.tell_samples.annotate_sample_comment(current_sample.db_id, updated_comment) self._refresh_compact_queue_preview() @@ -2026,15 +2220,21 @@ class MainWindow(QMainWindow): # Prototype views live in the View menu (below the themes), not on the # menubar; only their in-progress labels changed, the internal # action/slot names stay. - self._enter_automation_view_action = QAction("Automation View (prototyping)", self) + self._enter_automation_view_action = QAction( + "Automation View (prototyping)", self + ) self._enter_automation_view_action.setShortcut(QKeySequence("Ctrl+5")) - self._enter_automation_view_action.triggered.connect(self.enter_compact_automation_view) + self._enter_automation_view_action.triggered.connect( + self.enter_compact_automation_view + ) # Stays top-level: it only shows while INSIDE the automation view, # where the way back must not hide in a menu. self._return_main_view_action = QAction("Return to Main View", self) self._return_main_view_action.setShortcut(QKeySequence("Ctrl+Shift+5")) - self._return_main_view_action.triggered.connect(self._return_from_compact_automation_view) + self._return_main_view_action.triggered.connect( + self._return_from_compact_automation_view + ) menu_bar.addAction(self._return_main_view_action) self._portrait_mode_action = QAction("Playlist Mode (work in progress)", self) @@ -2052,7 +2252,9 @@ class MainWindow(QMainWindow): self._use_legacy_theme_action.triggered.connect(self.use_legacy_theme) self._theme_action_group.addAction(self._use_legacy_theme_action) - self._use_portrait_theme_action = QAction("Sunset Theme (work in progress)", self) + self._use_portrait_theme_action = QAction( + "Sunset Theme (work in progress)", self + ) self._use_portrait_theme_action.setCheckable(True) self._use_portrait_theme_action.setChecked(self._theme_mode == THEME_SUNSET) self._use_portrait_theme_action.triggered.connect(self.use_portrait_theme) @@ -2087,7 +2289,9 @@ class MainWindow(QMainWindow): show_face_panel_action.triggered.connect( lambda checked: self.face_panel_dock.setVisible(checked) ) - self.face_panel_dock.visibilityChanged.connect(show_face_panel_action.setChecked) + self.face_panel_dock.visibilityChanged.connect( + show_face_panel_action.setChecked + ) view_menu.addAction(show_face_panel_action) show_fluor_panel_action = QAction("Show fluorescence", self) @@ -2096,7 +2300,9 @@ class MainWindow(QMainWindow): show_fluor_panel_action.triggered.connect( lambda checked: self.fluor_panel_dock.setVisible(checked) ) - self.fluor_panel_dock.visibilityChanged.connect(show_fluor_panel_action.setChecked) + self.fluor_panel_dock.visibilityChanged.connect( + show_fluor_panel_action.setChecked + ) view_menu.addAction(show_fluor_panel_action) show_smargon_trace_action = QAction("Show Smargon trace", self) @@ -2105,9 +2311,13 @@ class MainWindow(QMainWindow): show_smargon_trace_action.triggered.connect( lambda checked: self.smargon_trace_dock.setVisible(checked) ) - self.smargon_trace_dock.visibilityChanged.connect(show_smargon_trace_action.setChecked) self.smargon_trace_dock.visibilityChanged.connect( - lambda visible: self.smargon_trace_panel.refresh_plot(force=True) if visible else None + show_smargon_trace_action.setChecked + ) + self.smargon_trace_dock.visibilityChanged.connect( + lambda visible: ( + self.smargon_trace_panel.refresh_plot(force=True) if visible else None + ) ) view_menu.addAction(show_smargon_trace_action) @@ -2139,29 +2349,39 @@ class MainWindow(QMainWindow): show_information_action.triggered.connect( lambda checked: self.information_dock.setVisible(checked) ) - self.information_dock.visibilityChanged.connect(show_information_action.setChecked) + self.information_dock.visibilityChanged.connect( + show_information_action.setChecked + ) view_menu.addAction(show_information_action) view_menu.addSeparator() sample_camera_tab_action = QAction("Sample camera tab", self) sample_camera_tab_action.setShortcut(QKeySequence("Ctrl+1")) - sample_camera_tab_action.triggered.connect(lambda: self.video_tab.setCurrentIndex(0)) + sample_camera_tab_action.triggered.connect( + lambda: self.video_tab.setCurrentIndex(0) + ) view_menu.addAction(sample_camera_tab_action) gonio_camera_tab_action = QAction("Gonio camera tab", self) gonio_camera_tab_action.setShortcut(QKeySequence("Ctrl+2")) - gonio_camera_tab_action.triggered.connect(lambda: self.video_tab.setCurrentIndex(1)) + gonio_camera_tab_action.triggered.connect( + lambda: self.video_tab.setCurrentIndex(1) + ) view_menu.addAction(gonio_camera_tab_action) beamline_view_tab_action = QAction("Beamline view tab", self) beamline_view_tab_action.setShortcut(QKeySequence("Ctrl+3")) - beamline_view_tab_action.triggered.connect(lambda: self.video_tab.setCurrentIndex(2)) + beamline_view_tab_action.triggered.connect( + lambda: self.video_tab.setCurrentIndex(2) + ) view_menu.addAction(beamline_view_tab_action) beamline_combined_tab_action = QAction("Beamline combined view tab", self) beamline_combined_tab_action.setShortcut(QKeySequence("Ctrl+4")) - beamline_combined_tab_action.triggered.connect(lambda: self.video_tab.setCurrentIndex(3)) + beamline_combined_tab_action.triggered.connect( + lambda: self.video_tab.setCurrentIndex(3) + ) view_menu.addAction(beamline_combined_tab_action) view_menu.addSeparator() @@ -2263,7 +2483,11 @@ class MainWindow(QMainWindow): auto_clear_ms: int | None = None, ) -> None: self.log_panel.show_notification( - title=title, message=message, level=level, sticky=sticky, auto_clear_ms=auto_clear_ms + title=title, + message=message, + level=level, + sticky=sticky, + auto_clear_ms=auto_clear_ms, ) def _show_runtime_waiting_notification(self, *, title: str, message: str) -> None: @@ -2392,7 +2616,9 @@ class MainWindow(QMainWindow): "complete the safety search before mounting." ) if getattr(bl, "pss_alarm", False): - return "The hutch safety alarm is active. Mounting is blocked until it clears." + return ( + "The hutch safety alarm is active. Mounting is blocked until it clears." + ) return None def _on_manual_mount_requested(self, sample, reference: bool = False) -> None: @@ -2508,13 +2734,23 @@ class MainWindow(QMainWindow): progress = AutomationProgress( current_step=None, steps=[ - StepState(step=WorkflowStateKind.MOUNT, status=StepStatus.PENDING), - StepState(step=WorkflowStateKind.LOOP_CENTRE, status=StepStatus.PENDING), - StepState(step=WorkflowStateKind.RASTER, status=StepStatus.PENDING), StepState( - step=WorkflowStateKind.DATA_COLLECTION, status=StepStatus.PENDING + step=WorkflowStateKind.MOUNT, status=StepStatus.PENDING + ), + StepState( + step=WorkflowStateKind.LOOP_CENTRE, + status=StepStatus.PENDING, + ), + StepState( + step=WorkflowStateKind.RASTER, status=StepStatus.PENDING + ), + StepState( + step=WorkflowStateKind.DATA_COLLECTION, + status=StepStatus.PENDING, + ), + StepState( + step=WorkflowStateKind.FINAL, status=StepStatus.PENDING ), - StepState(step=WorkflowStateKind.FINAL, status=StepStatus.PENDING), ], finished=False, success=None, @@ -2530,7 +2766,9 @@ class MainWindow(QMainWindow): progress.success = False self.automation_progress_panel.set_progress(progress) except Exception: - logger.exception("Failed to update automation progress after critical failure") + logger.exception( + "Failed to update automation progress after critical failure" + ) self._show_runtime_notification( title="Automation paused", message=message, level="error", sticky=True @@ -2587,7 +2825,9 @@ class MainWindow(QMainWindow): if not bool(getattr(self._decoded_token, "staff", False)): return if self._beamline_recovery_dialog is None: - self._beamline_recovery_dialog = BeamlineRecoveryDialog(daq=self.daq, parent=self) + self._beamline_recovery_dialog = BeamlineRecoveryDialog( + daq=self.daq, parent=self + ) self._beamline_recovery_dialog.show() self._beamline_recovery_dialog.raise_() self._beamline_recovery_dialog.activateWindow() @@ -2622,11 +2862,18 @@ class MainWindow(QMainWindow): # No baton -> watching only: camera views stay live, every operating # surface is greyed. The SESSION VACANT badge (and the status bar # session menu) remain the way back in. - owned = session_state in (SessionsStateEnum.OwnedByYou, SessionsStateEnum.PendingElseToYou) + owned = session_state in ( + SessionsStateEnum.OwnedByYou, + SessionsStateEnum.PendingElseToYou, + ) if owned == getattr(self, "_session_operations_enabled", None): return self._session_operations_enabled = owned - for widget in (self.left_column_tabs, self.beamline, self.tell_samples_dock.widget()): + for widget in ( + self.left_column_tabs, + self.beamline, + self.tell_samples_dock.widget(), + ): widget.setEnabled(owned) if owned: # None clears the effect (Qt API contract); the PySide6 stub @@ -2643,14 +2890,16 @@ class MainWindow(QMainWindow): # Vacant folds every panel shut; grabbing reopens exactly the ones # that were open before. Transient (persist=False) so the fold never # overwrites the user's saved per-panel choices. - banners = self.left_column_tabs.findChildren(TitleLabel) + self.beamline.findChildren( + banners = self.left_column_tabs.findChildren( TitleLabel - ) + ) + self.beamline.findChildren(TitleLabel) if owned: for banner in getattr(self, "_pre_vacancy_open_banners", []): banner.set_collapsed(False, persist=False) else: - self._pre_vacancy_open_banners = [b for b in banners if not b.is_collapsed()] + self._pre_vacancy_open_banners = [ + b for b in banners if not b.is_collapsed() + ] for banner in banners: banner.set_collapsed(True, persist=False) @@ -2686,7 +2935,9 @@ class MainWindow(QMainWindow): # before the window is shown, where isVisible() is False for # everything and the restore would then "restore" all-hidden. self._pre_watch_dock_state = self.saveState() - self._pre_watch_visibility = [(w, not w.isHidden()) for w in hide_in_watch_mode] + self._pre_watch_visibility = [ + (w, not w.isHidden()) for w in hide_in_watch_mode + ] for widget in hide_in_watch_mode + self.findChildren(QDockWidget): widget.hide() @@ -2735,7 +2986,9 @@ class MainWindow(QMainWindow): def update_daq_status(self, s: DAQStatusModel): self._latest_daq_status = s self._apply_session_gate(getattr(getattr(s, "session", None), "session", None)) - self._apply_pgroup_gate(getattr(getattr(s, "session", None), "current_pgroup", None)) + self._apply_pgroup_gate( + getattr(getattr(s, "session", None), "current_pgroup", None) + ) # No login-default state: the GUI adopts whatever state the server # reports and never posts a transition on startup — a state POST can @@ -2751,10 +3004,16 @@ class MainWindow(QMainWindow): if self._is_automation_active(): self._refresh_idle_activity(report_backend=False) - if hasattr(self, "beamline_camera_thread") and self.beamline_camera_thread is not None: + if ( + hasattr(self, "beamline_camera_thread") + and self.beamline_camera_thread is not None + ): self.beamline_camera_thread.set_busy(s.busy) - if hasattr(self, "gonio_camera_thread") and self.gonio_camera_thread is not None: + if ( + hasattr(self, "gonio_camera_thread") + and self.gonio_camera_thread is not None + ): self.gonio_camera_thread.set_busy(s.busy) # Sample alignment busy is the alignment itself (same exception as @@ -2765,11 +3024,17 @@ class MainWindow(QMainWindow): session_state=getattr(getattr(s, "session", None), "session", None), ) - if hasattr(self, "beamline_view_panel") and self.beamline_view_panel is not None: + if ( + hasattr(self, "beamline_view_panel") + and self.beamline_view_panel is not None + ): self.beamline_view_panel.set_busy_style(busy_style) if hasattr(self, "gonio_view_panel") and self.gonio_view_panel is not None: self.gonio_view_panel.set_busy_style(busy_style) - if hasattr(self, "beamline_combined_panel") and self.beamline_combined_panel is not None: + if ( + hasattr(self, "beamline_combined_panel") + and self.beamline_combined_panel is not None + ): self.beamline_combined_panel.set_busy_style(busy_style) self.target_stability_panel.set_beam_center( @@ -2801,7 +3066,7 @@ class MainWindow(QMainWindow): # alignment, Beam location, Beamstop alignment, Flux measurement # etc. run busy while the user watches the sample camera itself. # Edge-triggered so a manual tab choice survives between transitions. - moving = s.state in (BeamlineStateEnum.Moving, BeamlineStateEnum.RobotSampleExchange) + moving = s.state == BeamlineStateEnum.RobotSampleExchange if moving and not self._watching_motion: self._watching_motion = True self.video_tab.setCurrentWidget(self.beamline_combined_panel) @@ -2832,7 +3097,9 @@ class MainWindow(QMainWindow): self._baton_request_dialog.accepted_signal.connect( self.status_bar._on_baton_dialog_accepted ) - self._baton_request_dialog.refused_signal.connect(self.status_bar._on_baton_dialog_refused) + self._baton_request_dialog.refused_signal.connect( + self.status_bar._on_baton_dialog_refused + ) self._baton_request_dialog.show() self._baton_request_dialog.raise_() self._baton_request_dialog.activateWindow() @@ -2857,7 +3124,9 @@ class MainWindow(QMainWindow): self._close_baton_pending_dialog() if status.you_are_holder: - self.alert_banner.show_message("Baton acquired!", False, auto_clear_ms=4000) + self.alert_banner.show_message( + "Baton acquired!", False, auto_clear_ms=4000 + ) else: self.alert_banner.show_message( "Request declined or cancelled", False, auto_clear_ms=4000 @@ -2865,7 +3134,9 @@ class MainWindow(QMainWindow): # Manage incoming request dialog (when someone requests from us) if not status.incoming_request and self._baton_request_dialog is not None: - logger.info("Incoming baton request no longer active, closing request dialog") + logger.info( + "Incoming baton request no longer active, closing request dialog" + ) self._close_baton_dialog() @Slot(dict) @@ -2891,12 +3162,15 @@ class MainWindow(QMainWindow): if getattr(self, "_baton_pending_dialog", None) is None: target_user = holder.replace("Request sent to ", "").replace( - " (Note: beamline is currently busy, transfer will be queued if accepted)", "" + " (Note: beamline is currently busy, transfer will be queued if accepted)", + "", ) self._baton_pending_dialog = BatonPendingDialog( target_user=target_user, timeout_seconds=timeout, parent=self ) - self._baton_pending_dialog.cancelled_signal.connect(self.daq.cancel_baton_request) + self._baton_pending_dialog.cancelled_signal.connect( + self.daq.cancel_baton_request + ) self._baton_pending_dialog.show() else: self._baton_pending_dialog.update_remaining(timeout) @@ -2906,14 +3180,18 @@ class MainWindow(QMainWindow): elif result.get("queued"): self._waiting_for_baton_response = True - self.alert_banner.show_waiting("Control transfer queued - waiting for beamline") + self.alert_banner.show_waiting( + "Control transfer queued - waiting for beamline" + ) logger.info("Baton transfer queued") if getattr(self, "_baton_pending_dialog", None) is None: self._baton_pending_dialog = BatonPendingDialog( target_user="Current Holder", timeout_seconds=0, parent=self ) - self._baton_pending_dialog.cancelled_signal.connect(self.daq.cancel_baton_request) + self._baton_pending_dialog.cancelled_signal.connect( + self.daq.cancel_baton_request + ) self._baton_pending_dialog.show() self._baton_pending_dialog.set_queued_state() @@ -2939,12 +3217,16 @@ class MainWindow(QMainWindow): logger.debug(f"Baton response result: {result}") if result.get("accepted"): self._waiting_for_baton_response = False - self.alert_banner.show_message("Control transferred", False, auto_clear_ms=4000) + self.alert_banner.show_message( + "Control transferred", False, auto_clear_ms=4000 + ) self._close_baton_dialog() self.status_bar.update_baton_status(self.status_bar._baton_status) elif result.get("refused"): self._waiting_for_baton_response = False - self.alert_banner.show_message("Request declined", False, auto_clear_ms=4000) + self.alert_banner.show_message( + "Request declined", False, auto_clear_ms=4000 + ) self._close_baton_dialog() self.status_bar.update_baton_status(self.status_bar._baton_status) else: @@ -2968,7 +3250,9 @@ class MainWindow(QMainWindow): elif result.get("queued"): self._waiting_for_baton_response = True - self.alert_banner.show_waiting("Control transfer queued - waiting for beamline") + self.alert_banner.show_waiting( + "Control transfer queued - waiting for beamline" + ) if getattr(self, "_baton_pending_dialog", None) is not None: self._baton_pending_dialog.set_queued_state() @@ -2976,13 +3260,17 @@ class MainWindow(QMainWindow): self._baton_pending_dialog = BatonPendingDialog( target_user="Current Holder", timeout_seconds=0, parent=self ) - self._baton_pending_dialog.cancelled_signal.connect(self.daq.cancel_baton_request) + self._baton_pending_dialog.cancelled_signal.connect( + self.daq.cancel_baton_request + ) self._baton_pending_dialog.show() self._baton_pending_dialog.set_queued_state() elif result.get("refused"): self._waiting_for_baton_response = False - self.alert_banner.show_message("Request declined", False, auto_clear_ms=4000) + self.alert_banner.show_message( + "Request declined", False, auto_clear_ms=4000 + ) self._close_baton_pending_dialog() else: @@ -3013,7 +3301,9 @@ class MainWindow(QMainWindow): settings.beginGroup("panel_visibility") settings.setValue("smargon_trace", self.smargon_trace_dock.isVisible()) settings.setValue("target_stability", self.target_stability_dock.isVisible()) - settings.setValue("prediction_metrics", self.prediction_metrics_dock.isVisible()) + settings.setValue( + "prediction_metrics", self.prediction_metrics_dock.isVisible() + ) settings.setValue("face_detection", self.face_panel_dock.isVisible()) settings.setValue("fluorescence", self.fluor_panel_dock.isVisible()) settings.setValue("information", self.information_dock.isVisible()) @@ -3025,7 +3315,9 @@ class MainWindow(QMainWindow): settings.beginGroup("panel_visibility") if settings.contains("smargon_trace"): - self.smargon_trace_dock.setVisible(settings.value("smargon_trace", False, type=bool)) + self.smargon_trace_dock.setVisible( + settings.value("smargon_trace", False, type=bool) + ) if settings.contains("target_stability"): self.target_stability_dock.setVisible( settings.value("target_stability", False, type=bool) @@ -3035,15 +3327,21 @@ class MainWindow(QMainWindow): settings.value("prediction_metrics", False, type=bool) ) if settings.contains("face_detection"): - self.face_panel_dock.setVisible(settings.value("face_detection", False, type=bool)) + self.face_panel_dock.setVisible( + settings.value("face_detection", False, type=bool) + ) if settings.contains("fluorescence"): - self.fluor_panel_dock.setVisible(settings.value("fluorescence", False, type=bool)) + self.fluor_panel_dock.setVisible( + settings.value("fluorescence", False, type=bool) + ) # New key: the old "log" flag described a dock that defaulted hidden; # the merged Information dock defaults visible, so old values would # wrongly hide it. if settings.contains("information"): # bool() wrap: settings.value is typed object even with type=bool. - self.information_dock.setVisible(bool(settings.value("information", True, type=bool))) + self.information_dock.setVisible( + bool(settings.value("information", True, type=bool)) + ) settings.endGroup() @@ -3056,7 +3354,11 @@ class MainWindow(QMainWindow): # them. Fall back to the default layout instead of resurrecting it # (the vanished-Sample-List-on-restart bug). docks = self.findChildren(QDockWidget) - if docks and all(d.isHidden() for d in docks) and self._default_window_state is not None: + if ( + docks + and all(d.isHidden() for d in docks) + and self._default_window_state is not None + ): self.restoreState(self._default_window_state) self._restore_panel_visibility_settings() @@ -3073,7 +3375,9 @@ class MainWindow(QMainWindow): def _apply_default_dock_split(self) -> None: self.resizeDocks( - [self.tell_samples_dock, self.information_dock], [240, 240], Qt.Orientation.Vertical + [self.tell_samples_dock, self.information_dock], + [240, 240], + Qt.Orientation.Vertical, ) self.resizeDocks( [self.tell_samples_dock, self.information_dock], @@ -3085,12 +3389,17 @@ class MainWindow(QMainWindow): try: self._return_to_main_view_for_shutdown() except Exception as e: - logger.warning(f"Failed to restore main view before close: {e}", exc_info=True) + logger.warning( + f"Failed to restore main view before close: {e}", exc_info=True + ) try: # Closing while watch-only would persist the all-hidden fold and # poison every future start — put the pre-watch layout back first. - if self._session_operations_enabled is False and self._pre_watch_dock_state is not None: + if ( + self._session_operations_enabled is False + and self._pre_watch_dock_state is not None + ): self.restoreState(self._pre_watch_dock_state) # TODO put all setting related handling into state_manager self.state_manager.save_window(self) @@ -3121,12 +3430,17 @@ class MainWindow(QMainWindow): try: self._return_to_main_view_for_shutdown() except Exception as e: - logger.warning(f"Failed to restore main view during cleanup: {e}", exc_info=True) + logger.warning( + f"Failed to restore main view during cleanup: {e}", exc_info=True + ) self._cleanup_done = True try: - if hasattr(self, "_samcam_source_timer") and self._samcam_source_timer is not None: + if ( + hasattr(self, "_samcam_source_timer") + and self._samcam_source_timer is not None + ): self._samcam_source_timer.stop() except Exception as e: logger.warning(f"Failed to stop _samcam_source_timer: {e}", exc_info=True) @@ -3138,7 +3452,10 @@ class MainWindow(QMainWindow): logger.warning(f"Failed to stop _idle_timer: {e}", exc_info=True) try: - if hasattr(self, "_remote_close_timer") and self._remote_close_timer is not None: + if ( + hasattr(self, "_remote_close_timer") + and self._remote_close_timer is not None + ): self._remote_close_timer.stop() except Exception as e: logger.warning(f"Failed to stop _remote_close_timer: {e}", exc_info=True) @@ -3150,7 +3467,9 @@ class MainWindow(QMainWindow): ): self._axis_camera_refresh_timer.stop() except Exception as e: - logger.warning(f"Failed to stop _axis_camera_refresh_timer: {e}", exc_info=True) + logger.warning( + f"Failed to stop _axis_camera_refresh_timer: {e}", exc_info=True + ) try: if hasattr(self, "daq") and self.daq is not None: @@ -3179,7 +3498,9 @@ class MainWindow(QMainWindow): if hasattr(self, "job_list_panel") and self.job_list_panel is not None: return bool(self.job_list_panel.is_running()) except Exception: - logger.debug("Could not read the job list panel running state", exc_info=True) + logger.debug( + "Could not read the job list panel running state", exc_info=True + ) try: progress = getattr(self.automation_progress_panel, "_progress", None) @@ -3197,7 +3518,10 @@ class MainWindow(QMainWindow): return now = time.monotonic() - if now - self._last_interaction_report_ts < self._interaction_report_min_interval_s: + if ( + now - self._last_interaction_report_ts + < self._interaction_report_min_interval_s + ): return self._last_interaction_report_ts = now @@ -3279,7 +3603,11 @@ class MainWindow(QMainWindow): # Closing a floated (popped-out) dock re-docks it instead of hiding — # otherwise the panel silently disappears and has to be restored via # the View menu. - if event.type() == QEvent.Type.Close and isinstance(obj, QDockWidget) and obj.isFloating(): + if ( + event.type() == QEvent.Type.Close + and isinstance(obj, QDockWidget) + and obj.isFloating() + ): obj.setFloating(False) event.ignore() return True @@ -3381,7 +3709,8 @@ class MainWindow(QMainWindow): if not self._can_close_for_idle_or_remote(): if self._remote_close_banner_active: self.alert_banner_secondary.show_message( - "GUI close requested, waiting for beamline/automation to become idle.", True + "GUI close requested, waiting for beamline/automation to become idle.", + True, ) return