Skip to content

fix(gui): fill the floating window for width-capped applets (#3451)#3827

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3451-center-floating
Jun 26, 2026
Merged

fix(gui): fill the floating window for width-capped applets (#3451)#3827
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3451-center-floating

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

What & why

Fixes #3451. When a width-capped applet is popped out of the docked strip into its own floating window, its content stayed pinned to the left edge with a growing dead zone on the right — the "left-justified" look the reporter showed for Antenna Genius.

Root cause. Several applets clamp their own maximumWidth so they sit tidily in the narrow right-hand strip:

  • AntennaGeniusApplet.cpp:55setMaximumWidth(260)
  • ShackSwitchApplet.cpp:82setMaximumWidth(260)
  • ClientRxDspApplet.cpp:17setMaximumWidth(250)

That cap is right when docked, but FloatingContainerWindow opens at 300 px (and is freely resizable) with a plain QVBoxLayout and no alignment, so the 260-px content hugs the left edge. The wider the user drags the window, the bigger the dead zone.

Fix. ContainerWidget now tracks dock state for its body content: while floating it lifts each child's width cap so the content fills the window; on re-dock it restores the original cap (remembered per child). Rather than centering the capped content — which only makes the wasted space symmetric — the content now actually uses the real estate, which is what was asked for here.

The Amplifier / Power Genius applet has no width cap, so it already filled the window; the change is a verified no-op for it (see below). The same fix also covers ShackSwitch and the RX-DSP applet.

Screenshots — affected applets (floated, widened to 440 px)

Antenna Genius — the reported applet (fixed)

Antenna Genius before/after

Before, the controls stop at 260 px; after, the device combo, Connect, Manual IP, the AUTO bars and the Port A/B rows all stretch to the window edge. (Shown disconnected, so the per-antenna button grid is empty — when connected that 4-column grid fills the extra width too.)

Power Genius / Amplifier — checked, already effective

Amplifier before/after

You flagged this one too. I checked it: it has no width cap, so the PWR/SWR/Id gauges already span the full width. Before and after pixels are byte-identical — no change needed. So among the two named applets, only Antenna Genius had the dead-space bug.

How the agent automation bridge proved it

The fix was verified headlessly with the agent automation bridge on an isolated, non-connecting instance (no radio contention):

Step Verb Result
Safety get radio connected: false (offline, no radio touched)
Float AG invoke AG/containerFloatToggle click ok, newValue: ↙
Widen resize AG 440 560 actual 440×560
Capture grab AG PNG — content fills 440 px (after) vs 260 px (before)

This relied on two small bridge-reachability additions in this PR: every ContainerWidget now carries an objectName equal to its stable id (AG, AMP, SS, …), and the title-bar float/pin/close buttons carry accessible names — so a driver can scope a control to one container (AG/containerFloatToggle). Closes a real gap (#3646): before this, no floating container's title-bar controls were addressable.

Tests

tests/container_widget_test.cpp gains coverage: docked content keeps its cap → floating lifts it to QWIDGETSIZE_MAX → re-docking restores it; uncapped content is untouched in both states. All container tests pass.

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

…r#3451)

Width-capped applets clamp their own maximumWidth so they sit tidily in
the narrow docked strip: Antenna Genius and ShackSwitch cap at 260 px,
the docked RX-DSP applet at 250 px. That cap makes sense docked, but when
the container is popped out into its own FloatingContainerWindow (default
300 px wide, and freely resizable) the 260-px content stays pinned to the
left edge with a growing dead zone on the right — the left-justified look
reported in aethersdr#3451.

Fix: ContainerWidget now tracks dock state for its body content. While
floating it lifts each child's width cap so the content fills the window;
on re-dock it restores the original cap. Uncapped applets (e.g. the
Amplifier / Power Genius, which has no cap) are unaffected — they already
filled the width.

Also gives each ContainerWidget an objectName (its stable id: "AG",
"AMP", "SS", …) and accessible names to the title-bar float/pin/close
buttons, so the agent automation bridge can scope a control to a specific
container (e.g. invoke "AG/containerFloatToggle"). This is what made the
fix provable headlessly and closes a real bridge reachability gap (aethersdr#3646).

Adds container_widget_test coverage: cap is lifted on float, restored on
dock, and uncapped content is left untouched in both states.

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat
@jensenpat jensenpat marked this pull request as ready for review June 26, 2026 02:25
@jensenpat jensenpat requested review from a team as code owners June 26, 2026 02:25

@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 the well-documented fix, @jensenpat — the root-cause writeup and before/after screenshots make this easy to reason about, and the approach (lift the width cap while floating, restore it on re-dock rather than centering) is the right call.

I traced the dock transition end-to-end and the logic holds up:

  • FloatingContainerWindow::takeContainer reparents the container and then calls setDockMode(Floating) (FloatingContainerWindow.cpp:62-65), so the body children are already in m_bodyLayout when the policy loop runs — the cap actually gets lifted. releaseContainer mirrors it. Both setContent/insertChildWidget also re-apply per the current mode, so a swap-while-floating is covered.
  • Filling (rather than centering) is consistent with the layout: m_bodyLayout->addWidget(content, 1) is a QVBoxLayout with no horizontal alignment, so lifting maximumWidth lets the content stretch to the window edge.
  • The save-once guard (if (!m_savedMaxWidths.contains(child))) correctly preserves the original docked cap across repeated floats, and restoreWidthPolicy uses take() so the entry is cleared on restore.
  • The test exercises both the capped and uncapped paths and the full float→dock→float cycle. CI is green across all six checks (build, macOS, Windows, CodeQL, a11y).

Two minor, non-blocking notes:

  1. Stale entry in m_savedMaxWidths if a body child is destroyed without going through removeChildWidget/setContent. In the normal lifecycle this is fine (the hash dies with the container, or restoreWidthPolicy clears the entry). The only theoretical hazard is a child deleted while the container lives on, followed by a new widget reusing that address — contains() would then short-circuit the save and restore would write back a stale cap. The pointer is never dereferenced so there's no UAF, but if you want to be defensive you could clear the entry on a QObject::destroyed connection. Genuinely low severity; mentioning for completeness, not asking you to change it.

  2. Scope: the objectName/accessible-name additions (ContainerWidget.cpp, ContainerTitleBar.cpp, #3646) are bridge-reachability changes bundled alongside the #3451 fix. You called this out explicitly in the PR body and they're tiny and low-risk, so no objection — just flagging that they're a separate concern from the title.

Looks good to me. Nice work.


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

@ten9876 ten9876 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.

Clean, correct, tested layout fix. Verified the cap save/restore: the double-save guard (if !m_savedMaxWidths.contains(child)) correctly remembers the original docked cap rather than the lifted QWIDGETSIZE_MAX; restoreWidthPolicy uses take() so re-float re-saves fresh; content-swap paths (setContent/insert/removeChildWidget) apply/restore so children leave with their original cap; uncapped content is a genuine no-op (matching the Amplifier byte-identical claim); setDockMode re-applies to all body children. Good call filling the window rather than centering. Test covers docked-keeps → float-lifts → re-dock-restores → uncapped-untouched. No TX/protocol surface.

Non-blocking nits: m_savedMaxWidths keyed by raw QWidget* could dangle if a child is destroyed while floating without removeChildWidget (harmless — key-only, never deref); and the ContainerTitleBar.cpp:83 comment is truncated mid-sentence. Thanks @jensenpat.

@ten9876 ten9876 merged commit b20ddb1 into aethersdr:main Jun 26, 2026
6 checks passed
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.

Enhancement: Center display/output when windows are undocked from main AetherSDR screen

2 participants