feat: reference-tools position column and state-driven default sample tab

The reference table gets a display-only '#' column (like the Dewar
list) with sort indices shifted around it; the sample dock switches to
the Auxiliary puck tab on entering alignment/maintenance states and
back to Dewar elsewhere, only on state transitions so a manual choice
sticks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 09:47:06 +02:00
co-authored by Claude Fable 5
parent 612bc60b89
commit c8dbf7f8a4
2 changed files with 48 additions and 7 deletions
+30
View File
@@ -593,6 +593,10 @@ class MainWindow(QMainWindow):
# click is caught in eventFilter via the geometric tabAt() instead.
self.sample_lists_tabs.tabBar().installEventFilter(self)
# Tracks beamline-state TRANSITIONS for the default sample-tab switch
# (see _apply_default_sample_tab).
self._last_beamline_state: BeamlineStateEnum | None = None
# Wrapper for the left inset: QTabWidget ignores its own contents
# margins for the tab bar, so the padding lives one level up. Aligns
# the panel's left edge with the left column above (Loop centering).
@@ -2554,11 +2558,37 @@ class MainWindow(QMainWindow):
for widget in hide_in_watch_mode + self.findChildren(QDockWidget):
widget.hide()
# Alignment/maintenance states mount reference pins, so the sample dock
# defaults to the Auxiliary puck there; every other state defaults back
# to the Dewar list.
_AUX_PUCK_STATES = frozenset(
{
BeamlineStateEnum.Maintenance,
BeamlineStateEnum.BeamLocation,
BeamlineStateEnum.BeamstopAlignment,
BeamlineStateEnum.FluxMeasurement,
}
)
def _apply_default_sample_tab(self, state: BeamlineStateEnum | None) -> None:
# Index 1 = Auxiliary puck; disabled for non-staff, never force it.
if state in self._AUX_PUCK_STATES and self.sample_lists_tabs.isTabEnabled(1):
self.sample_lists_tabs.setCurrentIndex(1)
else:
self.sample_lists_tabs.setCurrentIndex(0)
@Slot(DAQStatusModel)
def update_daq_status(self, s: DAQStatusModel):
self._latest_daq_status = s
self._apply_session_gate(getattr(getattr(s, "session", None), "session", None))
# Default tab only on state TRANSITIONS — a manual tab choice
# survives while the state stays put.
new_beamline_state = getattr(s, "state", None)
if new_beamline_state != self._last_beamline_state:
self._last_beamline_state = new_beamline_state
self._apply_default_sample_tab(new_beamline_state)
if self._is_automation_active():
self._refresh_idle_activity(report_backend=False)
+18 -7
View File
@@ -40,7 +40,10 @@ class ReferenceToolsModel(QAbstractTableModel):
self.samples: list[SampleShortInfo] = rows or []
self.current_reference = current_reference
# Column 0 is display-only: the row position ("#"), matching the
# Dewar samples table; the vertical header is hidden in the panel.
self.header = [
"#",
"Position",
"Sample name",
"Mount count",
@@ -48,7 +51,7 @@ class ReferenceToolsModel(QAbstractTableModel):
"Rotation count",
"Screening count",
]
self._sort_col = 0
self._sort_col = 1
self._sort_order = Qt.SortOrder.AscendingOrder
self._sorted_samples: list[SampleShortInfo] = []
if self.samples:
@@ -70,7 +73,9 @@ class ReferenceToolsModel(QAbstractTableModel):
return None
if role == Qt.ItemDataRole.DisplayRole:
return get_entry(self._sorted_samples[index.row()], index.column())
if index.column() == 0:
return str(index.row() + 1)
return get_entry(self._sorted_samples[index.row()], index.column() - 1)
elif role == Qt.ItemDataRole.TextAlignmentRole:
return Qt.AlignmentFlag.AlignCenter
elif role == Qt.ItemDataRole.BackgroundRole:
@@ -109,6 +114,9 @@ class ReferenceToolsModel(QAbstractTableModel):
self.endResetModel()
def sort(self, column, order):
# The "#" column is display-only — nothing to sort by.
if column == 0:
return
self.layoutAboutToBeChanged.emit()
self._sort_order = order
self._sort_col = column
@@ -121,14 +129,14 @@ class ReferenceToolsModel(QAbstractTableModel):
self._sorted_samples = []
return
if self._sort_col == 0:
if self._sort_col == 1:
# Special sorting for location (Position column)
self._sorted_samples = sorted(
self.samples,
key=lambda row: row.loc_str_sort(),
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
)
elif self._sort_col == 2:
elif self._sort_col == 3:
# Numeric sort for Mount count; place None last on ascending, first on descending
none_sentinel = (
float("inf") if self._sort_order == Qt.SortOrder.AscendingOrder else float("-inf")
@@ -141,10 +149,11 @@ class ReferenceToolsModel(QAbstractTableModel):
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
)
else:
# String sort with empty fallback
# String sort with empty fallback (get_entry columns sit one left
# of the view columns because of the display-only "#").
self._sorted_samples = sorted(
self.samples,
key=lambda row: get_entry(row, self._sort_col) or "",
key=lambda row: get_entry(row, self._sort_col - 1) or "",
reverse=(self._sort_order == Qt.SortOrder.DescendingOrder),
)
@@ -221,7 +230,9 @@ class ReferenceToolsPanel(QFrame):
header.setStretchLastSection(True)
# No bold column titles when cells are selected.
header.setHighlightSections(False)
self.table_view.verticalHeader().setVisible(True)
# Row numbers live in the display-only "#" column (like the Dewar
# table), not the vertical header.
self.table_view.verticalHeader().setVisible(False)
logger.debug("Setting up table view sorting")
# Adopt the model's current order first — a second panel on a shared
# model must not re-sort it on open.