diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index 183934e1..41ed7607 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -173,7 +173,6 @@ class MainWindow(QMainWindow): _default_dock_split_done: bool = False _pre_watch_dock_state: QByteArray | None = None _pre_watch_visibility: list[tuple[QWidget, bool]] | None = None - _login_default_state_sent: bool = False def __init__( self, @@ -404,6 +403,10 @@ class MainWindow(QMainWindow): self.left_column_tabs.addTab(beamline_page, "Beamline") self.left_column_tabs.addTab(experiment_page, "Experiment") + # A click on the pgroup-gated (disabled) Beamline tab is otherwise + # eaten silently by the tab bar — the filter pops the explanation + # instead (same pattern as the Auxiliary-puck tab). + self.left_column_tabs.tabBar().installEventFilter(self) # Only the visible page counts toward the height — same trick as the # content stack below, else the taller page pads the other tab. @@ -1424,6 +1427,20 @@ class MainWindow(QMainWindow): "to configure the beamline.", ) + def _show_beamline_tab_gated_popup(self) -> None: + # The tab is only ever disabled by the pgroup gate; staff hitting it + # are running a visitor's pgroup, users simply lack the rights. + if self._decoded_token.staff: + QMessageBox.information( + self, + "Beamline tab disabled", + "The active pgroup is not one of yours, so the Beamline tab " + "is disabled while running a visitor's experiment. Switch " + "back to your own pgroup to configure the beamline.", + ) + else: + self._show_beamline_staff_only_popup() + @Slot() def _raise_reference_tools(self) -> None: if not self._decoded_token.staff: @@ -2643,17 +2660,9 @@ class MainWindow(QMainWindow): self._apply_session_gate(getattr(getattr(s, "session", None), "session", None)) self._apply_pgroup_gate(getattr(getattr(s, "session", None), "current_pgroup", None)) - # Login default: staff start in BeamLocation, users in SampleAlignment. - # One-shot on the first status tick where this GUI owns the session and - # the beamline is idle — a busy beamline or watch-only GUI must never - # have its state yanked by someone merely logging in. - if not self._login_default_state_sent and self._session_operations_enabled and not s.busy: - self._login_default_state_sent = True - if self._decoded_token.staff: - if s.state != BeamlineStateEnum.BeamLocation: - self.daq.beam_location() - elif s.state != BeamlineStateEnum.SampleAlignment: - self.daq.sample_alignment() + # No login-default state: the GUI adopts whatever state the server + # reports and never posts a transition on startup — a state POST can + # move motors, and merely logging in must not move hardware. # Default tab only on state TRANSITIONS — a manual tab choice # survives while the state stays put. @@ -3225,6 +3234,18 @@ class MainWindow(QMainWindow): if event.type() == QEvent.Type.MouseButtonPress and obj in self._locked_beamline_banners: self._show_beamline_staff_only_popup() return True + # Click on the pgroup-gated (disabled) Beamline tab: tabAt() is + # geometric, so it still sees the disabled tab under the cursor. + left_tab_bar = self.left_column_tabs.tabBar() + if ( + event.type() == QEvent.Type.MouseButtonPress + and left_tab_bar is not None + and obj is left_tab_bar + and left_tab_bar.tabAt(event.position().toPoint()) == 0 + and not self.left_column_tabs.isTabEnabled(0) + ): + self._show_beamline_tab_gated_popup() + return True return super().eventFilter(obj, event) def _start_remote_close_countdown(