diff --git a/gui/sky_tab.py b/gui/sky_tab.py index 9f114bf..af22764 100644 --- a/gui/sky_tab.py +++ b/gui/sky_tab.py @@ -13,7 +13,7 @@ from PyQt5.QtWidgets import ( QSplitter, QGroupBox, QFrame, QCheckBox, QSpinBox, ) from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtGui import QColor, QBrush +from PyQt5.QtGui import QColor, QBrush, QIcon, QPixmap from data_model import ReceiverManager, GNSS_NAMES, GNSS_COLORS from sky_widget import SkyPlotWidget @@ -37,6 +37,8 @@ class SkyTab(QWidget): self._name_labels: dict = {} # rid -> QLabel (receiver display name) self._trail: dict = {} # (rid, gnss_id, sv_id) -> deque[(elev, azim)] self._hi_timers: dict = {} # (rid, gnss_id, sv_id) -> QTimer + self._gnss_checks: dict = {} # gnss_id -> QCheckBox + self._enabled_gnss: set = set(GNSS_NAMES.keys()) outer = QVBoxLayout(self) outer.setContentsMargins(4, 4, 4, 4) @@ -68,7 +70,28 @@ class SkyTab(QWidget): self._trail_spin.setEnabled(False) self._trail_spin.valueChanged.connect(self._on_trail_toggled) src_bl.addWidget(self._trail_spin) - outer.addWidget(src_box) + + # ── Constellation legend / filter ───────────────────────────────────── + const_box = QGroupBox("Constellations (check to show)") + const_box.setMaximumHeight(80) + const_bl = QHBoxLayout(const_box) + const_bl.setContentsMargins(4, 2, 4, 2) + const_bl.setSpacing(8) + for gid in sorted(GNSS_NAMES): + cb = QCheckBox(GNSS_NAMES[gid]) + cb.setChecked(True) + pix = QPixmap(10, 10) + pix.fill(QColor(GNSS_COLORS.get(gid, '#9E9E9E'))) + cb.setIcon(QIcon(pix)) + cb.toggled.connect(lambda checked, g=gid: self._on_gnss_toggled(g, checked)) + self._gnss_checks[gid] = cb + const_bl.addWidget(cb) + const_bl.addStretch() + + filter_row = QHBoxLayout() + filter_row.addWidget(src_box) + filter_row.addWidget(const_box) + outer.addLayout(filter_row) # ── Info bar (one card per receiver) ────────────────────────────────── info_frame = QFrame() @@ -181,7 +204,7 @@ class SkyTab(QWidget): 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] + for k in self._store if k[0] in selected and k[1] in self._enabled_gnss] def _active_trails(self) -> dict: if not self._trail_cb.isChecked(): @@ -192,7 +215,14 @@ class SkyTab(QWidget): selected = self._selected_rids() return {key: list(dq)[-n_points:] for key, dq in self._trail.items() - if key[0] in selected} + if key[0] in selected and key[1] in self._enabled_gnss} + + def _on_gnss_toggled(self, gid: int, checked: bool): + if checked: + self._enabled_gnss.add(gid) + else: + self._enabled_gnss.discard(gid) + self._refresh_immediate() # ── Trail toggle ────────────────────────────────────────────────────────── diff --git a/gui/sky_widget.py b/gui/sky_widget.py index 801ddd6..23bc28e 100644 --- a/gui/sky_widget.py +++ b/gui/sky_widget.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QWidget, QSizePolicy, QApplication from PyQt5.QtGui import (QPainter, QColor, QPen, QBrush, QFont, QImage) from PyQt5.QtCore import Qt, QPointF, QRectF, QThread, pyqtSignal -from data_model import GNSS_NAMES, GNSS_COLORS, GNSS_SHORT +from data_model import GNSS_COLORS, GNSS_SHORT # ── Static colors (created once in main thread, read-only in render thread) ── _C_BG = QColor('#1a1a2e') @@ -92,7 +92,6 @@ class SkyRenderThread(QThread): if trails: self._draw_trails(p, cx, cy, max_r, trails) self._draw_satellites(p, cx, cy, max_r, sats) - self._draw_legend(p, sats) p.end() return img @@ -184,24 +183,6 @@ class SkyRenderThread(QThread): p.drawText(QRectF(x + size + 1, y - 6, 28, 12), Qt.AlignLeft, f"{short}{sat.sv_id}") - def _draw_legend(self, p: QPainter, sats): - used = {sat.gnss_id for _, sat in sats} - if not used: - return - f = QFont(); f.setPointSize(8) - p.setFont(f) - lx, ly = 8, 8 - for gid in sorted(used): - color = _GNSS_QCOLORS.get(gid) - if color is None: - continue - p.setBrush(QBrush(color)) - p.setPen(Qt.NoPen) - p.drawEllipse(QPointF(lx + 5, ly + 6), 5, 5) - p.setPen(QPen(_C_SAT_LBL)) - p.drawText(lx + 14, ly + 11, GNSS_NAMES.get(gid, f'SYS{gid}')) - ly += 18 - # ── Widget ────────────────────────────────────────────────────────────────────