sky tab: add Msg column with 10s activity indicator per satellite row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-2
@@ -18,9 +18,11 @@ from PyQt5.QtGui import QColor, QBrush
|
|||||||
from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS
|
from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS
|
||||||
from sky_widget import SkyPlotWidget
|
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_MAXLEN = 86400 # 24 hours at 1 Hz
|
||||||
_TRAIL_MAX_HOURS = 24
|
_TRAIL_MAX_HOURS = 24
|
||||||
|
_MSG_HIGHLIGHT_MS = 10_000
|
||||||
|
|
||||||
|
|
||||||
class SkyTab(QWidget):
|
class SkyTab(QWidget):
|
||||||
@@ -33,6 +35,7 @@ class SkyTab(QWidget):
|
|||||||
self._info_cards: dict = {} # rid -> QFrame
|
self._info_cards: dict = {} # rid -> QFrame
|
||||||
self._info_labels: dict = {} # rid -> (lbl_pdop, lbl_visible, lbl_used)
|
self._info_labels: dict = {} # rid -> (lbl_pdop, lbl_visible, lbl_used)
|
||||||
self._trail: dict = {} # (rid, gnss_id, sv_id) -> deque[(elev, azim)]
|
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 = QVBoxLayout(self)
|
||||||
outer.setContentsMargins(4, 4, 4, 4)
|
outer.setContentsMargins(4, 4, 4, 4)
|
||||||
@@ -87,7 +90,9 @@ class SkyTab(QWidget):
|
|||||||
self._table.setHorizontalHeaderLabels(_COL_HEADERS)
|
self._table.setHorizontalHeaderLabels(_COL_HEADERS)
|
||||||
hdr = self._table.horizontalHeader()
|
hdr = self._table.horizontalHeader()
|
||||||
hdr.setSectionResizeMode(QHeaderView.ResizeToContents)
|
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.setEditTriggers(QTableWidget.NoEditTriggers)
|
||||||
self._table.setSelectionBehavior(QTableWidget.SelectRows)
|
self._table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||||
self._table.setAlternatingRowColors(False)
|
self._table.setAlternatingRowColors(False)
|
||||||
@@ -193,12 +198,15 @@ class SkyTab(QWidget):
|
|||||||
# ── Data update ───────────────────────────────────────────────────────────
|
# ── Data update ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _on_sat_update(self, rid: str, sats: list):
|
def _on_sat_update(self, rid: str, sats: list):
|
||||||
|
selected = self._selected_rids()
|
||||||
for sat in sats:
|
for sat in sats:
|
||||||
key = (rid, sat.gnss_id, sat.sv_id)
|
key = (rid, sat.gnss_id, sat.sv_id)
|
||||||
self._store[key] = sat
|
self._store[key] = sat
|
||||||
if key not in self._trail:
|
if key not in self._trail:
|
||||||
self._trail[key] = deque(maxlen=_TRAIL_MAXLEN)
|
self._trail[key] = deque(maxlen=_TRAIL_MAXLEN)
|
||||||
self._trail[key].append((sat.elevation, sat.azimuth))
|
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._sky.update_data(self._visible_sats(), self._active_trails())
|
||||||
self._update_info()
|
self._update_info()
|
||||||
@@ -219,6 +227,36 @@ class SkyTab(QWidget):
|
|||||||
def _rebuild_table_from_store(self):
|
def _rebuild_table_from_store(self):
|
||||||
self._rebuild_table(self._visible_sats())
|
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 ─────────────────────────────────────────────────────────────────
|
# ── Table ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _rebuild_table(self, sats: list):
|
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, 4, cell(f"{sat.cno:.1f}"))
|
||||||
self._table.setItem(row, 5, cell("✓" if sat.used_in_fix else ""))
|
self._table.setItem(row, 5, cell("✓" if sat.used_in_fix else ""))
|
||||||
self._table.setItem(row, 6, cell(rid))
|
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)
|
self._table.setSortingEnabled(True)
|
||||||
vp.setUpdatesEnabled(True)
|
vp.setUpdatesEnabled(True)
|
||||||
|
|||||||
Reference in New Issue
Block a user