feat(gui): mouse-over numeric readout on TX meters (SWR/power/ALC/level/compression)#3936
Conversation
…el/comp)
While transmitting, the SWR, forward-power and ALC bar meters — plus the mic
Level and Compression meters — only conveyed a value by how far the bar filled
against its scale. Hovering now pops a floating badge with the exact numeric
value, so the operator can read watts / SWR ratio / dBFS directly instead of
eyeballing the fill.
The badge reuses the existing DragValuePopup — the same widget (20 px /
weight-800, rounded cyan-bordered) the sliders flash on keyboard/drag
adjustment — so the style, font and size match exactly. It follows the pointer,
updates live as the meter value changes while hovered, and fades one second
after the pointer leaves the bar.
Implementation:
- HGauge gains an opt-in hover readout (setHoverValuePopupEnabled +
setHoverValueFormatter). Mouse tracking + enter/move/leave drive a per-gauge
DragValuePopup; leave lingers for 1000 ms. Default off, so telemetry gauges
(PA temp, supply, fan) are unaffected.
- TxApplet enables it on the forward-power ("N W") and SWR ("N.NN:1") gauges;
PhoneCwApplet on the ALC/Level ("N.N dB(FS)") and Compression ("N.N dB",
reported as a positive amount) gauges.
Automation bridge:
- New `hover <target> [leave]` verb synthesizes a real hover (QEnterEvent +
no-button QMouseMove; `leave` fires QEvent::Leave) so hover-driven UI is
provable end-to-end. Documented in docs/automation-bridge.md.
- amp_applet_test links DragValuePopup.cpp (HGauge.h now pulls it in).
Proven live over the bridge against a FLEX-8400M, transmitting a two-tone into
the ANT2 dummy load: forward power 12 W, SWR 1.12:1, ALC -6.4 dBFS all rendered
in the badge; badge confirmed visible 0.4 s after mouse-leave and gone by 1.2 s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Nice, well-scoped feature — the hover readout is a genuinely useful addition to the TX meters, and the implementation is clean. I checked the areas most likely to bite:
Verified good
- Lifetime / RAII:
m_hoverPopup = new AetherSDR::DragValuePopup(this)is parented to theHGauge, so Qt owns cleanup — no leak, and it matches the existingGuardedSlider/MeterSliderpattern. The popup'sWA_TransparentForMouseEventsattribute means it won't intercept the pointer and cause an enter/leave flicker loop. All null-guarded (if (m_hoverPopup)beforelinger/hideNow). - Transitive-include link fix: including
DragValuePopup.hfromHGauge.hmeans every TU that compiles an HGauge consumer needsDragValuePopup.cpplinked. I confirmed the only test target outside the main build that compiles such a TU isamp_applet_test(viaAmpApplet.cpp) — and that's exactly the one you added it to. The other four consumers (TxApplet/PhoneCwApplet/TunerApplet/MeterApplet) are main-build only, which already linksDragValuePopup.cpp. Coverage is complete. - Opt-in default off keeps the telemetry gauges (PA temp/supply/fan) untouched, and mouse tracking is only enabled when the readout is.
static_cast<HGauge*>(m_fwdGauge)is needed becauseTxAppletdeclares those members asQWidget*; the objects are constructed asHGauge, so the cast is safe.- All six CI checks green (build + macOS/Windows/Linux + CodeQL + a11y).
One minor, non-blocking observation
- Live updates route through
setValue/setValueImmediate→showHoverPopup→DragValuePopup::showValue, which callsraise()on every invocation. While a meter is hovered and actively updating during TX, that's araise()per meter tick. It's negligible in practice (only while the pointer is parked on the bar), so no change needed — just noting it in case you ever see the badge fighting z-order with other tool windows.
Thanks for the thorough automation-bridge testing and the new hover verb — proving hover-driven UI end-to-end is a nice touch. 👍
🤖 aethersdr-agent · cost: $2.6941 · model: claude-opus-4-8
There was a problem hiding this comment.
Nice, clean addition — thanks @jensenpat. The hover readout reuses DragValuePopup cleanly, the feature is opt-in so the telemetry gauges (PA temp/supply/fan) stay untouched, and the hover/hover … leave bridge verb makes it genuinely provable end-to-end. A few notes, nothing blocking:
Verified good:
- No leak / no null risk. The popup is lazily created and parented to the
HGauge(new DragValuePopup(this)), so it's destroyed with the gauge, and everyshowHoverPopupguards onm_hoverPopupEnabled+ lazy-inits.linger()only arms its timer when visible, andshowValue("")self-hides. - CMake fix is complete. Adding
#include "DragValuePopup.h"to the header-onlyHGaugemeans any TU that constructs anHGaugenow needsDragValuePopup.cppat link. I checked every test target:amp_applet_testis the only test that pulls in anHGauge-bearing source, and it's exactly the one you added the source to. The mainAetherSDRtarget already listsDragValuePopup.cpp. So nothing else link-breaks. doHoverboundary handling covers widget-not-found and not-visible, and the syntheticQEnterEvent+ no-buttonQMouseMovefaithfully mirrors a real hover (mouse tracking is enabled bysetHoverValuePopupEnabled).std::lroundinTxApplet.cppis backed by an explicit<cmath>.
Minor, optional:
- While hovered,
setValue/setValueImmediatecallshowHoverPopup→showValue→move()+raise()on every meter tick. On a live TX meter that's ~10–20 Hz of re-raising a top-level tooltip window. It's harmless functionally (WA_ShowWithoutActivatingkeeps focus out of it), but you could cheaply gate the re-show on the formatted text actually changing (cache the last string) to avoid the churn. Not required for merge.
Looks good to me overall.
🤖 aethersdr-agent · cost: $2.8353 · model: claude-opus-4-8
The mouse-over value readout only hid its DragValuePopup via leaveEvent (and the explicit setHoverValuePopupEnabled(false)). But Qt does not guarantee a leaveEvent when a widget is hidden or reparented while the pointer is still over it — tab switch, applet float/dock (rearrangeLayout's setParent), or window minimize. Because the popup is a top-level Qt::ToolTip window, it could then linger orphaned on screen (or reappear stale on tab-return, since m_hovered was never cleared). Add a hideEvent() override that hides the popup immediately and clears the hover state, closing the gap on every hide/reparent path in one place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two review-follow-up cleanups on the TX meter hover readout: - Efficiency: while hovered, setValue() fires at ballistics-animation rate but the badge text/anchor are usually unchanged frame-to-frame. showHoverPopup() now caches the last text+anchor and skips DragValuePopup::showValue() (which re-runs adjustSize/resize/move/raise every call) when nothing changed and the popup is already visible. isVisible() gates the re-enter case so a matching cache can't suppress a needed re-show after leaveEvent/hideEvent. - Replace the placeholder "(#feat ...)" comment tags with the real "(aethersdr#3936)" reference, matching the codebase's issue-number convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Reviewed with an 8-angle finder + verify pass (correctness, removed-behavior, cross-file, reuse/simplification, efficiency, altitude, conventions, and per-meter value/unit accuracy). Clean, careful PR — the hide/reparent/re-enter edge cases are handled explicitly and the ### hover bridge verb is properly documented.
The scary-sounding candidates all refuted on inspection:
- No DragValuePopup leak — it's
QWidget(parent, Qt::ToolTip|…); the ToolTip flag only changes window management, the QObject parent is retained, so it's destroyed with the HGauge. - Disable/re-enable re-show works — the
isVisible()gate inshowHoverPopupis exactly what re-shows the popup afterhideNow(). - Overridden mouse/enter/leave/hide handlers all call the base and early-return when
!m_hoverPopupEnabled— inert for gauges that don't opt in.
Non-blocking polish (optional, can be follow-ups):
- The readout shows
m_value(the true set value) while the bar renders the ballistics-smoothedm_smooth— defensible (exact number > lagging bar), just means badge/bar disagree briefly during a fast swing. - SWR formatter has no NaN/inf guard — an upstream
inf(forward=0) would print"inf:1"; a cheapisfinite/qBoundguard would harden it. - SWR can show a stale ratio at 0 W, but that's pre-existing
updateMetersbehavior (the bar already shows it) — out of scope here.
Approving.
#3961) ## Release documentation prep for **v26.7.1** (2026-07-02) 29 commits since v26.6.5. Docs-only + version bump — no code behavior change. ### Version bump 26.6.5 → 26.7.1 The three canonical locations (per `AGENTS.md`): - `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` → `AETHERSDR_VERSION` (the app's version string) - `README.md` — Current version line - `AGENTS.md` — Current version line ### `CHANGELOG.md` Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style (v-prefix, em-dash, "N commits since…" opener + headline), authored from all merged PRs and categorized: - **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937), in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs (#3920) - **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX speaker-latency cut (#3897), Display pane sections (#3935) - **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892, #3924, #3891, #3890, #3900, #3947 - Fresh empty `[Unreleased]` restored above the release block. Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per house style. ### `ROADMAP.md` - Cycle header → "post-v26.7.1" - NVIDIA BNR removed from **In flight** (shipped this cycle) - Five v26.7.1 highlights prepended to **Recently shipped** ### `README.md` - Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at up to 60 fps** and the **3D stacked-trace** spectrum mode ### Reviewer notes - Touches protected paths (`*.md` + `AGENTS.md` Tier-1 + `CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off. - Tagging `v26.7.1` and cutting the release build are **not** part of this PR (docs prep only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Mouse-over numeric readout on the TX meters
While transmitting, the SWR, forward-power and ALC bar meters — and now the mic Level and Compression meters — only told you a value by how far the bar filled against its scale. You had to eyeball it. This adds a hover readout: mouse over any of those meters and a floating badge shows the exact numeric value.
Proven live over the automation bridge against a FLEX-8400M, transmitting a two-tone into the ANT2 dummy load. Forward power, SWR and ALC show real keyed values; Mic Level and Compression are shown idle because two-tone bypasses the mic/speech path.
What it does
12 W1.12 : 1-6.4 dBFS-12.5 dB6.0 dB(reported as a positive amount of compression)DragValuePopup— the same widget the sliders flash when you adjust them by keyboard/drag (20 px, weight-800, rounded cyan-bordered badge) — so font, size and style match exactly, as requested.Implementation
HGaugegains an opt-in hover readout:setHoverValuePopupEnabled(bool)+setHoverValueFormatter(...). Mouse tracking plus enter/move/leave events drive a per-gaugeDragValuePopup;leavelingers for 1000 ms. Default off, so the telemetry gauges (PA temp, supply, fan) are untouched.TxAppletenables it on the forward-power and SWR gauges with unit-aware formatters.PhoneCwAppletenables it on the ALC, mic-Level and Compression gauges.Automation bridge (testing support)
hover <target> [leave]verb synthesizes a real hover (QEnterEvent+ no-buttonQMouseMove;leavefiresQEvent::Leave) so hover-driven UI is provable end-to-end. Documented indocs/automation-bridge.md.amp_applet_testnow linksDragValuePopup.cpp(pulled in transitively byHGauge.h).Verification
Driven entirely through the automation bridge:
12 W1.12 : 1-6.4 dBFS-150.0 dB0.0 dBANT2(dummy load) viadumpTreebefore any keying; tune power held at ~15–20 %; the transmitter was unkeyed and state restored after capture (transmitting == falseconfirmed).💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat