feat(gui): FreeDV Reporter window with live station table, band/freq filter, and track-slice mode#3439
Conversation
|
Hi @NF0T — thanks for the FreeDV Reporter dialog, that's a great addition. 👋 I dug into the failing Qt Accessibility Static Analysis check on What I checkedRan Exit 0, zero findings. The "Comment with accessibility summary" sub-step on the failed run is also marked skipped (its Why the step still failedBug is in COUNT=$(grep -c "^::warning" /tmp/a11y_output.txt || echo "0")
echo "findings=$COUNT" >> $GITHUB_OUTPUTWhen there are zero warnings, …to A one-liner fix would be Bottom line for this PR
Nothing for you to change here. Sit tight on the other checks. 73 and thanks for contributing! 🤖 aethersdr-agent · cost: $22.2425 · model: claude-opus-4-7 |
…le. Principle V.
Non-modal resizable dialog under View → FreeDV Reporter showing all active
qso.freedv.org stations in a 13-column sortable table (Callsign, Locator,
km, Hdg, Version, MHz, TX Mode, Status, Message, Last TX, RX Call, SNR,
Last Update). Band/frequency filter with Track mode follows the active VFO
slice. Per-session sort on any column.
Row highlight palette matches freedv-gui reference:
- Orange (#fc4500): live TX — set/cleared exclusively by tx_report
- Teal (#379baf): RX receiving — 5 s timeout after rx_reports stop;
cleared immediately when EOO callsign is decoded
- Purple (#e58be5): new station arrival or message update — 5 s timeout
- 250 ms QTimer drives expiry; black foreground on all highlights
State updates are decoupled from the SpotHub spot-emission gates so the
table reflects raw WebSocket protocol state, not the filtered spot feed.
Protocol correctness (Principle VIII — all verified against freedv-gui source):
- TX state owned exclusively by tx_report; freq_change never touches it
- bulk_update processes transmitting=false entries — no station stuck TX
- rx_only status immutable once set by new_connection
- Model reset on WebSocket disconnect prevents ghost rows on reconnect
- Dialog seeded from FreeDvClient cache on first open (lazy-construct safe)
- GPS-aware my-grid resolved at open time, not read from AppSettings directly
New files: src/core/MaidenheadLocator.h, src/gui/FreeDvReporterModel.h/.cpp,
src/gui/FreeDvReporterDialog.h/.cpp
Modified: src/core/FreeDvClient.h/.cpp, src/gui/MainWindow.h/.cpp,
CMakeLists.txt
Principle V (config under AppSettings["FreeDvReporter"]).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
12cb723 to
5e0e0d3
Compare
|
Rebased your branch onto current main (`5e0e0d32`) to pick up #3442's a11y workflow fix. Your code didn't trigger any a11y findings — the lint scanned 6 files and reported 0 findings, then the workflow step crashed writing the result to `$GITHUB_OUTPUT`. What the failing run actually showed``` Root causeThe pre-#3442 workflow had: When `grep -c` finds zero matches it exits 1 and prints `0` to stdout, so `|| echo "0"` appended a second `0`, making `COUNT=$'0\\n0'`. The next line `echo "findings=$COUNT" >> $GITHUB_OUTPUT` wrote a two-line value that the GITHUB_OUTPUT parser rejected with `Invalid format '0'` — and the step failed even though the lint had already succeeded. #3442 fixed this by changing `|| echo "0"` to `|| true`, so `COUNT` ends up as just `0` on the zero-findings case. Timing: your CI run was 2026-06-06 19:41 UTC, #3442 merged 2026-06-06 21:44 UTC — your run hit the workflow file from your branch (workflows run from PR-branch state on `pull_request` events), which was pre-fix. What I didSame maintainer-fix-up pattern as #3279/#3286/#3289/#3381/#3391/#3397/#3398/#3417 — rebased your single commit onto current main, no content changes. Verified locally on Arch Linux x86: full `AetherSDR` build clean (634/635, only the pre-existing unrelated `macDaxDriverInstalled` warning). CI will rerun against the new HEAD. Happy to revert any piece you'd rather take a different cut at. Principle XI. |
Two related fixes to TitleBar's new PanLock accessors so all four platforms compile again: 1. signals: vs. public: — MOC parses anything inside a `signals:` block as a signal declaration, so the inline `bool isPanFollowChecked() const` and `void setPanFollowChecked(bool)` were rejected at moc time with "Not a signal declaration" (TitleBar.h:56). Both methods are accessors, not signals — they belong in `public:`. 2. Out-of-line over inline. Even after the section move, the inline bodies referenced `m_panFollowBtn->isChecked()` and `QSignalBlocker`, which need the full QPushButton type. The header only forward-declares QPushButton, so inline bodies couldn't compile in callers either (the moc error masked this until now). Moving the definitions to TitleBar.cpp keeps the header light and matches the style of neighbours like `isSystemMoveAreaAt`. Build verified locally on Arch Linux x86 — 632/632 clean (only the pre-existing unrelated macDaxDriverInstalled warning). Same maintainer fix-up pattern as aethersdr#3279/aethersdr#3286/aethersdr#3289/aethersdr#3381/aethersdr#3398/aethersdr#3417/aethersdr#3439/aethersdr#3441. Principle XI.
Two related fixes to TitleBar's new PanLock accessors so all four platforms compile again: 1. signals: vs. public: — MOC parses anything inside a `signals:` block as a signal declaration, so the inline `bool isPanFollowChecked() const` and `void setPanFollowChecked(bool)` were rejected at moc time with "Not a signal declaration" (TitleBar.h:56). Both methods are accessors, not signals — they belong in `public:`. 2. Out-of-line over inline. Even after the section move, the inline bodies referenced `m_panFollowBtn->isChecked()` and `QSignalBlocker`, which need the full QPushButton type. The header only forward-declares QPushButton, so inline bodies couldn't compile in callers either (the moc error masked this until now). Moving the definitions to TitleBar.cpp keeps the header light and matches the style of neighbours like `isSystemMoveAreaAt`. Build verified locally on Arch Linux x86 — 632/632 clean (only the pre-existing unrelated macDaxDriverInstalled warning). Same maintainer fix-up pattern as aethersdr#3279/aethersdr#3286/aethersdr#3289/aethersdr#3381/aethersdr#3398/aethersdr#3417/aethersdr#3439/aethersdr#3441. Principle XI.
… ThemeManager tokens. Principle IX. (#3645) ## Summary Follow-up to #3439 and #3441. Fills three hardcoded-colour gaps in the theme token system — the last surfaces in the FreeDV Reporter dialog and Radio Setup dialog that hadn't been migrated to ThemeManager. **FreeDvReporterModel — row highlight colours** - Removes four hardcoded `QColor` constants (`kTxBg` / `kRxBg` / `kMsgBg` / `kHlFg`) and replaces their three use sites in `data()` with `ThemeManager::instance().color(token)` calls. - Adds a `themeChanged` → `dataChanged{BackgroundRole, ForegroundRole}` connection in the constructor so highlighted rows repaint immediately on a theme switch instead of waiting for the next 250 ms highlight tick. - Note: `color.background.tx` (`#3a2a0e`) is a chrome-panel tint used in 35+ surfaces and could not be reused here. The TX-row highlight (`#fc4500`) is a distinct high-visibility colour matching the freedv-gui reference palette, so new tokens were required for all four constants. **RadioSetupDialog — button disabled states** - Migrates three `QPushButton:disabled` CSS blocks to tokens. - Reboot Radio button (`buildRadioTab` ~line 601): uses new `color.button.danger.*disabled` tokens — preserves the intentional danger-red colouring in the disabled state. - Upload Firmware button (`buildRadioTab` ~line 838): was using `{{color.meter.bar.fill}}` and `{{color.background.1}}` as disabled-state stand-ins (semantically wrong tokens from a prior partial migration). Now uses correct `color.button.*disabled` tokens. - Serial tab Open/Close port buttons (`buildSerialTab` ~line 4054): hardcoded hex replaced with `color.button.*disabled` tokens; `setStyleSheet` calls replaced with `applyStyleSheet` so the template engine resolves tokens and the widgets participate in live theme switching. **New tokens — 10 total across both theme files** | Token | Dark | Light | |---|---|---| | `color.highlight.tx` | `#fc4500` | `#fc4500` | | `color.highlight.rx` | `#379baf` | `#379baf` | | `color.highlight.message` | `#e58be5` | `#e58be5` | | `color.highlight.fg` | `#000000` | `#000000` | | `color.button.background.disabled` | `#203040` | `{color.gray.700}` | | `color.button.foreground.disabled` | `{color.gray.500}` | `{color.gray.500}` | | `color.button.border.disabled` | `{color.gray.700}` | `{color.gray.600}` | | `color.button.danger.background.disabled` | `#2a1818` | `#f0d8d8` | | `color.button.danger.foreground.disabled` | `#8a6055` | `#b08080` | | `color.button.danger.border.disabled` | `#4a2828` | `#d0a8a8` | Highlight colours are kept identical between dark and light themes — they are high-saturation reference palette colours from freedv-gui that provide adequate contrast with the black foreground on both backgrounds. A future theme author can override them via the token. Fixes #3446. ## Test plan - [ ] Build clean on Windows (MinGW), no new warnings - [ ] FreeDV Reporter: open dialog, observe another station transmitting — TX row shows orange background with black text - [ ] FreeDV Reporter: RX highlight (teal) and message highlight (purple) appear and expire after ~5 s - [ ] FreeDV Reporter: switch theme while a highlight is active — row repaints immediately (does not wait for the 250 ms tick) - [ ] Radio Setup → General tab: disconnect radio, confirm Reboot Radio button disabled state is dark red-tinted (not generic grey) - [ ] Radio Setup → General tab: Upload Firmware button renders correct disabled state when no firmware check has run - [ ] Radio Setup → Serial tab: open a port config, confirm Open/Close buttons show correct disabled styling when not applicable - [ ] Light theme: open FreeDV Reporter and Radio Setup, confirm all three token groups (highlight, button, danger) render legibly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…filter, and track-slice mode (aethersdr#3439) ## Summary Adds a **FreeDV Reporter window** (View menu → FreeDV Reporter) that shows the live station table from `qso.freedv.org` — the same data the existing spot source feeds into the DX cluster overlay, now surfaced in a dedicated resizable window. **`FreeDvReporterDialog`** (`src/gui/`) — `PersistentDialog` subclass with: - `QTableView` driven by `FreeDvReporterModel` (13 columns: callsign, locator, distance km, bearing °, software version, frequency, mode, status, station message, last TX time, last heard callsign, SNR, last update) - Band filter row (160m / 80m / 40m / 20m / 15m / 10m / 6m / 2m / 70cm / All) — single-click, exclusive - Two filter modes: **Band** (by band edge) and **Freq** (±10 kHz of active slice) - **Track active slice** checkbox — auto-follows the slice frequency when Freq mode is active - Geometry and filter state persist via `AppSettings` nested-JSON (Principle V) - Frameless-chrome aware (`FramelessWindowTitleBar` + `FramelessResizer::install`) **`FreeDvReporterModel`** (`src/gui/`) — `QAbstractTableModel` with: - Distance and bearing computed from operator grid (Maidenhead) via new `MaidenheadLocator.h` - Timed highlight system: RX events highlight for 5 s, message updates highlight for 5 s; TX status uses live `info.status` directly - `m_highlightTimer` ticks at 1 s to expire stale highlights without full-model resets **`FreeDvClient` extensions** (`src/core/`): - `StationInfo` struct promoted from private to public — consumed directly by the model and dialog - New signals: `stationUpdated(sid, info)`, `stationRemoved(sid)`, `stationsCleared()` - `m_inBulkUpdate` flag prevents spot emission during `bulk_update` replay (spots only on live TX start) - `onMessageUpdate()` handler for `message_update` Socket.IO events (was silently ignored) - `stationUpdated` emitted from all event handlers so the model stays current without polling **`MainWindow`** wiring: dialog constructed lazily on first open, wired to `FreeDvClient` signals and active-slice changes. ## Constitution principle honored Principle V — New Configuration Uses Nested JSON Per Feature, Not Flat AppSettings. All dialog settings (band filter index, filter mode, track-slice toggle, window geometry) are stored under a single `AppSettings["FreeDvReporterDialog"]` JSON blob. ## Test plan - [x] Build passes — MinGW GCC 13.1.0 / Qt 6.11.0, verified locally - [x] Build passes with `HAVE_WEBSOCKETS` undefined — all new GUI code is gated `#ifdef HAVE_WEBSOCKETS` - [ ] FreeDV Reporter dialog opens from View menu and populates stations when connected to `qso.freedv.org` - [ ] Band filter correctly shows/hides stations by band edge - [ ] Freq filter + Track mode follows active slice frequency - [ ] TX/RX/highlight states update live during a FreeDV QSO - [ ] Window geometry and filter state persist across app restart - [ ] Existing tests pass (CI) ## Checklist - [x] Commits are signed (`docs/COMMIT-SIGNING.md`) - [x] No new flat-key `AppSettings` calls — nested-JSON under `FreeDvReporterDialog` (Principle V) - [x] All meter UI uses `MeterSmoother` — N/A - [x] Documentation updated if user-visible behavior changed — new View menu entry, no separate doc needed - [x] Security-sensitive changes reference a GHSA if applicable — N/A > **Note:** `src/gui/MainWindow.{h,cpp}` are in the maintainer-only CODEOWNERS tier — this PR requires @ten9876 approval. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
Adds a FreeDV Reporter window (View menu → FreeDV Reporter) that shows the live station table from
qso.freedv.org— the same data the existing spot source feeds into the DX cluster overlay, now surfaced in a dedicated resizable window.FreeDvReporterDialog(src/gui/) —PersistentDialogsubclass with:QTableViewdriven byFreeDvReporterModel(13 columns: callsign, locator, distance km, bearing °, software version, frequency, mode, status, station message, last TX time, last heard callsign, SNR, last update)AppSettingsnested-JSON (Principle V)FramelessWindowTitleBar+FramelessResizer::install)FreeDvReporterModel(src/gui/) —QAbstractTableModelwith:MaidenheadLocator.hinfo.statusdirectlym_highlightTimerticks at 1 s to expire stale highlights without full-model resetsFreeDvClientextensions (src/core/):StationInfostruct promoted from private to public — consumed directly by the model and dialogstationUpdated(sid, info),stationRemoved(sid),stationsCleared()m_inBulkUpdateflag prevents spot emission duringbulk_updatereplay (spots only on live TX start)onMessageUpdate()handler formessage_updateSocket.IO events (was silently ignored)stationUpdatedemitted from all event handlers so the model stays current without pollingMainWindowwiring: dialog constructed lazily on first open, wired toFreeDvClientsignals and active-slice changes.Constitution principle honored
Principle V — New Configuration Uses Nested JSON Per Feature, Not Flat AppSettings. All dialog settings (band filter index, filter mode, track-slice toggle, window geometry) are stored under a single
AppSettings["FreeDvReporterDialog"]JSON blob.Test plan
HAVE_WEBSOCKETSundefined — all new GUI code is gated#ifdef HAVE_WEBSOCKETSqso.freedv.orgChecklist
docs/COMMIT-SIGNING.md)AppSettingscalls — nested-JSON underFreeDvReporterDialog(Principle V)MeterSmoother— N/A