feat(theme): Phase 3 PR 3/5 — paint-color migration tool + SpectrumWidget#3113
Merged
Conversation
…dget pass
Adds migrate_paint_colours.py — sibling of migrate_colours.py for the
QColor literals that live in custom paint code (not setStyleSheet
strings). Handles all three constructor forms:
QColor(0xAA, 0xBB, 0xCC) → ThemeManager::instance().color("token")
QColor(0xAA, 0xBB, 0xCC, alpha) → AetherSDR::theme::withAlpha("token", alpha)
QColor("#aabbcc") → ThemeManager::instance().color("token")
Uses the same CANONICAL_TOKENS table from migrate_colours.py — colours
not in the table are left alone and logged for the cleanup pass.
Also adds AetherSDR::theme::withAlpha() helper in core/ThemeManager.h
so the alpha-bearing migration form stays readable instead of inlining
a lambda at every site.
Applied to src/gui/SpectrumWidget.cpp:
- 33 QColor literals migrated to theme tokens
- 37 left in place (not in the canonical table — spectrum-specific
accent shades, beacon markers, etc. — handled in PR 5 cleanup once
we extend the canonical table for spectrum-domain colours)
This converts the visible-most paint surface in the app — the main
panadapter — to participate in theme switching for those 33 sites.
The remaining 37 spectrum-specific literals stay hardcoded until PR 5
adds canonical tokens for them.
Verified: Linux build clean (68 link steps, no errors), no regressions
flagged by compiler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
ten9876
added a commit
that referenced
this pull request
May 25, 2026
…migration, canonical table closeout (#3117) ## Summary **PR 5 of 5** in the Phase 2/3 wrap-up — closes the mechanical migration sweep. 159 \`QColor\` literals migrated across 11 widget files via the now-mature \`migrate_paint_colours.py\` tool, plus a closing 50-entry canonical-table expansion. ## Files migrated (11) | File | Literals | Notes | |---|---|---| | \`SpectrumWidget\` | 40 | Residual paint code from #3113 | | \`ClientChainWidget\` | 20 | | | \`StripChainWidget\` | 20 | | | \`ClientRxChainWidget\` | 16 | | | \`StripRxChainWidget\` | 16 | | | \`WaveformWidget\` | 15 | rx + strip-mode share the same palette | | \`StripWaveform\` | 15 | | | \`KeyboardMapWidget\` | 8 | 10 \`categoryColor()\` returns left as-is (see Skips) | | \`ClientCompThresholdFader\` | 5 | Meter gradient stops deferred from #3116 | | \`ClientCompEditorCanvas\` | 3 | | | \`ClientGateCurveWidget\` | 1 | Translucent cyan fill deferred from #3116 | ## Canonical-table additions — 50 new close-mappings Three logical groups in \`tools/migrate_colours.py\`, each entry annotated with its sibling canonical value and ΔRGB so the rationale is auditable later: \`\`\`python # Spectrum paint code (25) \"#80d0ff\": \"color.accent.bright\", # peak hold light cyan \"#ff5858\": \"color.accent.danger\", # bright alert red \"#ffc040\": \"color.accent.warning\", # warning amber, ΔRGB≈0 from #ffb84d # … 22 more # Chain / waveform (11) \"#587890\": \"color.text.label\", # waveform centerline \"#ffd166\": \"color.accent.warning\", # peak-hold, ΔRGB≈1 from #ffd070 # … 9 more # Threshold fader / gate gradient stops (6) \"#2f9e6a\": \"color.accent.success\", # comp meter lo green \"#f2362a\": \"color.accent.danger\", # comp meter peak red \"#50b4dc\": \"color.accent.dim\", # gate curve translucent cyan # … 3 more \`\`\` ## Build verified clean - [x] AetherSDR builds clean - [x] All 308 targets build clean (every test target) - Two warnings surface (\`sliceColor\` / \`effectiveGridStepMhz\` unused) — both pre-existing on \`main\`, confirmed via local stash bisect ## Intentional skips (deferred to Phase 4) 1. **10 \`KeyboardMapWidget::categoryColor()\` returns** — semantically distinct hues for Frequency / TX / Audio / DSP / etc. categories. No shared visual language applies; converting would homogenize them and defeat the categorization. Left as raw \`QColor\`. 2. **1 \`ClientCompMeter\` no-go-zone tint (\`#3a1810\`)** — unique meter visual, no canonical equivalent worth absorbing. 3. **Slice indicator runtime theming** — \`SliceColorManager\` still reads from the compile-time \`kSliceColors[]\` table in \`SliceColors.h\`. Default-dark.json already has \`color.slice.a\` through \`.h\` tokens, but wiring \`SliceColorManager\` to read them requires touching \`SpectrumWidget\` overlay rendering + \`VfoWidget\` badges + \`RxApplet\`. Warrants its own PR. 4. **Waterfall colormap runtime theming** — \`color.waterfall.colormap\` gradient token is already shipped in default-dark.json (validating gradient-token support added in Phase 2), but the \`SpectrumWidget\` waterfall renderer hard-codes its colormap stop set. Conversion warrants its own PR. ## Phase 2/3 closeout This closes the **5-PR Phase 2/3 wrap-up** as constrained. Recap: | PR | What | Status | |---|---|---| | #3102 | Mass migration + token taxonomy | merged | | #3106 | MeterSlider pilot (Phase 3 PR 2/5) | merged | | #3113 | Paint-color tool + SpectrumWidget (PR 3/5) | merged | | #3116 | Comp/gate/curve widgets (PR 4/5) | merged | | **#XXXX** | **Spectrum + chain + waveform closeout (PR 5/5)** | **this PR** | Phase 4 picks up: default-light theme + slice indicator runtime theming + waterfall colormap runtime theming. ## Test plan - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Visual smoke: open the app and confirm the spectrum + chain + waveform widgets render to-within-ΔRGB-of-50 of current main (the 159 migrated literals resolve to canonical token values that fall within audited deviation thresholds) 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 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
…dget (aethersdr#3113) ## Summary **PR 3 of 5** in the Phase 2/3 wrap-up. Adds a paint-code QColor migration tool and applies it to \`src/gui/SpectrumWidget.cpp\` — the main panadapter, the visible-most paint surface in AetherSDR. ## The tool: \`tools/migrate_paint_colours.py\` Sibling to \`migrate_colours.py\` (the setStyleSheet migration tool from aethersdr#3102). Handles all three QColor constructor forms: \`\`\` QColor(0xAA, 0xBB, 0xCC) → ThemeManager::instance().color("token") QColor(0xAA, 0xBB, 0xCC, alpha) → AetherSDR::theme::withAlpha("token", alpha) QColor("#aabbcc") → ThemeManager::instance().color("token") \`\`\` Uses the same \`CANONICAL_TOKENS\` table as the setStyleSheet migration tool — colours not in the table are left alone (logged for the cleanup pass). ## New helper: \`AetherSDR::theme::withAlpha\` Added to [core/ThemeManager.h](src/core/ThemeManager.h) so alpha-bearing migrations stay readable: \`\`\`cpp namespace theme { inline QColor withAlpha(const QString& token, int alpha) { QColor c = ThemeManager::instance().color(token); c.setAlpha(alpha); return c; } } \`\`\` Without this, every alpha-bearing paint-code site would inline an ugly lambda. ## Applied to SpectrumWidget - **33 QColor literals migrated** to theme tokens (out of 70 in the file) - **37 skipped** — spectrum-specific accent shades, beacon markers, slice indicators, etc. that aren't in the canonical table yet The migrated 33 cover the main panadapter chrome (backgrounds, axes, body text, common accents). The skipped 37 are spectrum-domain colours that need dedicated tokens (PR 5 cleanup will extend the canonical table for them). ## Verified locally - [x] Linux build clean (68 link steps, no errors) - [x] No regressions flagged by compiler - [x] tools/migrate_paint_colours.py is dry-run by default (--apply to write) ## Why this is incremental The main panadapter now responds to theme changes for the 33 migrated sites. When Phase 4 ships the Default Light theme, the panadapter's overall chrome will adapt; the 37 spectrum-domain literals stay as-is until PR 5 adds their canonical tokens. ## Test plan - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Visual smoke: open the app, confirm panadapter renders identically (the 33 migrated literals resolve to the same hex values as before via the canonical table) 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 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
…migration, canonical table closeout (aethersdr#3117) ## Summary **PR 5 of 5** in the Phase 2/3 wrap-up — closes the mechanical migration sweep. 159 \`QColor\` literals migrated across 11 widget files via the now-mature \`migrate_paint_colours.py\` tool, plus a closing 50-entry canonical-table expansion. ## Files migrated (11) | File | Literals | Notes | |---|---|---| | \`SpectrumWidget\` | 40 | Residual paint code from aethersdr#3113 | | \`ClientChainWidget\` | 20 | | | \`StripChainWidget\` | 20 | | | \`ClientRxChainWidget\` | 16 | | | \`StripRxChainWidget\` | 16 | | | \`WaveformWidget\` | 15 | rx + strip-mode share the same palette | | \`StripWaveform\` | 15 | | | \`KeyboardMapWidget\` | 8 | 10 \`categoryColor()\` returns left as-is (see Skips) | | \`ClientCompThresholdFader\` | 5 | Meter gradient stops deferred from aethersdr#3116 | | \`ClientCompEditorCanvas\` | 3 | | | \`ClientGateCurveWidget\` | 1 | Translucent cyan fill deferred from aethersdr#3116 | ## Canonical-table additions — 50 new close-mappings Three logical groups in \`tools/migrate_colours.py\`, each entry annotated with its sibling canonical value and ΔRGB so the rationale is auditable later: \`\`\`python # Spectrum paint code (25) \"#80d0ff\": \"color.accent.bright\", # peak hold light cyan \"#ff5858\": \"color.accent.danger\", # bright alert red \"#ffc040\": \"color.accent.warning\", # warning amber, ΔRGB≈0 from #ffb84d # … 22 more # Chain / waveform (11) \"#587890\": \"color.text.label\", # waveform centerline \"#ffd166\": \"color.accent.warning\", # peak-hold, ΔRGB≈1 from #ffd070 # … 9 more # Threshold fader / gate gradient stops (6) \"#2f9e6a\": \"color.accent.success\", # comp meter lo green \"#f2362a\": \"color.accent.danger\", # comp meter peak red \"#50b4dc\": \"color.accent.dim\", # gate curve translucent cyan # … 3 more \`\`\` ## Build verified clean - [x] AetherSDR builds clean - [x] All 308 targets build clean (every test target) - Two warnings surface (\`sliceColor\` / \`effectiveGridStepMhz\` unused) — both pre-existing on \`main\`, confirmed via local stash bisect ## Intentional skips (deferred to Phase 4) 1. **10 \`KeyboardMapWidget::categoryColor()\` returns** — semantically distinct hues for Frequency / TX / Audio / DSP / etc. categories. No shared visual language applies; converting would homogenize them and defeat the categorization. Left as raw \`QColor\`. 2. **1 \`ClientCompMeter\` no-go-zone tint (\`#3a1810\`)** — unique meter visual, no canonical equivalent worth absorbing. 3. **Slice indicator runtime theming** — \`SliceColorManager\` still reads from the compile-time \`kSliceColors[]\` table in \`SliceColors.h\`. Default-dark.json already has \`color.slice.a\` through \`.h\` tokens, but wiring \`SliceColorManager\` to read them requires touching \`SpectrumWidget\` overlay rendering + \`VfoWidget\` badges + \`RxApplet\`. Warrants its own PR. 4. **Waterfall colormap runtime theming** — \`color.waterfall.colormap\` gradient token is already shipped in default-dark.json (validating gradient-token support added in Phase 2), but the \`SpectrumWidget\` waterfall renderer hard-codes its colormap stop set. Conversion warrants its own PR. ## Phase 2/3 closeout This closes the **5-PR Phase 2/3 wrap-up** as constrained. Recap: | PR | What | Status | |---|---|---| | aethersdr#3102 | Mass migration + token taxonomy | merged | | aethersdr#3106 | MeterSlider pilot (Phase 3 PR 2/5) | merged | | aethersdr#3113 | Paint-color tool + SpectrumWidget (PR 3/5) | merged | | aethersdr#3116 | Comp/gate/curve widgets (PR 4/5) | merged | | **#XXXX** | **Spectrum + chain + waveform closeout (PR 5/5)** | **this PR** | Phase 4 picks up: default-light theme + slice indicator runtime theming + waterfall colormap runtime theming. ## Test plan - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Visual smoke: open the app and confirm the spectrum + chain + waveform widgets render to-within-ΔRGB-of-50 of current main (the 159 migrated literals resolve to canonical token values that fall within audited deviation thresholds) 73, Jeremy KK7GWY & Claude (AI dev partner) 🤖 Generated with [Claude Code](https://claude.com/claude-code) 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
PR 3 of 5 in the Phase 2/3 wrap-up. Adds a paint-code QColor migration tool and applies it to `src/gui/SpectrumWidget.cpp` — the main panadapter, the visible-most paint surface in AetherSDR.
The tool: `tools/migrate_paint_colours.py`
Sibling to `migrate_colours.py` (the setStyleSheet migration tool from #3102). Handles all three QColor constructor forms:
```
QColor(0xAA, 0xBB, 0xCC) → ThemeManager::instance().color("token")
QColor(0xAA, 0xBB, 0xCC, alpha) → AetherSDR::theme::withAlpha("token", alpha)
QColor("#aabbcc") → ThemeManager::instance().color("token")
```
Uses the same `CANONICAL_TOKENS` table as the setStyleSheet migration tool — colours not in the table are left alone (logged for the cleanup pass).
New helper: `AetherSDR::theme::withAlpha`
Added to core/ThemeManager.h so alpha-bearing migrations stay readable:
```cpp
namespace theme {
inline QColor withAlpha(const QString& token, int alpha) {
QColor c = ThemeManager::instance().color(token);
c.setAlpha(alpha);
return c;
}
}
```
Without this, every alpha-bearing paint-code site would inline an ugly lambda.
Applied to SpectrumWidget
The migrated 33 cover the main panadapter chrome (backgrounds, axes, body text, common accents). The skipped 37 are spectrum-domain colours that need dedicated tokens (PR 5 cleanup will extend the canonical table for them).
Verified locally
Why this is incremental
The main panadapter now responds to theme changes for the 33 migrated sites. When Phase 4 ships the Default Light theme, the panadapter's overall chrome will adapt; the 37 spectrum-domain literals stay as-is until PR 5 adds their canonical tokens.
Test plan
73, Jeremy KK7GWY & Claude (AI dev partner)
🤖 Generated with Claude Code