feat(theme): Phase 5 PR 2 — Theme Editor inspector mode + 505-site reverse-map sweep#3144
Merged
Conversation
…verse-map sweep ## Summary Browser-devtools-style "click on the wrong-coloured part of the UI to fix it" inspector lands on top of the Phase 5 PR 1 token-tree editor. Per RFC #3076 the inspector is the **primary** interaction for the Theme Editor — the token tree is the secondary navigation for users who know token names already. ## New surface **View → Theme Editor → 🎯 Inspect:** - Click the toggle, cursor becomes a cross. - Move over the main UI — a cyan rounded outline tracks the widget under the cursor (top-level overlay, click-through, always-on-top). - Click a region — token list filters to just the tokens painting it, status reads "ClassName: N tokens." If exactly one matches, the colour picker opens immediately for the "click ugly thing → fix it" flow. - ESC cancels without picking. - The dialog itself is invisible to the inspector (no self-targeting). ## Reverse-map coverage The Phase 2 stylesheet resolver already had a `(widget → tokens)` reverse-map populated by `ThemeManager::applyStyleSheet()`, but only 22 sites in the codebase called that API. The remaining 505 sites used the equivalent-but-untracked `widget->setStyleSheet(theme.resolve(template))` pattern, leaving hundreds of widgets invisible to the inspector. This PR sweeps all 505 sites to `applyStyleSheet()` via a balanced- paren-aware Python migration tool (kept under `tools/` for the next sweep we need to do). The change is bit-identical at runtime — `applyStyleSheet` internally does the same `widget->setStyleSheet(resolve(template))` call — but the widget now shows up in the reverse-map. Coverage jumps from 22 → 527 widgets discoverable by the inspector. ## ThemeManager additions - `declareWidgetTokens(widget, tokens)` — explicit hook for custom- paint widgets (panadapter, waterfall, meters, slice indicators) that read tokens directly in `paintEvent` rather than through a stylesheet template. Same destroy-signal cleanup as `applyStyleSheet`. - `reapplyAllTrackedStyleSheets()` now skips entries with empty templates so paint-code-only declarations don't wipe any stylesheet the widget inherited from a parent / `Theme.h` helper. ## ThemeInspector New `src/gui/ThemeInspector.{h,cpp}` — QObject lifecycle: - `start()` installs a `QApplication`-level event filter + shows a frameless translucent overlay (`Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput`) with `WA_TransparentForMouseEvents` so it never intercepts events itself. The global event filter is the authoritative event source. - Mouse moves → `QApplication::widgetAt(globalPos)` → reposition overlay (skipping editor + overlay self-hits). - Left-click → eats the event, emits `widgetPicked(target, localPos)`, auto-deactivates. - ESC → eats the event, emits `canceled()`, deactivates. The dialog walks up the parent chain from the picked widget until it finds a tracked ancestor, so a click on a deep child (a `QLabel` inside a `QPushButton` inside a themed panel) still surfaces the panel-level tokens. ## Migration tool `tools/migrate_setStyleSheet_to_applyStyleSheet.py` — balanced-paren walker that handles arbitrary chained receivers (`m_btn->`, `this->m_btn->`, `btns[i]->`, `statusBar()->`, `parent()->widget->`), multi-line adjacent-string-literal templates, and the dot-form case (`menu.setStyleSheet(...)`) where the receiver is a stack-allocated value and applyStyleSheet needs `&obj`. ## Known limits (follow-up PRs) - Custom-paint widgets (SpectrumWidget, MeterWidget, slice indicators, applet headers painted in code) aren't declared yet. Clicks on the spectrum trace or meter bars fall through to the parent widget's tokens or hit "no tokens registered for this region yet." Phase 5 PR 3 will add the `themeRegions()` mixin from the RFC for fine- grained sub-region lookup on those widgets. - Test `theme_manager_test` has pre-existing stale assertions (from before the Phase 2 token realignment). Not introduced by this PR; needs its own cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
ten9876
added a commit
that referenced
this pull request
May 25, 2026
…sion Three review fixes from PR #3153: 1. MainWindow wiring (blocker) — HealthApplet was registered in the AppletPanel drawer but never given a MeterModel. Without setMeterModel() the three signal connections in HealthApplet::setMeterModel never form, bestSnapshot() always returns invalid, and the applet renders permanently as IDLE / "RF IDLE". Adds the canonical wiring pair next to MeterApplet's: m_appletPanel->healthApplet()->setMeterModel(&m_radioModel.meterModel()); m_appletPanel->healthApplet()->setPowerScale(maxW, ampActive); // updatePowerScale lambda 2. Inspector-discoverability — convert the ~8 setStyleSheet(theme.resolve()) call sites in HealthApplet to ThemeManager::applyStyleSheet(widget, template) so the labels and pills register in the Phase 5 inspector reverse-map (PR #3144's sweep pattern). labelStyle/pillStyle helpers now return unresolved {{token}} templates; new applyLabelStyle/applyPillStyle wrappers route through applyStyleSheet. 3. Tighten the disconnect in HealthApplet::setMeterModel — replace the broad disconnect(m_model, nullptr, this, nullptr) with explicit named-signal disconnects so any unrelated connections to the same model survive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ten9876
added a commit
that referenced
this pull request
May 25, 2026
…on-bar refactor (#3153) ## Summary Rebased version of @jensenpat's #3133 onto PR #3150's customisable button-bar architecture. Same `HealthApplet.{h,cpp}` source verbatim from #3133 (zero changes to applet code) — this PR is purely the AppletPanel integration delta that adapts to PR #3150's new `BarButton` registry + favorites/drawer model. **Depends on #3150** — currently targets `fix/button-bar`. Once #3150 merges to main, this PR's base will be retargeted to main and the diff will collapse to just the HLTH integration touchpoints. ## What changed vs #3133 Three integration touchpoints adjusted; HealthApplet itself untouched: 1. **`makeEntry()` callsite** routes through `m_drawer` / `m_drawerLayout` (the new drawer layout from #3150), matching every other drawer-resident applet's pattern. PR #3150's `registerBarButton()` inside `makeEntry()` handles bar-button registration automatically — no manual `btnRow2` plumbing required. 2. **Default position** moved from index 4 (between TX and PHNE) to after MTR in the drawer: ```cpp "RX", "TUN", "AMP", "TX", "PHNE", "P/CW", "EQ", "WAVE", "TXDSP", "CAT", "DAX", "TCI", "IQ", "MTR", "HLTH", "AG", "SS" ``` With the new 5-button favorites row, putting HLTH at index 4 would push PHNE out of favorites and surprise shipping users. Operators who want HLTH up top can drag it via the right-click Favorites picker. 3. **CMakeLists.txt** — `src/gui/HealthApplet.cpp` added to `GUI_SOURCES`. ## Credit Commit attribution stays with @jensenpat as the HealthApplet author. This PR exists only so the HLTH work doesn't have to wait on the contributor doing a manual rebase after #3150 lands — they're welcome to keep #3133 open if they'd prefer to do the rebase themselves. ## Review of HLTH itself The applet code is well-built — appreciated zero hardcoded colours (every paint and stylesheet call reads from `ThemeManager`), clean paint-method separation, visibility-gated 20 Hz timer, three-tier meter source priority (amp → TGXL tuner → radio TX), and EWMA + recent-window variance detection. One small consistency note for a follow-up: HLTH uses `widget->setStyleSheet(theme.resolve(...))` (~8 sites) instead of `theme.applyStyleSheet(widget, ...)`. That's the pattern PR #3144 swept across the rest of the codebase to make widgets inspector-discoverable. A two-line conversion would let the Phase 5 inspector find HLTH labels. Tiny follow-up — not blocking this PR. ## Test plan - [x] Linux (x86_64) — builds clean against `fix/button-bar` (`cmake --build --target all`) - [ ] Live smoke: HLTH appears in the drawer after MTR; right-click bar → Favorites picker shows HLTH in Active column - [ ] Once #3150 merges, retarget base to main, confirm no further diff change 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Pat Jensen <jensenpat@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closed
2 tasks
G6PWY-Chris
pushed a commit
to G6PWY-Chris/AetherSDR
that referenced
this pull request
Jun 22, 2026
…verse-map sweep (aethersdr#3144) ## Summary Browser-devtools-style "click on the wrong-coloured part of the UI to fix it" inspector lands on top of the Phase 5 PR 1 token-tree editor. Per [RFC aethersdr#3076](aethersdr#3076) the inspector is the **primary** interaction for the Theme Editor — the token tree is the secondary navigation. ## New surface **View → Theme Editor → 🎯 Inspect:** - Click the toggle, cursor becomes a cross. - Move over the main UI — a cyan rounded outline tracks the widget under the cursor (top-level overlay, click-through, always-on-top). - Click a region — token list filters to just the tokens painting it, status reads `ClassName: N tokens`. If exactly one matches, the colour picker opens immediately for the "click ugly thing → fix it" flow. - ESC cancels without picking. - The dialog itself is invisible to the inspector (no self-targeting). ## Reverse-map coverage: 22 → 527 widgets The Phase 2 stylesheet resolver already had a `(widget → tokens)` reverse-map populated by `ThemeManager::applyStyleSheet()`, but only 22 sites in the codebase called that API. The remaining 505 sites used the equivalent-but-untracked `widget->setStyleSheet(theme.resolve(template))` pattern, leaving hundreds of widgets invisible to the inspector. This PR sweeps all 505 sites to `applyStyleSheet()` via a balanced-paren-aware Python migration tool (kept under `tools/` for the next sweep we need to do). **Bit-identical at runtime** — `applyStyleSheet` internally does the same `widget->setStyleSheet(resolve(template))` call — but the widget now shows up in the reverse-map. ## `ThemeManager` additions - `declareWidgetTokens(widget, tokens)` — explicit hook for custom-paint widgets (panadapter, waterfall, meters, slice indicators) that read tokens directly in `paintEvent` rather than through a stylesheet template. Same destroy-signal cleanup as `applyStyleSheet`. - `reapplyAllTrackedStyleSheets()` now skips entries with empty templates so paint-code-only declarations don't wipe any stylesheet the widget inherited from a parent / `Theme.h` helper. ## `ThemeInspector` class `src/gui/ThemeInspector.{h,cpp}` — `QObject` lifecycle: - `start()` installs a `QApplication`-level event filter + shows a frameless translucent overlay (`Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput`) with `WA_TransparentForMouseEvents` so it never intercepts events itself. The global event filter is the authoritative event source. - Mouse moves → `QApplication::widgetAt(globalPos)` → reposition overlay (skipping editor + overlay self-hits). - Left-click → eats the event, emits `widgetPicked(target, localPos)`, auto-deactivates. - ESC → eats the event, emits `canceled()`, deactivates. The dialog walks up the parent chain from the picked widget until it finds a tracked ancestor, so a click on a deep child (a `QLabel` inside a `QPushButton` inside a themed panel) still surfaces the panel-level tokens. ## Migration tool `tools/migrate_setStyleSheet_to_applyStyleSheet.py` — balanced-paren walker that handles arbitrary chained receivers (`m_btn->`, `this->m_btn->`, `btns[i]->`, `statusBar()->`, `parent()->widget->`), multi-line adjacent-string-literal templates, and the dot-form case (`menu.setStyleSheet(...)`) where the receiver is a stack-allocated value and `applyStyleSheet` needs `&obj`. ## Known limits (follow-up PRs) - Custom-paint widgets (`SpectrumWidget`, `MeterWidget`, slice indicators on the panadapter, applet headers painted in code) aren't declared yet. Clicks on the spectrum trace or meter bars fall through to the parent widget's tokens or hit "no tokens registered for this region yet." **Phase 5 PR 3** will add the `themeRegions()` mixin from the RFC for fine-grained sub-region lookup. - Test `theme_manager_test` has pre-existing stale assertions from before the Phase 2 token realignment. Not introduced by this PR; needs its own cleanup. ## Test plan - [x] Builds clean (`--target all`, including all test executables — no bot-CMake-omission regressions) - [x] Visual smoke: every panel/dialog/applet renders identically (sweep is runtime-bit-identical) - [x] Inspector flow on KDE Plasma X11 — outline tracking, click-to-pick, ESC cancel all working - [x] Slice strip widgets (VfoWidget) inspectable: slice badge, filter-width label, freq label, band/mode buttons, mute/lock/tx - [x] Title bar + status bar inspectable - [ ] Wayland session smoke (untested locally — relying on CI) - [ ] Custom-paint widgets (panadapter, waterfall) — known not-yet-instrumented; falls through to parent chain 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
…3150 button-bar refactor (aethersdr#3153) ## Summary Rebased version of @jensenpat's aethersdr#3133 onto PR aethersdr#3150's customisable button-bar architecture. Same `HealthApplet.{h,cpp}` source verbatim from aethersdr#3133 (zero changes to applet code) — this PR is purely the AppletPanel integration delta that adapts to PR aethersdr#3150's new `BarButton` registry + favorites/drawer model. **Depends on aethersdr#3150** — currently targets `fix/button-bar`. Once aethersdr#3150 merges to main, this PR's base will be retargeted to main and the diff will collapse to just the HLTH integration touchpoints. ## What changed vs aethersdr#3133 Three integration touchpoints adjusted; HealthApplet itself untouched: 1. **`makeEntry()` callsite** routes through `m_drawer` / `m_drawerLayout` (the new drawer layout from aethersdr#3150), matching every other drawer-resident applet's pattern. PR aethersdr#3150's `registerBarButton()` inside `makeEntry()` handles bar-button registration automatically — no manual `btnRow2` plumbing required. 2. **Default position** moved from index 4 (between TX and PHNE) to after MTR in the drawer: ```cpp "RX", "TUN", "AMP", "TX", "PHNE", "P/CW", "EQ", "WAVE", "TXDSP", "CAT", "DAX", "TCI", "IQ", "MTR", "HLTH", "AG", "SS" ``` With the new 5-button favorites row, putting HLTH at index 4 would push PHNE out of favorites and surprise shipping users. Operators who want HLTH up top can drag it via the right-click Favorites picker. 3. **CMakeLists.txt** — `src/gui/HealthApplet.cpp` added to `GUI_SOURCES`. ## Credit Commit attribution stays with @jensenpat as the HealthApplet author. This PR exists only so the HLTH work doesn't have to wait on the contributor doing a manual rebase after aethersdr#3150 lands — they're welcome to keep aethersdr#3133 open if they'd prefer to do the rebase themselves. ## Review of HLTH itself The applet code is well-built — appreciated zero hardcoded colours (every paint and stylesheet call reads from `ThemeManager`), clean paint-method separation, visibility-gated 20 Hz timer, three-tier meter source priority (amp → TGXL tuner → radio TX), and EWMA + recent-window variance detection. One small consistency note for a follow-up: HLTH uses `widget->setStyleSheet(theme.resolve(...))` (~8 sites) instead of `theme.applyStyleSheet(widget, ...)`. That's the pattern PR aethersdr#3144 swept across the rest of the codebase to make widgets inspector-discoverable. A two-line conversion would let the Phase 5 inspector find HLTH labels. Tiny follow-up — not blocking this PR. ## Test plan - [x] Linux (x86_64) — builds clean against `fix/button-bar` (`cmake --build --target all`) - [ ] Live smoke: HLTH appears in the drawer after MTR; right-click bar → Favorites picker shows HLTH in Active column - [ ] Once aethersdr#3150 merges, retarget base to main, confirm no further diff change 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Pat Jensen <jensenpat@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Browser-devtools-style "click on the wrong-coloured part of the UI to fix it" inspector lands on top of the Phase 5 PR 1 token-tree editor. Per RFC #3076 the inspector is the primary interaction for the Theme Editor — the token tree is the secondary navigation.
New surface
View → Theme Editor → 🎯 Inspect:
ClassName: N tokens. If exactly one matches, the colour picker opens immediately for the "click ugly thing → fix it" flow.Reverse-map coverage: 22 → 527 widgets
The Phase 2 stylesheet resolver already had a
(widget → tokens)reverse-map populated byThemeManager::applyStyleSheet(), but only 22 sites in the codebase called that API. The remaining 505 sites used the equivalent-but-untrackedwidget->setStyleSheet(theme.resolve(template))pattern, leaving hundreds of widgets invisible to the inspector.This PR sweeps all 505 sites to
applyStyleSheet()via a balanced-paren-aware Python migration tool (kept undertools/for the next sweep we need to do).Bit-identical at runtime —
applyStyleSheetinternally does the samewidget->setStyleSheet(resolve(template))call — but the widget now shows up in the reverse-map.ThemeManageradditionsdeclareWidgetTokens(widget, tokens)— explicit hook for custom-paint widgets (panadapter, waterfall, meters, slice indicators) that read tokens directly inpaintEventrather than through a stylesheet template. Same destroy-signal cleanup asapplyStyleSheet.reapplyAllTrackedStyleSheets()now skips entries with empty templates so paint-code-only declarations don't wipe any stylesheet the widget inherited from a parent /Theme.hhelper.ThemeInspectorclasssrc/gui/ThemeInspector.{h,cpp}—QObjectlifecycle:start()installs aQApplication-level event filter + shows a frameless translucent overlay (Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput) withWA_TransparentForMouseEventsso it never intercepts events itself. The global event filter is the authoritative event source.QApplication::widgetAt(globalPos)→ reposition overlay (skipping editor + overlay self-hits).widgetPicked(target, localPos), auto-deactivates.canceled(), deactivates.The dialog walks up the parent chain from the picked widget until it finds a tracked ancestor, so a click on a deep child (a
QLabelinside aQPushButtoninside a themed panel) still surfaces the panel-level tokens.Migration tool
tools/migrate_setStyleSheet_to_applyStyleSheet.py— balanced-paren walker that handles arbitrary chained receivers (m_btn->,this->m_btn->,btns[i]->,statusBar()->,parent()->widget->), multi-line adjacent-string-literal templates, and the dot-form case (menu.setStyleSheet(...)) where the receiver is a stack-allocated value andapplyStyleSheetneeds&obj.Known limits (follow-up PRs)
SpectrumWidget,MeterWidget, slice indicators on the panadapter, applet headers painted in code) aren't declared yet. Clicks on the spectrum trace or meter bars fall through to the parent widget's tokens or hit "no tokens registered for this region yet." Phase 5 PR 3 will add thethemeRegions()mixin from the RFC for fine-grained sub-region lookup.theme_manager_testhas pre-existing stale assertions from before the Phase 2 token realignment. Not introduced by this PR; needs its own cleanup.Test plan
--target all, including all test executables — no bot-CMake-omission regressions)