Skip to content

feat(cw): make Delay, Speed, Sidetone, and Pitch value fields editable#2429

Merged
ten9876 merged 3 commits into
aethersdr:mainfrom
chibondking:feature/cw-editable-value-fields
May 7, 2026
Merged

feat(cw): make Delay, Speed, Sidetone, and Pitch value fields editable#2429
ten9876 merged 3 commits into
aethersdr:mainfrom
chibondking:feature/cw-editable-value-fields

Conversation

@chibondking

Copy link
Copy Markdown
Collaborator

Replaces the read-only QLabel value displays in the CW sub-panel with QLineEdit widgets, matching SmartSDR behavior. Users can now click any value and type a number directly rather than dragging the slider.

  • Each field has a QIntValidator clamped to its valid range (Delay 0–2000 ms, Speed 5–100 WPM, Sidetone 0–100, Pitch 100–6000 Hz)
  • editingFinished clamps, corrects display, and drives the slider so the existing slider→model command path fires unchanged (no feedback loop)
  • Slider moves continue to update the field in real time
  • Focus highlight (#00b4d8 border) gives clear visual feedback on the active field
  • No protocol changes; all values route through existing model setters

Replaces the read-only QLabel value displays in the CW sub-panel with
QLineEdit widgets, matching SmartSDR behavior. Users can now click any
value and type a number directly rather than dragging the slider.

- Each field has a QIntValidator clamped to its valid range
  (Delay 0–2000 ms, Speed 5–100 WPM, Sidetone 0–100, Pitch 100–6000 Hz)
- editingFinished clamps, corrects display, and drives the slider so the
  existing slider→model command path fires unchanged (no feedback loop)
- Slider moves continue to update the field in real time
- Focus highlight (#00b4d8 border) gives clear visual feedback on the
  active field
- No protocol changes; all values route through existing model setters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@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 @chibondking — clean, well-scoped change. Same pattern applied consistently across all four fields, validators are parented to the edits, the editingFinished handlers re-clamp defensively, and routing the value through the existing slider path keeps the model-write path unchanged. No protocol changes, and m_updatingFromModel already guards the slider→model emit so there's no feedback loop.

One UX nit worth thinking about (not blocking):

syncCwFromModel() clobbers in-progress edits. It calls setText() on each QLineEdit unconditionally (PhoneCwApplet.cpp:700, 703, 707, 714). With QLabel that was harmless, but on a QLineEdit with focus and a half-typed value, the user's input is replaced when any status update arrives — e.g. the operator types "1" intending "1500" into Delay, and a status echo from a sidetone slider drag (or any other phoneStateChanged emission) wipes them back to the model value mid-keystroke. Easy guard:

if (!m_delayLabel->hasFocus())
    m_delayLabel->setText(QString::number(m_model->cwDelay()));

…repeated for the other three. The optimistic-update fix in #2428 reduces how often this happens for Delay, but Speed/Sidetone/Pitch have the same shape and any slice-state update can land on the worker side.

Two smaller things you can take or leave:

  • The m_*Label names now hold QLineEdit* — fine, but if you're touching this anyway, m_*Edit would read better.
  • The bare setStyleSheet block on m_pitchLabel (PhoneCwApplet.cpp:582-584) duplicates kInsetEditStyle with only the font-size and padding changed; could pull a second constant or pass them in, though there's only one caller so not a strong push.

The hasFocus guard is the only one I'd actually ask for. Otherwise this is good to go.

Replaces the read-only QLabel value displays in the CW sub-panel with
QLineEdit widgets, matching SmartSDR behavior. Users can now click any
value and type a number directly rather than dragging the slider.

- Each field has a QIntValidator clamped to its valid range
  (Delay 0–2000 ms, Speed 5–100 WPM, Sidetone 0–100, Pitch 100–6000 Hz)
- editingFinished clamps, corrects display, and drives the slider so the
  existing slider→model command path fires unchanged (no feedback loop)
- hasFocus() guards in both the slider valueChanged connections and
  syncCwFromModel() prevent mid-keystroke clobbering when a status echo
  arrives while the user is typing
- Slider moves continue to update the field in real time when unfocused
- Focus highlight (#00b4d8 border) gives clear visual feedback
- Renamed m_*Label → m_*Edit for the four QLineEdit members
- No protocol changes; all values route through existing model setters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chibondking

Copy link
Copy Markdown
Collaborator Author

@AetherClaude changes applied.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Reviewed the diff — implementation is clean and the wiring is correct. A few notes:

Feedback-loop avoidance looks right. The editingFinished handler sets the edit text first, then calls m_*Slider->setValue(v). The slider's valueChanged callback re-checks hasFocus() on the edit before setting text, so:

  • Enter pressed (focus stays on edit) → slider's setText is skipped, edit already shows the clamped value.
  • Focus lost (Tab/click) → slider's setText runs, but it's a no-op write of the same value.

Either way the model setter fires once via the slider path, so the existing command-emit path is preserved. Good call routing through the slider rather than calling m_model->setCwDelay(v) directly from the edit handler — keeps a single source of truth.

syncCwFromModel is safe. The explicit if (!hasFocus()) setText(...) after each slider->setValue is technically redundant (the slider's own valueChanged lambda would set the text), but it's defensive and harmless — and makes the sync read clearly when scanning the function. Worth keeping.

Pitch path is consistent. Since pitch has no slider, editingFinished calls setCwPitch directly, mirroring what the up/down buttons already do. Note editingFinished can fire twice on the same edit (once on Enter, again on focus-loss); the clamp + setText is idempotent so this is fine.

Empty input edge case. text().toInt() returns 0 on empty input, and qBound snaps it to each field's minimum (0/5/0/100). Reasonable behavior — users get a sane floor instead of an error.

Minor accessibility nit (optional). The new line edits set setAccessibleName but no setAccessibleDescription (matching the old labels). The corresponding sliders have descriptions like "CW break-in delay in milliseconds" — could mirror those on the edits for screen readers, e.g. m_delayEdit->setAccessibleDescription("CW break-in delay in milliseconds, 0 to 2000"). Not blocking.

Net: looks good to me. The hasFocus() guards in both directions are the key correctness piece and you've got them right.

@chibondking

Copy link
Copy Markdown
Collaborator Author

@AetherClaude A11Y changes added.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

A11Y additions look good — all four edits now carry descriptions and they consistently extend their corresponding slider descriptions with the valid range:

  • Delay edit: "CW break-in delay in milliseconds, 0 to 2000" ↔ slider "CW break-in delay in milliseconds"
  • Speed edit: "CW keying speed in words per minute, 5 to 100" ↔ slider "CW keying speed in words per minute"
  • Sidetone edit: "CW sidetone monitor volume, 0 to 100" ↔ slider "CW sidetone monitor volume"
  • Pitch edit: "CW sidetone pitch in Hz, 100 to 6000" (no slider counterpart, but matches the same <purpose>, <min> to <max> shape)

Including the range on the editable widget is the right call — for a typed input, screen reader users benefit from knowing the bounds before they start typing, where on a slider it's implicit from the track. Pattern is consistent with how m_micLevelSlider already documents its range elsewhere in this file, so nothing feels out of place.

Nothing else to flag from me — implementation, feedback-loop guards, and accessibility are all in good shape. LGTM.

@ten9876 ten9876 merged commit 903bdbc into aethersdr:main May 7, 2026
5 checks passed
@ten9876

ten9876 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

Claude here — clean SmartSDR-parity feature, CJ. The hasFocus() guards on the slider valueChanged paths AND on syncCwFromModel are exactly the right shape — mid-edit input is preserved against radio echoes, and Enter-then-loss-of-focus still routes through the slider so the existing model command path fires unchanged. Validator + qBound belt-and-braces is also good (validator blocks bad chars, qBound clamps out-of-range partial input). Pairs nicely with #2428 — that fix was the prerequisite for these edits to actually persist. Merged.

73, Jeremy KK7GWY & Claude (AI dev partner)

ten9876 added a commit that referenced this pull request May 7, 2026
… sweep (#2441)

Bumps version 0.9.7 → 0.9.8 in CMakeLists.txt, README.md, and
CLAUDE.md. Adds the v0.9.8 CHANGELOG entry covering 16 PRs since
v0.9.7, grouped Features / Bug fixes / Infrastructure with prominent
contributor credits to @chibondking, @jensenpat, @rfoust, and @s53zo,
plus @ct1drb for the cherry-picked rigctld pipe-separator fix.
WhatsNewData.cpp regenerated from CHANGELOG.md.

Theme: Aetherial Audio Channel Strip RX side (#2425) lands as the
headline feature, paired with Hamlib NET-rigctl / Not1MM interop
(#2438), an editable CW value-fields UX upgrade (#2429), and an
AetherDSP Settings dialog chrome refit. Reliability sweep covers two
silent Windows footguns (UI Scale completely broken on Windows;
spot clients crashed on disconnect cascade), hardware-PTT mic_selection
combinations, MIDI VFO jog-wheel stabilization, and a thrice-reported
filter-width-indicator drift fix.

Tag held until after this lands and CI passes.

Co-authored-by: Claude Opus 4.7 (1M context) <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.

2 participants