Skip to content

fix(gui): RX Controls hero frequency + never-clip shrink-to-fit (#3463)#3839

Merged
NF0T merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/vfo-freq-clipping
Jun 26, 2026
Merged

fix(gui): RX Controls hero frequency + never-clip shrink-to-fit (#3463)#3839
NF0T merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/vfo-freq-clipping

Conversation

@jensenpat

@jensenpat jensenpat commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

The bug

The VFO frequency readout clipped its leading (most-significant) MHz digits in the RX Controls box (RxApplet) and the VFO slice flag (VfoWidget), and rendered far too small. Reported in #3463 (Default Light, Windows/FLEX‑6400, "first number cut"); also visible on macOS default dark.

Root cause — two issues

  1. No shrink-to-fit. Both surfaces render the frequency as a QLabel styled by the theme tokens font.size.freq / font.family.freq (DSEG7 Modern), poured into a fixed box with no elision. A string wider than the box clips on the left (the label is right-aligned). In RX Controls the frequency also shared its row with TX/mode/WFM, leaving it only ~124px.
  2. setFont() didn't stick. Driving the fitted size via QWidget::setFont() silently did nothing: a QLabel whose stylesheet names font-family makes Qt recompute the font from the QSS on every re-polish and discard setFont(), falling back to the ~13px default. So the digits rendered at ~13px regardless of the fitted size — the real cause of "the frequency is tiny."

The fix

  • New FreqLabelFit.h::fitFreqPixelSize() computes the largest non-clipping pixel size; it's applied through the stylesheet as a literal font-size (which always wins over the cascade), with colour/family still resolved from theme tokens so it stays theme-reactive. The last size is cached to skip a re-polish on every tune step, and each freq box is a stable, font-independent width (RxApplet stack Expanding, VfoWidget fixed-width) so the fit can't oscillate.
  • RX Controls redesign: the frequency is the panel's hero — its own full-width row at ~34px, with the mode selector and the TX/WFM toggles on a compact context strip beneath it. (The band and filter-bandwidth labels were intentionally left off the strip — the band is obvious from the large frequency, and the bandwidth is already shown by the lit filter-preset button and the passband graph.) The VFO flag keeps the theme-nominal size.

Proof — agent automation bridge (live on a FLEX‑8400M)

tune + grab, before vs after, both themes. Left = original (clipped + ~13px); right = redesign (hero ~34px, decluttered).

RX Controls — default dark, 21.074 MHz
dark before/after

RX Controls — light theme, 21.074 MHz
light before/after

VFO slice flag — unchanged (theme nominal, no clip)
vfo flag

Case Before After
RxApplet dark 21.074 leading 2 clipped, ~13px full 21.074.000 @ ~34px
RxApplet dark 144.2 1 gone, 4 clipped full 144.200.000, no clip
RxApplet light 21.074 only 074.000 shown full 21.074.000
VfoWidget flag (already fit) unchanged, no regression

Files

  • src/gui/FreqLabelFit.h (new) — fitFreqPixelSize()
  • src/gui/RxApplet.{h,cpp} — hero frequency row, context strip, literal-QSS sizing
  • src/gui/VfoWidget.{h,cpp} — literal-QSS sizing + re-fit hooks

Rebased onto current main (includes #3800 / #3782). Closes #3463. Refs #3515.

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

…ethersdr#3463)

The frequency readout in the VFO slice flag (VfoWidget) and the RX Controls
box (RxApplet) is a plain QLabel styled by the theme's font.size.freq /
font.family.freq tokens (DSEG7 Modern, 20px dark / 26px light), poured into a
fixed-width box with no shrink-to-fit and no elision. When the rendered string
is wider than the box, the right-aligned label silently clips its leading
(most-significant) digits — the operator loses the MHz, the worst digits to
lose:

- RxApplet: the freq stack shares its row with TX/mode/WFM inside the fixed
  ~248px AppletPanel column; the leftover (~124px measured live) is narrower
  than even "21.074.000" at the default 20px, so the leading "2" clips on the
  DEFAULT DARK theme. In light theme (26px) only "074.000" survives.
- VfoWidget: the 252px flag fits 2-digit-MHz HF in dark, but the light-theme
  26px digits (and the wider Windows metrics in aethersdr#3463) overflow and clip the
  leading digit.

Fix: drop `font-size` from each freq label's QSS and drive the pixel size via
setFont() with a width-fitted size capped at the theme nominal (new
FreqLabelFit.h helper, shared by both surfaces). A QSS font-size OVERRIDES
setFont(), so omitting it is precisely what lets the fit take effect (verified
empirically). Re-fit on text update, label QEvent::Resize, and
ThemeManager::themeChanged so Dark<->Light and Theme-Editor font edits stay
correct. RxApplet's freq stack is made Expanding (the stretch is removed) so it
fills a font-independent width, avoiding a fit<->reflow oscillation. The digits
stay full-size whenever they fit and shrink only as much as needed to keep the
leading digits on screen.

Proven with the agent automation bridge (tune + grab on a FLEX-8400M,
RX-only, before/after, both themes):
- RxApplet dark 21.074 MHz: leading "2" clipped -> full "21.074.000"
- RxApplet dark 144.2 MHz: "1" gone, "4" clipped -> full "144.200.000"
- RxApplet light 21.074 MHz: only "074.000" -> full "21.074.000"
- VfoWidget light 21.074 MHz: leading "2" clipped -> full "21.074.000"
- VfoWidget dark (already fit): unchanged, no regression

Fixes aethersdr#3463. Refs aethersdr#3515 (the clipping is fixed; the "make the digits larger"
ask there remains a separate enhancement).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jensenpat jensenpat marked this pull request as ready for review June 26, 2026 18:43
@jensenpat jensenpat requested a review from a team as a code owner June 26, 2026 18:43

@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 for this @jensenpat — clean, well-scoped fix with a clear root-cause writeup and before/after proof. The QSS-font-size-overrides-setFont() finding is the crux and the design around it is sound. The diff reads well: null/empty/availW<=0/maxPx<=0 are all guarded, and making RxApplet's freq stack Expanding (so the fit width is font-independent) correctly forecloses the fit↔reflow oscillation. All five files serve the one fix, no scope creep, no settings/RAII concerns.

A couple of non-blocking notes:

1. Linear scan on a hot path. fitFreqPixelSize() walks maxPx → minPx constructing a fresh QFontMetrics and measuring horizontalAdvance() each step (~10–16 iterations), and applyFreqFit() runs on every updateFreqLabel() — i.e. on every frequency tick during tuning/scrolling, plus every resize. Glyph advance is monotonic in pixel size, so a binary search over [minPx, maxPx] would cut this to ~4 measurements with identical results. Given how perf-sensitive the spectrum/VFO path is here, might be worth the small change. Not a correctness issue.

2. Possible mojibake in a comment. The diff renders a stray replacement-style character in FreqLabelFit.h ("the theme's nominal size ��� it only shrinks…") where an em-dash looks intended. This may just be diff-transport mangling of the multi-byte char (the pre-existing box-drawing context lines show the same artifact), but worth a quick confirm that the committed file is clean UTF-8.

Behavior-wise this looks correct and the re-fit hooks (text update, QEvent::Resize, themeChanged) cover the Dark↔Light and Theme-Editor cases. Nice work.


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

@NF0T

NF0T commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

@NF0T review — PR #3839

Five files, one new header-only helper, clean fix for a genuinely bad UX bug (operator loses MHz digits in both themes on their own radio display).

Root cause is correctly and precisely diagnosed. The key Qt behavior — a QSS font-size overrides setFont(), not the other way around — is non-obvious and the PR confirms it empirically. Both freq labels had font-size: {{font.size.freq}}px locked in their QSS, so no setFont() call could shrink them. In a fixed-width right-aligned container any overflow falls off the left edge, silently losing the most significant digits. The measured RxApplet constraint (~124px leftover in default-dark) means even a 21 MHz frequency clips at 20px on macOS, let alone 26px light theme or wider Windows metrics.

The implementation is correct on all three mechanisms:

  1. QSS change — dropping only font-size from each label's stylesheet while keeping font-family, color, border, padding is exactly right. The family token still gets theme treatment; only the size is now owned by setFont(). The comments noting WHY font-size is deliberately absent are precisely the right kind of annotation — a future reader would otherwise reintroduce it.

  2. FreqLabelFit.h helperfitFreqPixelSize() is a downward linear search from maxPx. For a 9–11 char DSEG7 string at ≤26px this is at most ~16 iterations on a non-hot path (text/resize/theme events only) — fine. applyFittedFreqFont() has a null guard on label and a family fallback. pad is correctly differentiated: RxApplet uses 4px (no border, safety margin only), VfoWidget uses 6px (1px border each side + 2px left padding + 2px safety).

  3. Reflow oscillation prevention — this is the subtle part and it's handled correctly. m_freqStack->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed) + removing the addStretch(1) means the container's width is determined by the layout, not by the label's content. So setFont() can never shrink the container, and the QEvent::ResizeapplyFreqFit() path can't trigger another container resize. Without Expanding, a smaller font would reduce content width, shrink the stack, resize the label, call applyFreqFit() again — oscillating. The PR identifies and closes this loop.

All three re-trigger paths are wired: text update (both LOCKED and normal freq paths in updateFreqLabel()), QEvent::Resize on the label (return false is correct — observe only), and ThemeManager::themeChanged for Dark↔Light and Theme Editor font edits. Nothing is left stranded.

CI: All 6 checks pass — build, check-macos, check-windows, a11y, CodeQL (quick), and analyze (cpp) (39m55s, just completed). The live-radio grab verification in the PR body covers what CI can't reproduce (theme-dependent clipping at actual font metrics on real hardware).

Constitution: Principle XI (Fixes Are Demonstrated — before/after bridge grabs on both surfaces, both themes, two frequencies including the 3-digit-MHz 144 MHz case, explicit regression check on VfoWidget dark) and Principle VIII (QSS/setFont interaction verified empirically, not assumed) both honored.

Closes #3463 is in the PR body. Tier 3 only. Merging now.

@NF0T NF0T self-assigned this Jun 26, 2026

@NF0T NF0T left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Root cause confirmed empirically (QSS font-size overrides setFont — verified). QSS change, FreqLabelFit helper, and Expanding size policy all correct; reflow oscillation prevention is the subtle piece and it's handled. All three re-trigger paths wired (text update, QEvent::Resize, themeChanged). All 6 CI checks pass including analyze (cpp) at 39m55s. Tier 3 only.

Squash subject: fix(gui): shrink-to-fit VFO/RxApplet frequency so digits never clip — Principle XI. (#3839)

@NF0T NF0T merged commit c66f6fa into aethersdr:main Jun 26, 2026
6 checks passed
jensenpat pushed a commit to jensenpat/AetherSDR that referenced this pull request Jun 26, 2026
jensenpat pushed a commit to jensenpat/AetherSDR that referenced this pull request Jun 26, 2026
@jensenpat jensenpat changed the title fix(gui): shrink-to-fit VFO/RxApplet frequency so digits never clip (#3463) fix(gui): RX Controls hero frequency + never-clip shrink-to-fit (#3463) Jun 26, 2026
jensenpat added a commit to jensenpat/AetherSDR that referenced this pull request Jun 26, 2026
…ethersdr#3839 follow-up)

Follow-up to aethersdr#3839. That change stopped the frequency clipping but drove the
fitted pixel size via QWidget::setFont() — which a QLabel whose stylesheet
names font-family silently discards on every re-polish, so the digits actually
rendered at the ~13px app default ("the frequency is tiny by default"). This
applies the fitted size through the stylesheet as a literal font-size (which
always wins over the cascade), and reorganises the RX Controls panel around it.

- FreqLabelFit.h keeps fitFreqPixelSize(); the size is now applied via the
  label's QSS (literal font-size), with colour/family still resolved from theme
  tokens and the last size cached to avoid a re-polish on every tune step. Each
  freq box is a stable, font-independent width so the fit can't oscillate.
- Hero frequency: its own full-width row at ~34px, with the mode selector and
  the TX/WFM toggles on a compact context strip beneath it. The band and
  filter-bandwidth labels are dropped (the band is obvious from the large
  frequency; bandwidth is already shown by the lit preset button + passband
  graph).
- Volume and AGC are promoted to full-width sliders directly under the mode
  selector; STEP moves into the right control group with balance (pan) and
  squelch. Net top-to-bottom flow: frequency, mode/TX/WFM, volume, AGC, then a
  two-column grid (filter presets + passband | STEP, pan, SQL, RIT, XIT).
- VfoWidget flag: same literal-QSS sizing at the theme nominal.

Proven live on a FLEX-8400M via the agent automation bridge (tune + grab,
before/after, both themes): RX Controls 21.074 MHz shows the full "21.074.000"
at ~34px, 144.2 MHz shows "144.200.000" with no clip, and the VFO flag is
unchanged.

Refs aethersdr#3515, aethersdr#3463 (aethersdr#3839 follow-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NF0T pushed a commit that referenced this pull request Jun 26, 2026
Reverts #3839.

#3839 landed half-baked. It drove the fitted frequency pixel size via
`QWidget::setFont()`, but a `QLabel` whose stylesheet names
`font-family` makes Qt recompute the font from the QSS on every
re-polish and **discard `setFont()`**, falling back to the ~13px app
default. So the leading digits no longer clip, but the frequency renders
**unreadably small** — the fix is incomplete.

Rather than ship that behavior on `main`, this reverts it. The
**complete** fix is **#3844**:
- the fitted size is applied through the stylesheet as a literal
`font-size` (which always wins over the cascade), so the digits actually
render at the intended size;
- the RX Controls frequency becomes a full-width hero (~34px) with a
reorganized control layout.

Reverts cleanly — no commit since #3839 touched the frequency files.
Build passes.

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Jun 27, 2026
… don't clip

The WFM software-demod toggle lived in RxApplet's `m_freqRow` — the same row as
the large frequency readout — so at narrower applet widths it (with the mode
combo) stole width and clipped the digits. The earlier shrink-to-fit (#3839)
only masked the symptom.

Relocate it to the DAX pan overlay menu, directly below the "IQ Ch" selector —
the natural home, since WFM demodulates that pan's DAX IQ stream on the PC. The
toggle is slice-keyed (acts on the menu's slice) but always reachable (unlike
the FM-mode-gated VFO-flag button), so dropping the always-visible RxApplet
button doesn't cost reachability.

- RxApplet: drop m_wfmButton (creation + member) and the vestigial
  setWfmActive(); keep the mode-combo's wfmActivated(false) teardown so a
  real-mode selection still tears down a running demod.
- SpectrumOverlayMenu: add the WFM toggle below IQ Ch; emit
  wfmToggleRequested(on, sliceId); add a self-gated setWfmActive() reflector.
- MainWindow_Wiring: route the toggle to activateWFM/deactivateWFM with the
  same deactivate guard as the flag button.
- reflectWfmButtons: keep the VFO flag and the DAX-menu toggle in sync.

WFM remains a per-slice singleton; this only changes where the toggle lives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ten9876 added a commit that referenced this pull request Jun 27, 2026
…gits

The WFM software-demod toggle lived in RxApplet's `m_freqRow` — the same row as
the large frequency readout — so it clipped the digits (#3839's shrink-to-fit
only masked it). A second, FM-mode-gated copy also lived on the VFO flag.

Consolidate to a SINGLE WFM surface in the DAX pan overlay menu, directly below
the "IQ Ch" selector — the natural home, since WFM demodulates that pan's DAX IQ
stream on the PC. WFM runs on raw IQ and is mode-independent, so the menu toggle
is always reachable (the old flag button only appeared in FM modes).

- RxApplet: drop the WFM button + the vestigial setWfmActive(); keep the
  mode-combo wfmActivated(false) teardown.
- VfoWidget: remove the FM-gated WFM button, its setWfmActive() reflector, the
  wfmActivated signal, and the FM-mode gating that showed/tore it down.
- SpectrumOverlayMenu: add the WFM toggle below IQ Ch; emit
  wfmToggleRequested(on, sliceId); add a self-gated setWfmActive() reflector.
- MainWindow_Wiring: route the menu toggle to activateWFM/deactivateWFM.
- reflectWfmButtons: drive the single DAX-menu toggle.

WFM remains a per-slice singleton; this only changes where the toggle lives.
Note: with WFM no longer FM-gated, leaving FM mode on the flag no longer
auto-tears-down the demod (it is raw-IQ, mode-independent); an explicit RxApplet
mode change still tears it down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Deafaul Light Theme (VFO first number cut)

2 participants