Skip to content

feat(slice): radio-letter slice display mode for Multi-Flex (#2606)#2708

Merged
ten9876 merged 4 commits into
mainfrom
auto/fix-2606
May 16, 2026
Merged

feat(slice): radio-letter slice display mode for Multi-Flex (#2606)#2708
ten9876 merged 4 commits into
mainfrom
auto/fix-2606

Conversation

@ten9876

@ten9876 ten9876 commented May 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #2606. Adds an opt-in display mode that switches slice badges from the global slot letter ('A' + sliceId) to the radio-provided per-client letter with a 1-based global-slot subscript (e.g. A₂ for a secondary client's first slice on global slot 1).

Wolfgang (HB9RYZ) reported that secondary Multi-Flex clients see slice C/D in AetherSDR where SmartSDR shows A/B. The radio sends per-client letters via the index_letter slice-status field — we now parse it and surface it as a UI option without re-wiring any of the internal slice-id plumbing.

Default behaviour unchanged

SliceLetterDisplay defaults to "Global" in AppSettings. Existing users see no behaviour change until they flip the toggle at Radio Setup → Themes → Slice Letter Display.

What's in RadioIndexed mode

  • Badges show A₂ (radio letter + 1-based global-slot subscript).
  • Colour follows the letter — user's "A" slice is cyan regardless of which physical slot it landed on. Spectrum slice marker, filter passband, VFO badge, and RX-applet badge all consistent.
  • RX Applet tab row lays out as three groups, left-to-right:
    • Owned slices (in global-sliceId order, e.g. A₂)
    • Empty available slots labeled with the next sequential letter (B, C, …) — previews what the radio would call the slice if claimed
    • Foreign in-use slots rendered as (with the dim foreign styling)
  • Subscript hidden in Global mode so single-client users don't see noise.

Architecture

  • SliceModel::letter() returns the radio's index_letter (fallback 'A' + sliceId) + letterChanged signal.
  • RadioModel now tracks m_foreignSliceOwners and emits slotOccupancyChanged instead of dropping foreign-client slices silently.
  • New SliceLabel helper module (richText, unicodeForm, drawSliceBadge, displayColorIndex) reads AppSettings("SliceLetterDisplay") on every call so the runtime view tracks the toggle without restart.
  • SpectrumWidget::SliceOverlay gains a perClientLetter field; MainWindow keeps it synced via setSliceOverlayLetter on slice add and on letterChanged.
  • PanadapterApplet::setSliceId(int, const QString&) takes the per-client letter; 4 MainWindow callers pass slice->letter().

What did NOT change

  • Slice IDs in commands, signal routing, settings keys, model state — all global, same as before.
  • DAX channel and other letter-keyed AppSettings keep their current scheme; no migration.

Drive-by cleanups

  • src/gui/RxApplet.cpp.bak — a 793-line stale backup that had been committed by accident months ago. Deleting it as part of this PR so it doesn't keep showing up in repo grep results.

Follow-up commits in this PR

The first commit landed the feature; subsequent commits address review nits:

  • f0ba4e8 — off-screen spectrum pill + context-menu labels use SliceLabel::unicodeForm so the displayed letter tracks the mode; second RadioSetupDialog instance (Settings → Flex Control) now also wires sliceLetterDisplayModeChanged; updateSliceButtons memoises the colour index per button to skip stylesheet rebuilds when nothing colour-relevant changed; new slice_model_letter_test (6 cases) covering index_letter parsing, letterChanged emission semantics, and emitLetterRefresh.

Test plan

  • slice_label_test covers both modes plus fallbacks (10 checks incl. displayColorIndex)
  • slice_model_letter_test — 6 cases covering letter parsing, signal emission, refresh
  • Single-client session: badge reads B, no subscript (Global default)
  • Multi-Flex secondary client (primary on global slots 0/1): flip toggle → badge reads A₃, tab row shows [A₃] [B] [C] [—]
  • Single-client session: badge reads B, no subscript (Global default)
  • Foreign-slot tooltip shows the other station's name
  • Foreign client closes their slice → tab row reflows: dash → available letter
  • Spectrum slice marker colour follows the displayed letter in RadioIndexed mode
  • Toggle the mode mid-session → all badges + spectrum repaint without restart

🤖 Generated with Claude Code

Adds an opt-in display mode (Radio Setup → Themes → Slice Letter
Display) that switches slice badges from the global slot letter
('A' + sliceId) to the radio-provided per-client letter with a
1-based global-slot subscript (e.g. "A₂" for the second-client's
first slice on global slot 1).

Wolfgang (HB9RYZ) reported that secondary Multi-Flex clients see
slice C / D where SmartSDR shows A / B. The radio sends per-client
letters via `index_letter`; we now parse and surface them as a
display option without re-wiring any of the internal slice-id
plumbing (settings keys, signal routing, command building all stay
keyed on the global sliceId).

Defaults to "Global" so existing users see no behaviour change.

In RadioIndexed mode the RX Applet tab row also reorders to:
  [owned slices, sorted by sliceId] [empty slots with sequential
  letters] [foreign in-use slots as "—"]

So a user on a 4-slot radio with one slice and one other client
sees: [A₂] [B] [C] [—].  Colour follows the displayed letter so
the user's "A" slice stays cyan regardless of which global slot it
landed on.  Spectrum marker / passband colour follows the same
rule via SliceOverlay::perClientLetter.

Also cleans up a stale RxApplet.cpp.bak that had been committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 enabled auto-merge (squash) May 16, 2026 01:09
ten9876 and others added 2 commits May 15, 2026 18:26
Foreign in-use slots in the RX-applet tab row previously rendered as
the dim global letter ("A" / "B" / ...) in Global mode and "—" in
RadioIndexed mode.  The dim-letter form is visually indistinguishable
from an empty available slot for colour-blind users, so the in-use
state didn't read at a glance.

Unify on "—" in both modes — the dash glyph plus the foreign-style
QSS rule reads as "taken, not yours" regardless of colour vision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ize, tests (#2606)

Addresses follow-up items raised in the PR self-review:

1. **Spectrum off-screen pill + context menus** now resolve the
   slice letter via SliceLabel::unicodeForm(sliceId, perClientLetter)
   so the displayed letter follows the same display mode as the
   marker colour.  Previously the pill could read "C" while the
   marker was painted in cyan because the user's slice was A₂.

2. **Serial-shortcut RadioSetupDialog** (the Settings → Flex Control
   path) now also connects the sliceLetterDisplayModeChanged signal
   to the slice-letter refresh fan-out so toggling the mode from
   that dialog repaints live, not on the next slice event.

3. **RxApplet tab-row stylesheet memoization** — store the last
   applied colour index per button and skip setStyleSheet when
   nothing colour-relevant changed.  updateSliceButtons fires on
   every slot occupancy + letter change; cheap, but no reason to
   re-parse the stylesheet when the palette is unchanged.

4. **slice_model_letter_test** — 6 cases covering letter() fallback,
   index_letter parse + letterChanged emission, no-op re-apply,
   letter change, emitLetterRefresh, and unrelated status messages
   not disturbing the letter.

Co-Authored-By: Claude Opus 4.7 (1M context) <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 @ten9876 — this is a really thoughtful build-out for a fairly subtle Multi-Flex problem. The SliceLabel namespace is a clean place to land the mode logic, the default-off / opt-in toggle keeps existing single-client users invariant, and the tests cover both modes plus the fallbacks. Removing the stale RxApplet.cpp.bak is appreciated as a drive-by.

A few small observations, mostly minor:

1. SliceLabel::drawSliceBadge subscript size assumes a pixel-size font (src/gui/SliceLabel.cpp):

subFont.setPixelSize(std::max(7, (mainFont.pixelSize() * 7) / 10));

When the caller's font was set by setPointSize() (the common case — VfoWidget::paintEvent doesn't set a pixel size on badgeFont), mainFont.pixelSize() returns -1, the expression collapses to 0, and std::max(7, 0) pins the subscript at 7 px regardless of how large the main letter is. The output is still legible, but it won't scale with the badge. Consider falling back to QFontMetrics(mainFont).height() (or pointSize()) when pixelSize() is -1.

2. Foreign-owner cleanup on hard disconnect (src/models/RadioModel.cpp):

m_foreignSliceOwners is only pruned inside handleSliceStatus (either when we receive ownership of the slot or when a removed=1 status arrives). If a foreign Multi-Flex client drops without the radio publishing a removed=1 for their slices — abrupt TCP close, network blip — the foreign markers would persist and the slot would stay dimmed in the tab row until something else cleared it. I didn't see a cleanup path in this diff; worth verifying that client / disconnect handling elsewhere drops the entry (or clear it from the per-client-handle teardown path).

3. state as const char* with qstrcmp (src/gui/RxApplet.cpp updateSliceButtons): functional but a enum class SlotState { Ours, Foreign, Empty } would read more clearly and let the compiler check the branches. Not blocking — just a small future-cleanup note.

4. Property-cache key is colourIdx only (applyStyleIfChanged): if a button transitions ours → empty (or ours → foreign) but the colour happens to stay the same, the QSS string isn't rebuilt. The slotState dynamic property + unpolish/polish handles the visual switch via the attribute selector, so this is correct as written — just calling it out because the cache key looks under-specified at first glance. A short comment confirming "QSS attribute selector handles state transitions; cache only tracks colour" would save the next reader a head-scratch.

5. setTextFormat(Qt::RichText) is set on every text update in DaxApplet / TciApplet / PanadapterApplet rather than once at label construction. Harmless, just slightly wasteful — could be moved to the label's setup site.

Nothing here blocks the feature. The default-off behaviour and the test coverage make this safe to land for users who don't care, and the architecture leaves room to deepen the per-client tracking later. Nice work.

Five non-blocking observations from the bot review, all addressed in
this commit:

1. SliceLabel::drawSliceBadge now falls back to
   QFontMetrics(mainFont).height() when the painter's font was set via
   setPointSize() rather than setPixelSize() (the latter returns -1,
   which collapsed the subscript-size math to a fixed 7px regardless
   of badge size).

2. RadioModel now drops m_foreignSliceOwners entries for a client
   handle when the radio reports `client 0x<handle> disconnected`, and
   clears the whole map alongside the three other m_clientStations
   reset paths.  Covers the abrupt-disconnect case where the radio may
   not echo `slice N removed=1` for the dropped client's slices.

3. RxApplet::updateSliceButtons switches from `const char*` + qstrcmp
   to a function-scoped `enum class SlotState`.  Compiler-checked
   dispatch, cleaner code.

4. applyStyleIfChanged grew a comment explaining that the
   colour-index-only cache key is intentional — state transitions
   (ours/foreign/empty) at the same colour are handled by the
   slotState dynamic property + QSS attribute selector via
   unpolish/polish, not by the stylesheet rebuild.

5. setTextFormat(Qt::RichText) hoisted from per-update callsites to
   widget construction in DaxApplet, TciApplet, PanadapterApplet,
   RxApplet, and VfoWidget.  Slightly less wasteful; the format is set
   once at the same place we set the rest of the label's style.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ten9876 ten9876 merged commit e074301 into main May 16, 2026
5 checks passed
@ten9876 ten9876 deleted the auto/fix-2606 branch May 16, 2026 04:17
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.

Slice number using flex 6700 remote via VPN

1 participant