From d54259a77285e8463728909adef6835ceffd3292 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 24 May 2026 17:33:43 +0200 Subject: [PATCH] sky tab: add Msg column with 10s activity indicator per satellite row Co-Authored-By: Claude Sonnet 4.6 --- gui/sky_tab.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/gui/sky_tab.py b/gui/sky_tab.py index 996eabf..8dc4c9b 100644 --- a/gui/sky_tab.py +++ b/gui/sky_tab.py @@ -18,9 +18,11 @@ from PyQt5.QtGui import QColor, QBrush from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS from sky_widget import SkyPlotWidget -_COL_HEADERS = ['GNSS', 'SV', 'Elev °', 'Az °', 'C/N₀', 'Fix', 'Receiver'] +_COL_HEADERS = ['GNSS', 'SV', 'Elev °', 'Az °', 'C/N₀', 'Fix', 'Receiver', 'Msg'] +_COL_MSG = len(_COL_HEADERS) - 1 _TRAIL_MAXLEN = 86400 # 24 hours at 1 Hz _TRAIL_MAX_HOURS = 24 +_MSG_HIGHLIGHT_MS = 10_000 class SkyTab(QWidget): @@ -33,6 +35,7 @@ class SkyTab(QWidget): self._info_cards: dict = {} # rid -> QFrame self._info_labels: dict = {} # rid -> (lbl_pdop, lbl_visible, lbl_used) self._trail: dict = {} # (rid, gnss_id, sv_id) -> deque[(elev, azim)] + self._hi_timers: dict = {} # (rid, gnss_id, sv_id) -> QTimer outer = QVBoxLayout(self) outer.setContentsMargins(4, 4, 4, 4) @@ -87,7 +90,9 @@ class SkyTab(QWidget): self._table.setHorizontalHeaderLabels(_COL_HEADERS) hdr = self._table.horizontalHeader() hdr.setSectionResizeMode(QHeaderView.ResizeToContents) - hdr.setStretchLastSection(True) + hdr.setSectionResizeMode(_COL_MSG, QHeaderView.Fixed) + hdr.setStretchLastSection(False) + self._table.setColumnWidth(_COL_MSG, 36) self._table.setEditTriggers(QTableWidget.NoEditTriggers) self._table.setSelectionBehavior(QTableWidget.SelectRows) self._table.setAlternatingRowColors(False) @@ -193,12 +198,15 @@ class SkyTab(QWidget): # ── Data update ─────────────────────────────────────────────────────────── def _on_sat_update(self, rid: str, sats: list): + selected = self._selected_rids() for sat in sats: key = (rid, sat.gnss_id, sat.sv_id) self._store[key] = sat if key not in self._trail: self._trail[key] = deque(maxlen=_TRAIL_MAXLEN) self._trail[key].append((sat.elevation, sat.azimuth)) + if rid in selected: + self._highlight_row(key) self._sky.update_data(self._visible_sats(), self._active_trails()) self._update_info() @@ -219,6 +227,36 @@ class SkyTab(QWidget): def _rebuild_table_from_store(self): self._rebuild_table(self._visible_sats()) + # ── Row highlight on message receive ───────────────────────────────────── + + def _highlight_row(self, key): + try: + row = self._table_keys.index(key) + except ValueError: + return # row not in table yet (full rebuild pending) + item = self._table.item(row, _COL_MSG) + if item: + item.setText("✓") + t = self._hi_timers.get(key) + if t: + t.start(_MSG_HIGHLIGHT_MS) + else: + t = QTimer(self) + t.setSingleShot(True) + t.timeout.connect(lambda k=key, r=row: self._clear_highlight(k, r)) + t.start(_MSG_HIGHLIGHT_MS) + self._hi_timers[key] = t + + def _clear_highlight(self, key, row): + self._hi_timers.pop(key, None) + if row >= self._table.rowCount() or row >= len(self._table_keys): + return + if self._table_keys[row] != key: + return # row was reassigned during a full rebuild + item = self._table.item(row, _COL_MSG) + if item: + item.setText("") + # ── Table ───────────────────────────────────────────────────────────────── def _rebuild_table(self, sats: list): @@ -260,6 +298,10 @@ class SkyTab(QWidget): self._table.setItem(row, 4, cell(f"{sat.cno:.1f}")) self._table.setItem(row, 5, cell("✓" if sat.used_in_fix else "")) self._table.setItem(row, 6, cell(rid)) + msg_item = QTableWidgetItem("") + msg_item.setTextAlignment(Qt.AlignCenter) + msg_item.setBackground(brush) + self._table.setItem(row, _COL_MSG, msg_item) self._table.setSortingEnabled(True) vp.setUpdatesEnabled(True)