diff --git a/gui/sky_tab.py b/gui/sky_tab.py index bedfb89..b790c82 100644 --- a/gui/sky_tab.py +++ b/gui/sky_tab.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import ( QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView, QSplitter, QGroupBox, ) -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QColor, QBrush from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS @@ -24,7 +24,8 @@ class SkyTab(QWidget): def __init__(self, model: ReceiverManager): super().__init__() 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.setContentsMargins(4, 4, 4, 4) @@ -39,7 +40,7 @@ class SkyTab(QWidget): self._src_list.setFlow(QListWidget.LeftToRight) self._src_list.setWrapping(True) 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) outer.addWidget(src_box) @@ -63,6 +64,12 @@ class SkyTab(QWidget): splitter.setStretchFactor(1, 1) 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.receiver_added.connect(self._on_receiver_added) 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] for k in keys: del self._store[k] - self._refresh() + self._refresh_immediate() def _selected_rids(self) -> set: result = set() @@ -95,27 +102,51 @@ class SkyTab(QWidget): result.add(item.data(Qt.UserRole)) 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 ─────────────────────────────────────────────────────────── def _on_sat_update(self, rid: str, sats: list): for sat in sats: self._store[(rid, sat.gnss_id, sat.sv_id)] = sat - self._refresh() - - def _refresh(self): - selected = self._selected_rids() - visible = [(rid, self._store[k]) - for k in self._store - if (rid := k[0]) in selected] + # Sky widget: always submit latest (render is off-thread, trivially cheap) + self._sky.update_data(self._visible_sats()) + # Table: schedule a rebuild if one isn't already pending + if not self._table_timer.isActive(): + self._table_timer.start() + 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._rebuild_table(visible) + def _rebuild_table_from_store(self): + self._rebuild_table(self._visible_sats()) + # ── Table ───────────────────────────────────────────────────────────────── def _rebuild_table(self, sats: list): 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.setRowCount(len(sorted_sats)) @@ -140,3 +171,4 @@ class SkyTab(QWidget): self._table.setItem(row, 6, cell(rid)) self._table.setSortingEnabled(True) + vp.setUpdatesEnabled(True)