From a2c5ed3d5cb9fb622e5ac3abf6aae705de727dde Mon Sep 17 00:00:00 2001 From: Dawn Date: Wed, 9 Sep 2026 15:27:59 +0200 Subject: [PATCH 1/2] feat: show spreadsheet params in sample list Add Oscillation, Exposure, Total range and Transmission columns to the dewar sample table, read from SampleShortInfo.aaredb_params (already filled by spreadsheetupdater from AareDB, so no server change). Transmission is shown as percent like the user spreadsheet. The new columns are nullable, so the header sort key now puts None last instead of raising on None < float. Co-Authored-By: Claude Fable 5.1 --- src/aare/gui/models/user_sample_model.py | 26 ++++++++++++++++++++++-- tests/unit/gui/test_models.py | 25 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/aare/gui/models/user_sample_model.py b/src/aare/gui/models/user_sample_model.py index 0d25a5ce..78699887 100644 --- a/src/aare/gui/models/user_sample_model.py +++ b/src/aare/gui/models/user_sample_model.py @@ -43,7 +43,19 @@ def get_entry(sample: SampleShortInfo, column: int): return sample.screening_count elif column == 10: return sample.rotation_count + # Data-collection params straight from the AareDB spreadsheet (filled by + # spreadsheetupdater). Nullable: samples without params show blank. elif column == 11: + return sample.aaredb_params.oscillation if sample.aaredb_params else None + elif column == 12: + return sample.aaredb_params.exposure if sample.aaredb_params else None + elif column == 13: + return sample.aaredb_params.totalangle if sample.aaredb_params else None + elif column == 14: + # Model holds a 0-1 fraction; users think in spreadsheet percent. + t = sample.aaredb_params.transmission if sample.aaredb_params else None + return None if t is None else round(t * 100, 1) + elif column == 15: return sample.comment return "" @@ -74,6 +86,10 @@ class UserSampleSpreadsheet(QAbstractTableModel): # collected, so Screening sits left of Rotation. "Screening count", "Rotation count", + "Oscillation (°)", + "Exposure (s)", + "Total range (°)", + "Transmission (%)", "Comment", ] self.current_sample = current_sample @@ -261,10 +277,16 @@ class UserSampleSpreadsheet(QAbstractTableModel): ), # Reverse for descending order ) else: - # Sort the samples based on the specified column and order + # Sort the samples based on the specified column and order. + # Nullable columns (params, comment): None sorts last, never + # compared against a real value (None < float raises). + def _key(row): + v = get_entry(row, self._sort_col) + return (v is None, "" if v is None else v) + self._sorted_samples = sorted( filtered, - key=lambda row: get_entry(row, self._sort_col), + key=_key, reverse=( self._sort_order == Qt.SortOrder.DescendingOrder ), # Reverse for descending order diff --git a/tests/unit/gui/test_models.py b/tests/unit/gui/test_models.py index 95c634bc..e36b10df 100644 --- a/tests/unit/gui/test_models.py +++ b/tests/unit/gui/test_models.py @@ -263,3 +263,28 @@ def test_mime_data_round_trips_for_chip_drops(status_model): samples = SampleShortInfoList.model_validate_json(payload.text()) assert len(samples.s) == 2 assert samples.s[0].db_id == model.get_id(0).db_id + + +def test_spreadsheet_param_columns(status_model): + from aarecommon.models.models import DataCollectionParameters + + model = status_model + hdr = model.header + osc, exp, tot, trans = ( + hdr.index("Oscillation (°)"), + hdr.index("Exposure (s)"), + hdr.index("Total range (°)"), + hdr.index("Transmission (%)"), + ) + model.get_id(_row_of(model, 1)).aaredb_params = DataCollectionParameters( + oscillation=0.1, exposure=0.01, totalangle=360, transmission=0.125 + ) + row = _row_of(model, 1) + d = lambda c: model.data(model.index(row, c), Qt.ItemDataRole.DisplayRole) + assert (d(osc), d(exp), d(tot), d(trans)) == (0.1, 0.01, 360, 12.5) + # No params -> blank, and sorting a mostly-None column must not raise; + # the real value sorts first. + assert model.data(model.index(_row_of(model, 2), osc), Qt.ItemDataRole.DisplayRole) is None + model.sort(osc, Qt.SortOrder.AscendingOrder) + assert model.get_id(0).db_id == 1 + assert model.headerData(hdr.index("Comment"), Qt.Orientation.Horizontal, 0) == "Comment" -- 2.54.0 From 22228c0868d7276a0e8aa1da8cba2a93024b76de Mon Sep 17 00:00:00 2001 From: Dawn Date: Wed, 9 Sep 2026 15:28:30 +0200 Subject: [PATCH 2/2] feat: highlight whole mounted row in sample list The mounted sample used to tint only the frozen "#" cell. Paint the full row in the same blue so it is findable at a glance, and keep the theme's own text color on it (the mid-tone blue reads fine without the dark ink that the pastel status tints need). Repaint the whole row span on mount/unmount, not just column 0. Co-Authored-By: Claude Fable 5.1 --- src/aare/gui/models/user_sample_model.py | 23 ++++++++++++++++------- tests/unit/gui/test_models.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/aare/gui/models/user_sample_model.py b/src/aare/gui/models/user_sample_model.py index 78699887..4fbd7b30 100644 --- a/src/aare/gui/models/user_sample_model.py +++ b/src/aare/gui/models/user_sample_model.py @@ -139,17 +139,25 @@ class UserSampleSpreadsheet(QAbstractTableModel): elif role == Qt.ItemDataRole.BackgroundRole: # Status lives in the "#" column, as a full cell fill under the # row number — rows themselves alternate grey/white (view-level) - # and selection stays the pale blue tint. + # and selection stays the pale blue tint. Exception: the mounted + # sample paints the WHOLE row blue so it is findable at a glance. + sample = self._sorted_samples[index.row()] + if sample.db_id == self.current_sample: + return QBrush(qcolor(SAMPLE_ROW_QUEUED_BG)) if index.column() == COL_STATUS: - color = self._status_color(self._sorted_samples[index.row()]) + color = self._status_color(sample) if color is not None: return QBrush(qcolor(color)) elif role == Qt.ItemDataRole.ForegroundRole: - # Tinted cells get fixed dark ink: the tints stay light pastel in - # BOTH themes, so Sunset's white theme text would vanish on them. + # Pastel-tinted "#" cells get fixed dark ink: the tints stay light + # in BOTH themes, so Sunset's white theme text would vanish on + # them. The mounted-row blue is mid-tone and readable with the + # theme's own text color, so it keeps the default ink. + sample = self._sorted_samples[index.row()] if ( index.column() == COL_STATUS - and self._status_color(self._sorted_samples[index.row()]) is not None + and sample.db_id != self.current_sample + and self._status_color(sample) is not None ): return QBrush(qcolor(SAMPLE_STATUS_TEXT)) elif role == Qt.ItemDataRole.TextAlignmentRole: # Align text to center @@ -220,10 +228,11 @@ class UserSampleSpreadsheet(QAbstractTableModel): def _emit_tints_changed(self) -> None: if self.rowCount() > 0: + # Whole span: the mounted row tints every column, not just "#". self.dataChanged.emit( self.index(0, COL_STATUS), - self.index(self.rowCount() - 1, COL_STATUS), - [Qt.ItemDataRole.BackgroundRole], + self.index(self.rowCount() - 1, self.columnCount() - 1), + [Qt.ItemDataRole.BackgroundRole, Qt.ItemDataRole.ForegroundRole], ) def headerData(self, section, orientation, role=None): diff --git a/tests/unit/gui/test_models.py b/tests/unit/gui/test_models.py index e36b10df..58868d6a 100644 --- a/tests/unit/gui/test_models.py +++ b/tests/unit/gui/test_models.py @@ -265,6 +265,21 @@ def test_mime_data_round_trips_for_chip_drops(status_model): assert samples.s[0].db_id == model.get_id(0).db_id +def test_mounted_row_is_blue_across_all_columns(status_model): + # Whole-row blue (not just "#") so the mounted sample is findable at a + # glance; the blue keeps the theme's own ink (no dark override). + model = status_model + model.updateCurrentSample(current_puck="P1", current_sample=1) + row = _row_of(model, 1) + for col in range(model.columnCount()): + brush = model.data(model.index(row, col), Qt.ItemDataRole.BackgroundRole) + assert brush.color().name().lower() == "#729fcf" + assert model.data(model.index(row, col), Qt.ItemDataRole.ForegroundRole) is None + # Other rows: still "#"-only tint, other columns untouched. + other = _row_of(model, 2) + assert model.data(model.index(other, 1), Qt.ItemDataRole.BackgroundRole) is None + + def test_spreadsheet_param_columns(status_model): from aarecommon.models.models import DataCollectionParameters -- 2.54.0