Skip to content

refactor(slice): centralize LOCKED tune-feedback timer in SliceModel#3017

Merged
ten9876 merged 1 commit into
mainfrom
auto/fix-2983
May 23, 2026
Merged

refactor(slice): centralize LOCKED tune-feedback timer in SliceModel#3017
ten9876 merged 1 commit into
mainfrom
auto/fix-2983

Conversation

@ten9876

@ten9876 ten9876 commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements #2983. Moves the 500ms LOCKED visual-feedback gate from per-widget timers in VfoWidget and RxApplet to a single QTimer in SliceModel.

Before: each widget had its own m_lockedFrequencyTimer + m_showingLockedFrequencyFeedback bool. Two timers, two state mirrors, same UX concept.

After: one timer + one bool on SliceModel. New signal lockedFeedbackActiveChanged(bool) drives repaint; the existing tuneBlockedByLock() keeps its one-shot semantics for per-event side effects (cancel direct-entry, future status-bar ack — see #2984). setLocked(false) and applyStatus("lock=0") both clear feedback centrally.

Why this is worth doing

  • One state variable for "is feedback active" — no risk of widgets drifting out of sync.
  • Future consumers (spectrum-widget VFO marker, hardware controller LED, status bar) connect to one signal, not two.
  • Simpler clean-up: setSlice() no longer needs to manually clear per-widget state when swapping slices.

Stats

  • 6 files changed, +73 / -83
  • Drops 2 QTimer members + 2 mirrored bool flags + 2 static constexpr 500ms constants across the widget pair.
  • Adds 1 QTimer + 1 bool + 1 kLockedFeedbackMs on SliceModel.

Test plan

  • Clean build (680 targets, 0 warnings)
  • Full test suite passes (18/18)
  • Manual: lock slice via RX applet ⇒ scroll-wheel tune ⇒ both VFO and RX freq label show "LOCKED" together for 500ms, then both clear.
  • Manual: lock slice ⇒ keyboard/MIDI tune ⇒ same as above.
  • Manual: lock slice ⇒ blocked tune ⇒ unlock immediately ⇒ overlay clears immediately (doesn't sit for the remainder of 500ms).
  • Manual: VFO direct-entry open ⇒ blocked tune attempt ⇒ direct-entry cancels, LOCKED shows.

Closes #2983.

🤖 Generated with Claude Code

Move the 500ms LOCKED visual-feedback gate from per-widget timers in
VfoWidget and RxApplet to a single QTimer in SliceModel. Both widgets
previously tracked the same UX concept (slice tune blocked → show
"LOCKED" for 500ms) with independent timers and mirrored bool state.

The new API:
- tuneBlockedByLock() keeps its one-shot semantics for consumers
  needing per-event reactions (cancel direct-entry, future status-bar
  ack — see #2984).
- lockedFeedbackActiveChanged(bool) and isLockedFeedbackActive()
  expose the sustained "is LOCKED overlay active" state. Widgets that
  render the overlay connect to the signal for repaint and read the
  flag in their freq-label update path.
- setLocked(false) and applyStatus("lock=0") both clear the timer +
  flag centrally, so widgets no longer need to mirror unlock events.

Net change: -83 / +73 across the four files, dropping two timers and
two bools in favor of one source of truth. Future consumers (spectrum
overlay, hardware LED, multi-flex bridge) can subscribe to one signal.

Closes #2983.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 requested a review from jensenpat as a code owner May 23, 2026 19:22
@ten9876 ten9876 enabled auto-merge (squash) May 23, 2026 19:22
@ten9876 ten9876 merged commit ed2b7e6 into main May 23, 2026
5 checks passed
@ten9876 ten9876 deleted the auto/fix-2983 branch May 23, 2026 21:06
ten9876 added a commit that referenced this pull request May 24, 2026
## Summary

Implements #2984. Wires `MainWindow::onSliceAdded()` to the new
`SliceModel::lockedFeedbackActiveChanged` signal (from #2983 / #3017) so
a 3-second status-bar message fires on the first blocked tune of a
sustained sequence:

> Slice is locked — unlock to tune.

## Why this is the option-2 path

The issue listed two approaches: per-path latches in `nudgeFreq` /
`rx.tuneKnob` / HID coalesce, or driving the message off a single
SliceModel signal. With #2983 landed, the signal route is one
connection, automatic dedup (only fires on the false→true edge), and
naturally covers every current and future blocked-tune entry point — no
per-call-site latch state.

## Dedup behavior

`lockedFeedbackActiveChanged(true)` fires once when the LOCKED feedback
gate goes active. The 500 ms timer in SliceModel keeps the gate active
across rapid repeated blocked attempts (stuck MIDI knob, held arrow
key), so a burst gives **one toast at the start**, not one per event.
When the timer expires without another block, the gate clears; the next
blocked attempt re-triggers a fresh toast.

## Scope

- 1 file, +10 / -0
- No new state in MainWindow
- Same message for keyboard / MIDI / HID / wheel / direct-entry /
band-shortcut paths — operator knows which input they used; the message
just surfaces the lock cause
- Matches the existing memory-recall lock toast pattern in
MainWindow.cpp

## Test plan

- [x] Clean build, 0 warnings
- [ ] Manual: lock slice, scroll-wheel tune ⇒ LOCKED overlay shows AND
status-bar toast appears
- [ ] Manual: lock slice, hold keyboard right-arrow for 2s ⇒ overlay
shows continuously, ONE toast at start (not spammed)
- [ ] Manual: lock slice, spin MIDI tune knob ⇒ same as above
- [ ] Manual: unlock slice, scroll-tune ⇒ no toast (gate didn't
activate)

Closes #2984.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…ethersdr#3017)

## Summary

Implements aethersdr#2983. Moves the 500ms LOCKED visual-feedback gate from
per-widget timers in `VfoWidget` and `RxApplet` to a single `QTimer` in
`SliceModel`.

Before: each widget had its own `m_lockedFrequencyTimer` +
`m_showingLockedFrequencyFeedback` bool. Two timers, two state mirrors,
same UX concept.

After: one timer + one bool on `SliceModel`. New signal
`lockedFeedbackActiveChanged(bool)` drives repaint; the existing
`tuneBlockedByLock()` keeps its one-shot semantics for per-event side
effects (cancel direct-entry, future status-bar ack — see aethersdr#2984).
`setLocked(false)` and `applyStatus("lock=0")` both clear feedback
centrally.

## Why this is worth doing

- One state variable for "is feedback active" — no risk of widgets
drifting out of sync.
- Future consumers (spectrum-widget VFO marker, hardware controller LED,
status bar) connect to one signal, not two.
- Simpler clean-up: `setSlice()` no longer needs to manually clear
per-widget state when swapping slices.

## Stats

- 6 files changed, **+73 / -83**
- Drops 2 `QTimer` members + 2 mirrored `bool` flags + 2 `static
constexpr` 500ms constants across the widget pair.
- Adds 1 `QTimer` + 1 `bool` + 1 `kLockedFeedbackMs` on `SliceModel`.

## Test plan

- [x] Clean build (680 targets, 0 warnings)
- [x] Full test suite passes (18/18)
- [ ] Manual: lock slice via RX applet ⇒ scroll-wheel tune ⇒ both VFO
and RX freq label show "LOCKED" together for 500ms, then both clear.
- [ ] Manual: lock slice ⇒ keyboard/MIDI tune ⇒ same as above.
- [ ] Manual: lock slice ⇒ blocked tune ⇒ unlock immediately ⇒ overlay
clears immediately (doesn't sit for the remainder of 500ms).
- [ ] Manual: VFO direct-entry open ⇒ blocked tune attempt ⇒
direct-entry cancels, LOCKED shows.

Closes aethersdr#2983.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…dr#3020)

## Summary

Implements aethersdr#2984. Wires `MainWindow::onSliceAdded()` to the new
`SliceModel::lockedFeedbackActiveChanged` signal (from aethersdr#2983 / aethersdr#3017) so
a 3-second status-bar message fires on the first blocked tune of a
sustained sequence:

> Slice is locked — unlock to tune.

## Why this is the option-2 path

The issue listed two approaches: per-path latches in `nudgeFreq` /
`rx.tuneKnob` / HID coalesce, or driving the message off a single
SliceModel signal. With aethersdr#2983 landed, the signal route is one
connection, automatic dedup (only fires on the false→true edge), and
naturally covers every current and future blocked-tune entry point — no
per-call-site latch state.

## Dedup behavior

`lockedFeedbackActiveChanged(true)` fires once when the LOCKED feedback
gate goes active. The 500 ms timer in SliceModel keeps the gate active
across rapid repeated blocked attempts (stuck MIDI knob, held arrow
key), so a burst gives **one toast at the start**, not one per event.
When the timer expires without another block, the gate clears; the next
blocked attempt re-triggers a fresh toast.

## Scope

- 1 file, +10 / -0
- No new state in MainWindow
- Same message for keyboard / MIDI / HID / wheel / direct-entry /
band-shortcut paths — operator knows which input they used; the message
just surfaces the lock cause
- Matches the existing memory-recall lock toast pattern in
MainWindow.cpp

## Test plan

- [x] Clean build, 0 warnings
- [ ] Manual: lock slice, scroll-wheel tune ⇒ LOCKED overlay shows AND
status-bar toast appears
- [ ] Manual: lock slice, hold keyboard right-arrow for 2s ⇒ overlay
shows continuously, ONE toast at start (not spammed)
- [ ] Manual: lock slice, spin MIDI tune knob ⇒ same as above
- [ ] Manual: unlock slice, scroll-tune ⇒ no toast (gate didn't
activate)

Closes aethersdr#2984.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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.

Centralize LOCKED tune-feedback timer on SliceModel (single source of truth)

1 participant