Skip to content

feat(freedv-reporter): 6m+ band, multi-select, 3 fixes. Principles V, XIV.#3591

Open
NF0T wants to merge 1 commit into
aethersdr:mainfrom
NF0T:feat/freedv-reporter-6m-multiband
Open

feat(freedv-reporter): 6m+ band, multi-select, 3 fixes. Principles V, XIV.#3591
NF0T wants to merge 1 commit into
aethersdr:mainfrom
NF0T:feat/freedv-reporter-6m-multiband

Conversation

@NF0T

@NF0T NF0T commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-on omnibus PR to the FreeDV Reporter dialog, adding two user-facing enhancements and fixing three pre-existing bugs discovered during review and OTA testing.

Enhancements:

  • 6m+ catch-all band — adds a "6m+" button at index 9 covering 50 MHz–1300 MHz (6m, 2m, 70cm, 23cm), mirroring the "Other" filter in FreeDV-GUI. Without this, VHF/UHF RADE stations fell through to "All" with no way to isolate them.
  • Ctrl+click multi-band selection — plain click retains existing single-select behavior; Ctrl+click (⌘-click on macOS) toggles individual bands in/out of the active set. Deselecting the last active band reverts to "All" mode automatically. Tooltip on the Band radio button documents the modifier.

Bug fixes:

  • Fix A (testing): In Track + Freq mode, band buttons didn't update visually when the slice frequency changed (data filtering was correct; only the highlight was stranded). Root cause: the band-finding loop ran only in the Band branch of onSliceFrequencyChanged. Fixed by hoisting the loop before the Band/Freq split so syncButtonStates() fires in both branches.
  • Fix B (pre-existing): persistSettings() was called inside buildBody() (via applyBandFilter()) before restoreSettings() ran, overwriting the user's saved selection with defaults on every startup. Fixed with an m_initializing flag (pattern from MqttSettingsDialog) that gates all saves until construction completes.
  • Fix C (pre-existing): Switching to Band mode with Track enabled triggered three persistSettings() calls. Removed the redundant explicit trailing call from onBandModeToggled(); applyBandFilter() already persists.

Settings migration: Old format (bandIndex scalar, 10-band schema) is detected by the absence of bandVersion. Indices 0–8 (160m–10m) map 1:1. Old index 9 ("All") migrates to empty set → All mode. New format uses bandVersion: 1 + bandIndices JSON array (Principle V).

Files changed: src/gui/FreeDvReporterDialog.{h,cpp} only (default CODEOWNERS tier).

Principles V (nested-JSON config), XIV (atomic persistence).

Test plan

  • Build clean with HAVE_WEBSOCKETS enabled; zero new warnings
  • 11 band buttons visible: 160m–10m, 6m+, All
  • Plain click any named band → single-select; other buttons deselect
  • Plain click "All" → all buttons deselect, All highlights
  • Ctrl+click "40m" then Ctrl+click "20m" → both highlighted, table shows 40m + 20m stations only
  • Ctrl+click an active band → deselects it; remaining bands still active
  • Ctrl+click last active band → reverts to All mode automatically
  • Ctrl+click "All" → behaves same as plain click All
  • Track + Band: tune to 7.200 → "40m" auto-selects; tune to 50.200 → "6m+" auto-selects; out-of-band → All
  • Track + Freq (Fix A): tune between bands → band button highlight follows slice frequency; table filters by exact Hz throughout
  • Settings persist (Fix B): select "40m", close and reopen app → "40m" restored (not "All")
  • Multi-band selection persists across restart
  • Migration: manually write old-format JSON {"bandMode":true,"bandIndex":5,"track":false} to FreeDvReporter AppSettings key → reopen → "17m" correctly restored
  • Old "All" (bandIndex: 9) migrates to All mode

🤖 Generated with Claude Code

@NF0T NF0T requested a review from a team as a code owner June 14, 2026 17:58

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @NF0T — this is a clean, well-reasoned omnibus. The multi-select rewrite (single QSet<int> replacing the scalar index), the m_initializing gate matching the MqttSettingsDialog pattern, the versioned nested-JSON schema via AppSettings (Principle V), and the explicit old→new migration are all idiomatic for the codebase. CI is green on all six checks, and nothing outside the dialog references the changed private signatures. The three bug fixes each have a clear root cause and the diff matches the description.

A few observations, none blocking:

1. (minor, partly pre-existing) restoreSettings() always leaves the proxy in Band mode. The new flow ends with an unconditional applyBandFilter(), and setBandFilters() sets m_mode = Band on the proxy. So when restoring in Freq mode (bandMode == false) with Track off, the table ends up band-filtered rather than freq-filtered until the next retune. The old code had the same shape, so it's not a regression — but since this PR rewrote the restore path, it's a natural spot to fix it, e.g.:

if (bandMode) {
    m_bandRadio->setChecked(true);
    applyBandFilter();
} else {
    m_freqRadio->setChecked(true);
    // Freq mode: proxy is driven by the next slice update; syncing the
    // band-button highlight only.
    syncButtonStates();
}

Practical impact is small (Freq mode has no frequency to filter on without a tracked slice, and m_slice is typically null at restore), hence low priority.

2. (minor) Per-retune persistence while tracking. In Band + Track, every onSliceFrequencyChanged that lands in a band runs applyBandFilter()persistSettings()AppSettings::save(), i.e. a disk write on each band-crossing tune. Pre-existing and unchanged by this PR, just flagging since the hoisted loop now routes through it in both branches. Not worth addressing unless you're already in here.

3. (note, no action) Track collapses a multi-band selection. Enabling Track with several bands Ctrl-selected will overwrite the set with the single tracked band on the first slice update, and that's now persisted. That reads as the intended semantics of Track mode, so just confirming it's deliberate.

The migration test cases (last two checkboxes) are still unchecked in the plan — worth ticking those off before merge since the bandVersion-absence detection and the old index-9→All mapping are the trickiest part to get right. Nice work.


🤖 aethersdr-agent · cost: $4.5796 · model: claude-opus-4-8

@NF0T NF0T force-pushed the feat/freedv-reporter-6m-multiband branch from c046549 to 66c7eb1 Compare June 15, 2026 00:22
@NF0T

NF0T commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

Amended the branch commit (force-pushed) to fold in a small follow-on fix discovered during the compiler warning cleanup feasibility pass run after this PR was opened.

What changed: FreeDvReporterProxy::setBandFilters() and setFreqFilter() both called QSortFilterProxyModel::invalidateFilter(), which was deprecated in Qt 6.8 in favor of beginFilterChange()/endFilterChange(). Replaced both call sites with the new API — functionally identical, just wraps the state mutation between the two boundary calls instead of triggering a re-filter after the fact.

Why in this commit rather than a follow-on: The two affected methods are in the proxy class added by this PR, so the deprecation is our introduction. Folding the fix here keeps the warning count from going up on merge rather than requiring an immediate cleanup PR.

No other changes to the diff. History is linear — one commit on the branch.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Thanks for this, @NF0T — the multi-band selection work looks great, and I appreciate you folding the proxy cleanup in rather than leaving a warning behind. The CI failure is a small, easy one, and it traces directly back to that folded-in cleanup. Good news: it's a real source-portability issue, not CI infrastructure, so it's fully in your control to fix.

What failed

On commit 66c7eb1:

Job Qt Result
check-macos Homebrew qt@6 (≥ 6.9) ✅ pass
build (Linux) 6.4.2 (Docker floor) ❌ fail
check-windows 6.8.3 (ci.yml L71) ❌ fail

That "mac passes, Windows + Linux fail" split is the tell: the diff uses an API that only exists in the newest Qt.

Root cause

