Skip to content

fix(gui): Profile Switcher non-editable combo focus swallows first Space PTT / CW key (#3908)#3926

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3908-combo-ptt-focus
Jun 30, 2026
Merged

fix(gui): Profile Switcher non-editable combo focus swallows first Space PTT / CW key (#3908)#3926
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:fix/3908-combo-ptt-focus

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

Summary

After selecting a profile from the Profile Switcher (or any other non-editable QComboBox in the app), the first Space press doesn't key PTT — it opens the combo's dropdown instead. A second Space is needed to actually transmit.

Root cause

textInputCaptured() (src/gui/MainWindow_Shortcuts.cpp) treats any focused QComboBox as a text-input widget:

bool textInputCaptured()
{
    auto* w = QApplication::focusWidget();
    if (!w) return false;
    return qobject_cast<QLineEdit*>(w) || qobject_cast<QTextEdit*>(w)
        || qobject_cast<QPlainTextEdit*>(w) || qobject_cast<QSpinBox*>(w)
        || qobject_cast<QComboBox*>(w);
}

handlePttHoldShortcut() and handleCwMomentaryShortcut() both bail out (return false, don't consume the key) when this predicate is true. A non-editable QComboBox (like the Profile Switcher's Global/TX/Mic rows) keeps keyboard focus after its popup closes from a mouse selection. So:

  1. First Space → combo still focused → textInputCaptured() true → handler returns false → Qt's default QComboBox behavior opens the dropdown instead of keying TX.
  2. Second Space → closes the popup → next Space reaches the handler → TX keys.

The same guard sits in the CW momentary-key handler, so CW keying is swallowed the same way after a profile selection.

Fix

Add textEntryCaptured(), a narrower predicate that only counts widgets that actually consume typed text: QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, and an editable QComboBox (its line edit is the real focus target, so it's still caught). A non-editable combo no longer counts.

Use textEntryCaptured() to gate the PTT-hold and CW-momentary keying handlers. Leave textInputCaptured() (and everywhere else it's used, e.g. arrow-key tune/gain shortcuts) unchanged, so Up/Down/Left/Right still navigate a focused combo's list instead of double-firing as global tune/gain shortcuts.

Files changed

  • src/gui/MainWindowShortcutState.h — declare textEntryCaptured()
  • src/gui/MainWindow_Shortcuts.cpp — define textEntryCaptured(); switch handlePttHoldShortcut() and handleCwMomentaryShortcut() to use it instead of textInputCaptured()

Test plan

  • Built RelWithDebInfo, all warnings pre-existing/unrelated
  • Maintainer/CI verification on hardware

Fixes #3908

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

…ethersdr#3908)

After selecting a profile from the Profile Switcher (or any other
non-editable QComboBox), the combo retains keyboard focus. The first
Space press then opens the combo's dropdown instead of keying PTT,
because textInputCaptured() treats any focused QComboBox as a text
input and handlePttHoldShortcut()/handleCwMomentaryShortcut() bail
out rather than consuming the key. A second Space closes the popup
and the next press finally keys TX.

Add textEntryCaptured(), a narrower predicate that only counts
editable text-entry widgets (QLineEdit, QTextEdit, QPlainTextEdit,
QSpinBox, and editable QComboBox) as capturing keyboard input. Use it
to gate the PTT-hold and CW-momentary keying handlers instead of
textInputCaptured(). Arrow-key shortcuts still use textInputCaptured()
unchanged, so Up/Down/Left/Right continue to navigate a focused
combo's list rather than double-firing as tune/gain shortcuts.

Fixes aethersdr#3908
@jensenpat jensenpat changed the title fix(gui): non-editable combo focus swallows first Space PTT / CW key (#3908) fix(gui): Profile Switcher non-editable combo focus swallows first Space PTT / CW key (#3908) Jun 30, 2026
@jensenpat jensenpat marked this pull request as ready for review June 30, 2026 19:15
@jensenpat jensenpat requested a review from a team as a code owner June 30, 2026 19:15

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

Reviewed the diff against the codebase — this is a clean, well-scoped fix. Thanks @jensenpat.

The fix is correct. The root cause is accurately diagnosed: textInputCaptured() casts any focused QComboBox as text-input, so the PTT/CW handlers return false, the app-level filter doesn't consume the key, and Qt's default combo behavior opens the dropdown on the first Space. Splitting out textEntryCaptured() and gating only the two keying handlers with it — while leaving textInputCaptured() for the arrow tune/gain shortcuts — is exactly the right surgical boundary. Combo arrow-navigation stays intact.

A few things I verified:

  • Editable-combo handling is sound. The PR comment claims an editable QComboBox is still caught via its internal QLineEdit. That holds: for an editable combo QApplication::focusWidget() returns the line edit (focus proxy), so qobject_cast<QComboBox*>(w) is false and it falls to the QLineEdit branch → still captures. And even if focus ever landed on the combo itself, the combo->isEditable() branch covers it. Belt and suspenders, both correct.
  • Release paths stay fail-safe. handlePttHoldShortcut's modifier-tolerant release (line 228) still re-checks textEntryCaptured() before un-keying, and the CW handler's !press && !currentlyActive return is updated symmetrically — so press/release gate consistently and TX can't get stuck keyed.
  • No collateral. textInputCaptured() is unchanged for every other caller, includes (QComboBox/QLineEdit/QSpinBox) are already present, focusWidget() is null-checked, and the two handlers have no consumers outside the event filter in this file.

No AppSettings/RAII/lifetime concerns — this touches no persistence or ownership. Nothing blocking from me; looks good to merge once hardware/CI verification lands.


🤖 aethersdr-agent · cost: $5.5276 · 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.

Independently verified against the tree:

  • The app-level event filter (MainWindow_Shortcuts.cpp:407-427) sees the key before the focused QComboBox, so consuming it (return true) correctly beats the dropdown — that's the broken link the old textInputCaptured() left open.
  • textEntryCaptured() still catches editable combos (isEditable() + their internal QLineEdit), so text entry is unaffected.
  • textInputCaptured() is untouched, so arrow-key list navigation / tune-gain suppression are preserved.
  • Press/release gates are symmetric across both handlers.
  • New Space-with-combo-focused behavior matches the filter's own stated intent ('buttons, combos won't steal Space').

All 6 checks green. LGTM.

@ten9876 ten9876 merged commit 9e7009c into aethersdr:main Jun 30, 2026
6 checks passed
ten9876 added a commit that referenced this pull request Jul 2, 2026
#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>
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.

MOX after profile switch activates after second push of space bar

2 participants