sky tab: decouple table rebuilds from sky updates, update cells in-place
- Sky widget always gets the latest data immediately (off-thread render) - Table rebuilds throttled to ~2 Hz via single-shot QTimer - Cell values updated in-place when satellite set is unchanged, preserving scroll position; full rebuild only when satellites appear/disappear Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+43
-11
@@ -11,7 +11,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
||||||
QSplitter, QGroupBox,
|
QSplitter, QGroupBox,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, QTimer
|
||||||
from PyQt5.QtGui import QColor, QBrush
|
from PyQt5.QtGui import QColor, QBrush
|
||||||
|
|
||||||
from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS
|
from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS
|
||||||
@@ -24,7 +24,8 @@ class SkyTab(QWidget):
|
|||||||
def __init__(self, model: ReceiverManager):
|
def __init__(self, model: ReceiverManager):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._model = model
|
self._model = model
|
||||||
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
||||||
|
self._table_keys: list = [] # current row order as (rid, gnss_id, sv_id)
|
||||||
|
|
||||||
outer = QVBoxLayout(self)
|
outer = QVBoxLayout(self)
|
||||||
outer.setContentsMargins(4, 4, 4, 4)
|
outer.setContentsMargins(4, 4, 4, 4)
|
||||||
@@ -39,7 +40,7 @@ class SkyTab(QWidget):
|
|||||||
self._src_list.setFlow(QListWidget.LeftToRight)
|
self._src_list.setFlow(QListWidget.LeftToRight)
|
||||||
self._src_list.setWrapping(True)
|
self._src_list.setWrapping(True)
|
||||||
self._src_list.setResizeMode(QListWidget.Adjust)
|
self._src_list.setResizeMode(QListWidget.Adjust)
|
||||||
self._src_list.itemChanged.connect(self._refresh)
|
self._src_list.itemChanged.connect(self._refresh_immediate)
|
||||||
src_bl.addWidget(self._src_list)
|
src_bl.addWidget(self._src_list)
|
||||||
outer.addWidget(src_box)
|
outer.addWidget(src_box)
|
||||||
|
|
||||||
@@ -63,6 +64,12 @@ class SkyTab(QWidget):
|
|||||||
splitter.setStretchFactor(1, 1)
|
splitter.setStretchFactor(1, 1)
|
||||||
outer.addWidget(splitter)
|
outer.addWidget(splitter)
|
||||||
|
|
||||||
|
# ── Table throttle timer (max ~2 Hz rebuilds) ─────────────────────────
|
||||||
|
self._table_timer = QTimer(self)
|
||||||
|
self._table_timer.setSingleShot(True)
|
||||||
|
self._table_timer.setInterval(500)
|
||||||
|
self._table_timer.timeout.connect(self._rebuild_table_from_store)
|
||||||
|
|
||||||
# ── Model signals ─────────────────────────────────────────────────────
|
# ── Model signals ─────────────────────────────────────────────────────
|
||||||
model.receiver_added.connect(self._on_receiver_added)
|
model.receiver_added.connect(self._on_receiver_added)
|
||||||
model.receiver_removed.connect(self._on_receiver_removed)
|
model.receiver_removed.connect(self._on_receiver_removed)
|
||||||
@@ -85,7 +92,7 @@ class SkyTab(QWidget):
|
|||||||
keys = [k for k in self._store if k[0] == rid]
|
keys = [k for k in self._store if k[0] == rid]
|
||||||
for k in keys:
|
for k in keys:
|
||||||
del self._store[k]
|
del self._store[k]
|
||||||
self._refresh()
|
self._refresh_immediate()
|
||||||
|
|
||||||
def _selected_rids(self) -> set:
|
def _selected_rids(self) -> set:
|
||||||
result = set()
|
result = set()
|
||||||
@@ -95,27 +102,51 @@ class SkyTab(QWidget):
|
|||||||
result.add(item.data(Qt.UserRole))
|
result.add(item.data(Qt.UserRole))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _visible_sats(self) -> list:
|
||||||
|
selected = self._selected_rids()
|
||||||
|
return [(k[0], self._store[k])
|
||||||
|
for k in self._store if k[0] in selected]
|
||||||
|
|
||||||
# ── Data update ───────────────────────────────────────────────────────────
|
# ── Data update ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _on_sat_update(self, rid: str, sats: list):
|
def _on_sat_update(self, rid: str, sats: list):
|
||||||
for sat in sats:
|
for sat in sats:
|
||||||
self._store[(rid, sat.gnss_id, sat.sv_id)] = sat
|
self._store[(rid, sat.gnss_id, sat.sv_id)] = sat
|
||||||
self._refresh()
|
# Sky widget: always submit latest (render is off-thread, trivially cheap)
|
||||||
|
self._sky.update_data(self._visible_sats())
|
||||||
def _refresh(self):
|
# Table: schedule a rebuild if one isn't already pending
|
||||||
selected = self._selected_rids()
|
if not self._table_timer.isActive():
|
||||||
visible = [(rid, self._store[k])
|
self._table_timer.start()
|
||||||
for k in self._store
|
|
||||||
if (rid := k[0]) in selected]
|
|
||||||
|
|
||||||
|
def _refresh_immediate(self):
|
||||||
|
"""Called on source-checkbox toggle or receiver removal — rebuild now."""
|
||||||
|
self._table_timer.stop()
|
||||||
|
visible = self._visible_sats()
|
||||||
self._sky.update_data(visible)
|
self._sky.update_data(visible)
|
||||||
self._rebuild_table(visible)
|
self._rebuild_table(visible)
|
||||||
|
|
||||||
|
def _rebuild_table_from_store(self):
|
||||||
|
self._rebuild_table(self._visible_sats())
|
||||||
|
|
||||||
# ── Table ─────────────────────────────────────────────────────────────────
|
# ── Table ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _rebuild_table(self, sats: list):
|
def _rebuild_table(self, sats: list):
|
||||||
sorted_sats = sorted(sats, key=lambda x: (x[1].gnss_id, x[1].sv_id))
|
sorted_sats = sorted(sats, key=lambda x: (x[1].gnss_id, x[1].sv_id))
|
||||||
|
new_keys = [(rid, sat.gnss_id, sat.sv_id) for rid, sat in sorted_sats]
|
||||||
|
|
||||||
|
if new_keys == self._table_keys:
|
||||||
|
# Only values changed — update in place, scroll position untouched
|
||||||
|
for row, (_, sat) in enumerate(sorted_sats):
|
||||||
|
self._table.item(row, 2).setText(f"{sat.elevation:.0f}")
|
||||||
|
self._table.item(row, 3).setText(f"{sat.azimuth:.0f}")
|
||||||
|
self._table.item(row, 4).setText(f"{sat.cno:.1f}")
|
||||||
|
self._table.item(row, 5).setText("✓" if sat.used_in_fix else "")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Satellite set changed — full rebuild
|
||||||
|
self._table_keys = new_keys
|
||||||
|
vp = self._table.viewport()
|
||||||
|
vp.setUpdatesEnabled(False)
|
||||||
self._table.setSortingEnabled(False)
|
self._table.setSortingEnabled(False)
|
||||||
self._table.setRowCount(len(sorted_sats))
|
self._table.setRowCount(len(sorted_sats))
|
||||||
|
|
||||||
@@ -140,3 +171,4 @@ class SkyTab(QWidget):
|
|||||||
self._table.setItem(row, 6, cell(rid))
|
self._table.setItem(row, 6, cell(rid))
|
||||||
|
|
||||||
self._table.setSortingEnabled(True)
|
self._table.setSortingEnabled(True)
|
||||||
|
vp.setUpdatesEnabled(True)
|
||||||
|
|||||||
Reference in New Issue
Block a user