Your force-push comment says the swap was made because invalidateFilter() "was deprecated in Qt 6.8 in favor of beginFilterChange()/endFilterChange()." That premise is the bug, on two counts:

  1. QSortFilterProxyModel::invalidateFilter() is not deprecated. It's a current public slot, available since Qt 5. The rest of this codebase still uses it — see DxClusterDialog.cpp:242, which compiles clean on every CI target.
  2. beginFilterChange() / endFilterChange() are new in Qt 6.9, not a 6.8 replacement. They're a batching convenience added that release. On Windows's pinned 6.8.3 and the Linux 6.4.2 floor, those symbols don't exist, so FreeDvReporterProxy::setBandFilters() and setFreqFilter() fail to compile. macOS only passed because Homebrew's qt@6 happens to be ≥ 6.9 right now.

(Worth flagging since the rationale came from a "compiler warning cleanup" pass — I'd double-check whatever surfaced the "deprecated" claim; it doesn't match Qt's actual API history.)

The fix

Revert just those two call sites back to the portable API. In setBandFilters() and setFreqFilter(), replace:

beginFilterChange();
m_mode  = Band;       // (your state mutation)
m_bands = bands;
endFilterChange();

with:

m_mode  = Band;       // (your state mutation)
m_bands = bands;
invalidateFilter();

It's functionally equivalent for this use — set the members, then invalidate — and it mirrors the existing DxClusterDialog proxy exactly. Everything else in the diff (the QSet<int> multi-select, the bandVersion settings migration, the 6m+ band) is unaffected.

The minimum Qt across CI is 6.4.2 (Linux floor), so anything newer than that needs a version guard or an alternative — a handy rule of thumb when CI is green on mac but red on Windows/Linux.

Push that one-line-per-method change and CI should go green. Nice feature — thanks for volunteering the time on it. 🙂


🤖 aethersdr-agent · cost: $3.0255 · model: claude-opus-4-8

… XIV.

Add "6m+" catch-all band button (index 9, 50–1300 MHz) mirroring
FreeDV-GUI's "Other" filter so VHF/UHF RADE stations can be isolated
without losing HF context.

Add Ctrl+click multi-band selection (⌘-click on macOS): plain click
retains single-select; Ctrl+click toggles bands in/out of the active
set; deselecting the last active band reverts to All mode. QSet<int>
replaces scalar m_activeBandIndex; proxy gains setBandFilters() with
an OR-of-ranges Band path.

Fix A: in Track+Freq mode, band buttons didn't update visually when
the slice frequency changed. Hoist the band-finding loop before the
Band/Freq branch split so syncButtonStates() fires in both modes.

Fix B: persistSettings() fired inside buildBody() before
restoreSettings() ran, overwriting saved state on every startup.
Guard with m_initializing flag (pattern from MqttSettingsDialog).

Fix C: switching to Band mode with Track enabled triggered three
redundant persistSettings() calls. Remove the trailing explicit call
from onBandModeToggled(); applyBandFilter() already persists.

Settings migrated: new format adds bandVersion:1 + bandIndices array;
old scalar bandIndex (0–8) maps 1:1; old index 9 ("All") → empty set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@NF0T NF0T force-pushed the feat/freedv-reporter-6m-multiband branch from 66c7eb1 to eb4035f Compare June 15, 2026 01:17
@NF0T

NF0T commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed a second amendment — reverts the beginFilterChange()/endFilterChange() calls back to invalidateFilter().

The aethersdr-agent review above is correct: beginFilterChange()/endFilterChange() are Qt 6.9+ symbols, not a Qt 6.8 replacement. Our local build runs Qt 6.11.0 so it compiled clean; CI on 6.8.3 (Windows) and 6.4.2 (Linux) doesn't have them. The deprecation warning our local compiler emitted against invalidateFilter() was apparently a Qt 6.9+ addition alongside the new API — irrelevant to any CI target this project supports.

Fix is setBandFilters() and setFreqFilter() both back to setting members then calling invalidateFilter(), matching DxClusterDialog exactly. No other diff changes. CI should be green now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant