From 7fe6c34fd252d0ccefde8ffa81565a2051070249 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sun, 24 May 2026 14:55:31 +0200 Subject: [PATCH] sky tab: replace per-satellite trail toggle with global 'Show Trails' checkbox Removed Trail column from satellite table. Added a single 'Show Trails' checkbox in the source selector box that enables/disables all trails at once. Co-Authored-By: Claude Sonnet 4.6 --- gui/sky_tab.py | 104 ++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/gui/sky_tab.py b/gui/sky_tab.py index ec5da5f..a13688d 100644 --- a/gui/sky_tab.py +++ b/gui/sky_tab.py @@ -1,4 +1,4 @@ -import sys, os, time +import sys, os from collections import deque _root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -10,7 +10,7 @@ for _p in [_root, _gui]: from PyQt5.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget, QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView, - QSplitter, QGroupBox, QFrame, + QSplitter, QGroupBox, QFrame, QCheckBox, ) from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QColor, QBrush @@ -18,8 +18,7 @@ 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', 'Trail'] -_COL_TRAIL = len(_COL_HEADERS) - 1 +_COL_HEADERS = ['GNSS', 'SV', 'Elev °', 'Az °', 'C/N₀', 'Fix', 'Receiver'] _TRAIL_MAXLEN = 3600 # 1 hour at 1 Hz @@ -27,29 +26,34 @@ class SkyTab(QWidget): def __init__(self, model: ReceiverManager): super().__init__() self._model = model - self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData - self._table_keys: list = [] # current row order as (rid, gnss_id, sv_id) - self._pdop: dict = {} # rid -> float - 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._trail_enabled: dict = {} # (rid, gnss_id, sv_id) -> bool + self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData + self._table_keys: list = [] # current row order as (rid, gnss_id, sv_id) + self._pdop: dict = {} # rid -> float + 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)] outer = QVBoxLayout(self) outer.setContentsMargins(4, 4, 4, 4) outer.setSpacing(4) - # ── Source selector ─────────────────────────────────────────────────── + # ── Source selector + trail toggle ──────────────────────────────────── src_box = QGroupBox("Receiver Sources (check to include)") src_box.setMaximumHeight(80) src_bl = QHBoxLayout(src_box) src_bl.setContentsMargins(4, 2, 4, 2) + src_bl.setSpacing(8) self._src_list = QListWidget() self._src_list.setFlow(QListWidget.LeftToRight) self._src_list.setWrapping(True) self._src_list.setResizeMode(QListWidget.Adjust) self._src_list.itemChanged.connect(self._refresh_immediate) src_bl.addWidget(self._src_list) + + self._trail_cb = QCheckBox("Show Trails") + self._trail_cb.setChecked(False) + self._trail_cb.toggled.connect(self._on_trail_toggled) + src_bl.addWidget(self._trail_cb) outer.addWidget(src_box) # ── Info bar (one card per receiver) ────────────────────────────────── @@ -73,16 +77,10 @@ class SkyTab(QWidget): self._table.setHorizontalHeaderLabels(_COL_HEADERS) hdr = self._table.horizontalHeader() hdr.setSectionResizeMode(QHeaderView.ResizeToContents) - hdr.setSectionResizeMode(_COL_TRAIL, QHeaderView.Fixed) - hdr.setStretchLastSection(False) - hdr.sectionClicked.connect(self._on_header_clicked) - self._table.setColumnWidth(_COL_TRAIL, 42) - self._table.horizontalHeader().setToolTip( - "Click 'Trail' header to select / deselect all") + hdr.setStretchLastSection(True) self._table.setEditTriggers(QTableWidget.NoEditTriggers) self._table.setSelectionBehavior(QTableWidget.SelectRows) self._table.setAlternatingRowColors(False) - self._table.itemChanged.connect(self._on_table_item_changed) splitter.addWidget(self._table) splitter.setStretchFactor(0, 2) @@ -101,7 +99,7 @@ class SkyTab(QWidget): model.satellite_update.connect(self._on_sat_update) model.pdop_update.connect(self._on_pdop_update) - # ── Source list management ───────────────────────────────────────────���──── + # ── Source list management ──────────────────────────────────────────────── def _on_receiver_added(self, rid: str): item = QListWidgetItem(f" {rid} ") @@ -130,9 +128,8 @@ class SkyTab(QWidget): if self._src_list.item(i).data(Qt.UserRole) == rid: self._src_list.takeItem(i) break - for k in [k for k in self._store if k[0] == rid]: del self._store[k] - for k in [k for k in self._trail if k[0] == rid]: del self._trail[k] - for k in [k for k in self._trail_enabled if k[0] == rid]: del self._trail_enabled[k] + for k in [k for k in self._store if k[0] == rid]: del self._store[k] + for k in [k for k in self._trail if k[0] == rid]: del self._trail[k] self._pdop.pop(rid, None) card = self._info_cards.pop(rid, None) if card: @@ -155,12 +152,17 @@ class SkyTab(QWidget): for k in self._store if k[0] in selected] def _active_trails(self) -> dict: + if not self._trail_cb.isChecked(): + return {} selected = self._selected_rids() - result = {} - for key, dq in self._trail.items(): - if key[0] in selected and self._trail_enabled.get(key, False): - result[key] = list(dq) - return result + return {key: list(dq) + for key, dq in self._trail.items() + if key[0] in selected} + + # ── Trail toggle ────────────────────────────────────────────────────────── + + def _on_trail_toggled(self, _checked: bool): + self._sky.update_data(self._visible_sats(), self._active_trails()) # ── Info bar ────────────────────────────────────────────────────────────── @@ -203,39 +205,6 @@ class SkyTab(QWidget): def _rebuild_table_from_store(self): self._rebuild_table(self._visible_sats()) - # ── Trail toggle ────────────────────────────────────────────────────────── - - def _on_table_item_changed(self, item): - if item.column() != _COL_TRAIL: - return - key = item.data(Qt.UserRole) - if key is None: - return - self._trail_enabled[key] = (item.checkState() == Qt.Checked) - self._sky.update_data(self._visible_sats(), self._active_trails()) - - def _on_header_clicked(self, col: int): - if col != _COL_TRAIL: - return - # Enable all if any are off, otherwise disable all - rows = self._table.rowCount() - any_off = any( - self._table.item(r, _COL_TRAIL).checkState() != Qt.Checked - for r in range(rows) - if self._table.item(r, _COL_TRAIL) - ) - new_state = Qt.Checked if any_off else Qt.Unchecked - self._table.blockSignals(True) - for r in range(rows): - item = self._table.item(r, _COL_TRAIL) - if item: - key = item.data(Qt.UserRole) - if key: - self._trail_enabled[key] = (new_state == Qt.Checked) - item.setCheckState(new_state) - self._table.blockSignals(False) - self._sky.update_data(self._visible_sats(), self._active_trails()) - # ── Table ───────────────────────────────────────────────────────────────── def _rebuild_table(self, sats: list): @@ -244,25 +213,21 @@ class SkyTab(QWidget): if new_keys == self._table_keys: # Only values changed — update in place, scroll position untouched - self._table.blockSignals(True) 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 "") - self._table.blockSignals(False) return # Satellite set changed — full rebuild self._table_keys = new_keys - self._table.blockSignals(True) vp = self._table.viewport() vp.setUpdatesEnabled(False) self._table.setSortingEnabled(False) self._table.setRowCount(len(sorted_sats)) for row, (rid, sat) in enumerate(sorted_sats): - key = (rid, sat.gnss_id, sat.sv_id) gnss_name = GNSS_NAMES.get(sat.gnss_id, f'SYS{sat.gnss_id}') bg = QColor(GNSS_COLORS.get(sat.gnss_id, '#9E9E9E')) bg.setAlpha(55) @@ -282,14 +247,5 @@ class SkyTab(QWidget): self._table.setItem(row, 5, cell("✓" if sat.used_in_fix else "")) self._table.setItem(row, 6, cell(rid)) - trail_item = QTableWidgetItem() - trail_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) - trail_item.setCheckState( - Qt.Checked if self._trail_enabled.get(key, False) else Qt.Unchecked) - trail_item.setData(Qt.UserRole, key) - trail_item.setTextAlignment(Qt.AlignCenter) - self._table.setItem(row, _COL_TRAIL, trail_item) - self._table.setSortingEnabled(True) vp.setUpdatesEnabled(True) - self._table.blockSignals(False)