added gui
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
import sys, os
|
||||
|
||||
_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
_gui = os.path.dirname(os.path.abspath(__file__))
|
||||
for _p in [_root, _gui]:
|
||||
if _p not in sys.path:
|
||||
sys.path.insert(0, _p)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget,
|
||||
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QSplitter, QGroupBox,
|
||||
)
|
||||
from PyQt5.QtCore import Qt
|
||||
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']
|
||||
|
||||
|
||||
class SkyTab(QWidget):
|
||||
def __init__(self, model: ReceiverManager):
|
||||
super().__init__()
|
||||
self._model = model
|
||||
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(4, 4, 4, 4)
|
||||
outer.setSpacing(4)
|
||||
|
||||
# ── Source selector ───────────────────────────────────────────────────
|
||||
src_box = QGroupBox("Receiver Sources (check to include)")
|
||||
src_box.setMaximumHeight(80)
|
||||
src_bl = QHBoxLayout(src_box)
|
||||
src_bl.setContentsMargins(4, 2, 4, 2)
|
||||
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)
|
||||
src_bl.addWidget(self._src_list)
|
||||
outer.addWidget(src_box)
|
||||
|
||||
# ── Sky plot + satellite table ─────────────────────────────────────────
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
self._sky = SkyPlotWidget()
|
||||
splitter.addWidget(self._sky)
|
||||
|
||||
self._table = QTableWidget()
|
||||
self._table.setColumnCount(len(_COL_HEADERS))
|
||||
self._table.setHorizontalHeaderLabels(_COL_HEADERS)
|
||||
self._table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
self._table.horizontalHeader().setStretchLastSection(True)
|
||||
self._table.setEditTriggers(QTableWidget.NoEditTriggers)
|
||||
self._table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
self._table.setAlternatingRowColors(False)
|
||||
splitter.addWidget(self._table)
|
||||
|
||||
splitter.setStretchFactor(0, 2)
|
||||
splitter.setStretchFactor(1, 1)
|
||||
outer.addWidget(splitter)
|
||||
|
||||
# ── Model signals ─────────────────────────────────────────────────────
|
||||
model.receiver_added.connect(self._on_receiver_added)
|
||||
model.receiver_removed.connect(self._on_receiver_removed)
|
||||
model.satellite_update.connect(self._on_sat_update)
|
||||
|
||||
# ── Source list management ────────────────────────────────────────────────
|
||||
|
||||
def _on_receiver_added(self, rid: str):
|
||||
item = QListWidgetItem(f" {rid} ")
|
||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||
item.setCheckState(Qt.Checked)
|
||||
item.setData(Qt.UserRole, rid)
|
||||
self._src_list.addItem(item)
|
||||
|
||||
def _on_receiver_removed(self, rid: str):
|
||||
for i in range(self._src_list.count()):
|
||||
if self._src_list.item(i).data(Qt.UserRole) == rid:
|
||||
self._src_list.takeItem(i)
|
||||
break
|
||||
keys = [k for k in self._store if k[0] == rid]
|
||||
for k in keys:
|
||||
del self._store[k]
|
||||
self._refresh()
|
||||
|
||||
def _selected_rids(self) -> set:
|
||||
result = set()
|
||||
for i in range(self._src_list.count()):
|
||||
item = self._src_list.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
result.add(item.data(Qt.UserRole))
|
||||
return result
|
||||
|
||||
# ── 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]
|
||||
|
||||
self._sky.update_data(visible)
|
||||
self._rebuild_table(visible)
|
||||
|
||||
# ── Table ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def _rebuild_table(self, sats: list):
|
||||
sorted_sats = sorted(sats, key=lambda x: (x[1].gnss_id, x[1].sv_id))
|
||||
|
||||
self._table.setSortingEnabled(False)
|
||||
self._table.setRowCount(len(sorted_sats))
|
||||
|
||||
for row, (rid, sat) in enumerate(sorted_sats):
|
||||
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)
|
||||
brush = QBrush(bg)
|
||||
|
||||
def cell(text, br=brush):
|
||||
it = QTableWidgetItem(str(text))
|
||||
it.setTextAlignment(Qt.AlignCenter)
|
||||
it.setBackground(br)
|
||||
return it
|
||||
|
||||
self._table.setItem(row, 0, cell(gnss_name))
|
||||
self._table.setItem(row, 1, cell(sat.sv_id))
|
||||
self._table.setItem(row, 2, cell(f"{sat.elevation:.0f}"))
|
||||
self._table.setItem(row, 3, cell(f"{sat.azimuth:.0f}"))
|
||||
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, 6, cell(rid))
|
||||
|
||||
self._table.setSortingEnabled(True)
|
||||
Reference in New Issue
Block a user