""" Block table widget — one row per sync block, colour-coded by type. Row tints use alpha-transparent QColor values so they blend with whatever base colour the OS palette provides, keeping type indication readable on both light and dark themes. """ from __future__ import annotations from PySide6.QtCore import Signal, Qt from PySide6.QtWidgets import ( QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView, ) from PySide6.QtGui import QColor, QBrush from seq_interp.src.gui.adapters import BlockRow _COL_NAMES = [ "#sync", "orig#", "Type", "Dur (µs)", "RF", "ADC", "Grad", "Delay", "gRF", "gADC", "gTR", "T-start (µs)", ] # Row background tints — alpha ~80/255 so they blend with the OS base colour. # On a white base: soft pastels. On a dark base: subtle dark tints. _BG: dict[str, QColor] = { "START": QColor(244, 143, 177, 80), # pink — start delay "TR": QColor(144, 202, 249, 80), # blue — TR delay "RF": QColor(255, 224, 130, 80), # amber — RF delay "adc": QColor(165, 214, 167, 80), # green — ADC block "rf": QColor(244, 143, 177, 80), # pink — RF block "grad": QColor(206, 147, 216, 80), # purple — gradient block "plain": QColor(0, 0, 0, 0 ), # transparent — no tint } # Structural stylesheet only — base text/background come from the OS palette. # Only the selection highlight and hover state are pinned to explicit colours # because they need to be clearly visible on any background. _STYLESHEET = """ QTableWidget { gridline-color: palette(mid); font-size: 12px; } QTableWidget::item { padding: 2px 4px; } QTableWidget::item:selected { background-color: #1565c0; color: #ffffff; } QTableWidget::item:hover:!selected { background-color: rgba(144, 202, 249, 60); } QHeaderView::section { border: none; border-bottom: 1px solid palette(mid); border-right: 1px solid palette(mid); padding: 4px 6px; font-weight: bold; } QScrollBar:horizontal, QScrollBar:vertical { background: palette(alternateBase); } """ def _bg_for(row: BlockRow) -> QColor: if row.is_delay: return _BG.get(row.delay_type, _BG["plain"]) if row.has_adc: return _BG["adc"] if row.has_rf: return _BG["rf"] if row.has_grad: return _BG["grad"] return _BG["plain"] class BlockTable(QTableWidget): blockSelected = Signal(int) # sync_index def __init__(self, parent=None): super().__init__(0, len(_COL_NAMES), parent) self.setHorizontalHeaderLabels(_COL_NAMES) self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setEditTriggers(QAbstractItemView.NoEditTriggers) self.setAlternatingRowColors(False) self.verticalHeader().setDefaultSectionSize(20) self.verticalHeader().setVisible(False) self.setStyleSheet(_STYLESHEET) self._rows: list[BlockRow] = [] self._suppress = False self.itemSelectionChanged.connect(self._on_selection) # ── public ──────────────────────────────────────────────────────────────── def load_rows(self, rows: list[BlockRow]) -> None: self._rows = rows self.setRowCount(0) self.setRowCount(len(rows)) for r, row in enumerate(rows): bg = QBrush(_bg_for(row)) vals = [ str(row.sync_index), str(row.orig_index) if row.orig_index >= 0 else "—", row.block_type, f"{row.duration * 1e6:.3f}", "✓" if row.has_rf else "", "✓" if row.has_adc else "", "✓" if row.has_grad else "", row.delay_type if row.is_delay else "", str(row.gate_rf), str(row.gate_adc), str(row.gate_tr), f"{row.t_start * 1e6:.3f}", ] for c, val in enumerate(vals): item = QTableWidgetItem(val) item.setTextAlignment(Qt.AlignCenter) item.setBackground(bg) # Foreground intentionally not set: the OS palette Text role # provides the correct readable colour for the current theme. self.setItem(r, c, item) def select_by_sync_index(self, sync_index: int) -> None: self._suppress = True try: for r, row in enumerate(self._rows): if row.sync_index == sync_index: self.selectRow(r) self.scrollTo(self.model().index(r, 0)) break finally: self._suppress = False def row_for_sync_index(self, sync_index: int) -> BlockRow | None: for row in self._rows: if row.sync_index == sync_index: return row return None # ── private ─────────────────────────────────────────────────────────────── def _on_selection(self) -> None: if self._suppress: return selected = self.selectionModel().selectedRows() if selected: r = selected[0].row() if r < len(self._rows): self.blockSelected.emit(self._rows[r].sync_index)