feat(theme): Phase 3 PR 4/5 — file-scope const QColor migration in comp/gate/curve widgets#3116
Merged
Conversation
…mp/gate/curve widgets
Migrates 76 QColor literals across 9 meter/curve widget files from static
file-scope const QColor declarations to inline-function form that resolves
through ThemeManager at call time. File-scope `const QColor kName("#hex");`
becomes `inline QColor kName() { return ThemeManager::instance().color("tok"); }`
and every call site `kName` is rewritten to `kName()`.
Why inline functions and not direct ThemeManager lookups: the original
declarations construct QColor at static-init time, which is too early for
the ThemeManager singleton; converting to a function defers the lookup
to first paint, after Qt and the manager are fully constructed.
Tool changes — tools/migrate_paint_colours.py:
* New FILE_SCOPE_HEX_RE and FILE_SCOPE_RGB_RE patterns for the
declarations themselves
* rewrite_file_scope_consts() that:
- Replaces matching declarations with the inline-function form
- Tracks renamed identifiers
- Sweeps the file to rewrite every bare `kName` reference to `kName()`
via a negative lookahead so we don't double-rewrite our own output
Canonical table — tools/migrate_colours.py:
* 13 close-mappings absorbed from the comp/gate/curve sweep
(e.g. #b0c4d6 → text.secondary, ΔRGB≈16; #e85a5a → accent.danger,
ΔRGB≈23). Each is annotated with its sibling canonical value and
the deviation so the rationale is auditable.
Files touched (9):
ClientCompKnob, ClientCompMeter, ClientCompCurveWidget,
ClientGateCurveWidget, ClientDeEssCurveWidget, ClientTubeCurveWidget,
ClientCompLimiterButton, ClientCompThresholdFader, ClientGateLevelView
Skip-count (7 left as static const for PR 5/5):
* #3a1810 (comp meter no-go zone dark red — unique tint)
* 6 gradient-stop literals inside ThresholdFader/GateCurveWidget paint
code that need a dedicated meter-gradient token set in PR 5/5
Build verified clean across all 308 targets (main app + every test).
73, Jeremy KK7GWY & Claude (AI dev partner)
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
…mp/gate/curve widgets (aethersdr#3116) ## Summary **PR 4 of 5** in the Phase 2/3 wrap-up. Migrates 76 file-scope const \`QColor\` declarations across 9 compressor / gate / curve widget files so the entire compressor + gate + tube panel responds to theme switching. ## The pattern File-scope \`const QColor\` declarations construct their value at static-init time, which is too early for the ThemeManager singleton. The migration converts them to inline functions that defer the lookup to first paint: \`\`\`cpp // Before const QColor kRingBg("#1a2a3a"); // ... QPen pen(kRingBg, 2); // After inline QColor kRingBg() { return AetherSDR::ThemeManager::instance().color("color.background.1"); } // ... QPen pen(kRingBg(), 2); \`\`\` Call sites are rewritten too — \`kName\` → \`kName()\` — via a negative lookahead in the tool so we don't double-rewrite the declaration line itself. ## Tool changes — \`tools/migrate_paint_colours.py\` - New \`FILE_SCOPE_HEX_RE\` and \`FILE_SCOPE_RGB_RE\` patterns matching both \`QColor("#aabbcc")\` and \`QColor(0xAA, 0xBB, 0xCC[, alpha])\` declaration forms - New \`rewrite_file_scope_consts()\` pass that runs first, before the in-paint-code pass, so the same hex literal isn't matched twice - Preserves the trailing comment if any (\`const QColor kCurveColor ("#c8a040"); // amber\`) ## Canonical table — \`tools/migrate_colours.py\` 13 close-mappings absorbed from the comp/gate/curve sweep. Each is annotated with its sibling canonical value and the ΔRGB deviation so the rationale is auditable later: \`\`\`python "#b0c4d6": "color.text.secondary", # knob/meter labels, ΔRGB≈16 from #a0b4c8 "#e85a5a": "color.accent.danger", # meter level-hi red, ΔRGB≈23 from #ff4d4d "#c8a040": "color.accent.warning", # gate curve amber # ... 10 more \`\`\` ## Files touched (9) - ClientCompKnob (5 colors) - ClientCompMeter (15) - ClientCompCurveWidget (8) - ClientGateCurveWidget (8) - ClientDeEssCurveWidget (8) - ClientTubeCurveWidget (7) - ClientCompLimiterButton (9) - ClientCompThresholdFader (7) - ClientGateLevelView (9) ## Skip-count (7 left for PR 5/5) - \`#3a1810\` — meter no-go zone dark red, unique tint - 6 gradient-stop literals inside \`ThresholdFader\` / \`GateCurveWidget\` paint code that warrant a dedicated meter-gradient token set in PR 5/5 ## Verified locally - [x] Linux build clean across **all 308 targets** (main app + every test) - [x] No regressions flagged by the compiler ## Why this is incremental Compressor, gate, de-esser, tube, and limiter widgets now participate in theme switching. The remaining file-scope-const pattern in RxChain / KeyboardMap / StripChain / StripWaveform / WaveformWidget falls under PR 5/5 along with the waterfall colormap + slice indicators + spectrum-domain canonical-table extension. ## Test plan - [ ] CI: build / check-paths / check-windows / CodeQL - [ ] Visual smoke: open the app, open the COMP applet, confirm comp/gate/de-ess curves + threshold fader + limiter button + meters render identically (the 76 migrated literals resolve to the same — or to-within-ΔRGB-of-50 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 4 of 5 in the Phase 2/3 wrap-up. Migrates 76 file-scope const `QColor` declarations across 9 compressor / gate / curve widget files so the entire compressor + gate + tube panel responds to theme switching.
The pattern
File-scope `const QColor` declarations construct their value at static-init time, which is too early for the ThemeManager singleton. The migration converts them to inline functions that defer the lookup to first paint:
```cpp
// Before
const QColor kRingBg("#1a2a3a");
// ...
QPen pen(kRingBg, 2);
// After
inline QColor kRingBg() { return AetherSDR::ThemeManager::instance().color("color.background.1"); }
// ...
QPen pen(kRingBg(), 2);
```
Call sites are rewritten too — `kName` → `kName()` — via a negative lookahead in the tool so we don't double-rewrite the declaration line itself.
Tool changes — `tools/migrate_paint_colours.py`
Canonical table — `tools/migrate_colours.py`
13 close-mappings absorbed from the comp/gate/curve sweep. Each is annotated with its sibling canonical value and the ΔRGB deviation so the rationale is auditable later:
```python
"#b0c4d6": "color.text.secondary", # knob/meter labels, ΔRGB≈16 from #a0b4c8
"#e85a5a": "color.accent.danger", # meter level-hi red, ΔRGB≈23 from #ff4d4d
"#c8a040": "color.accent.warning", # gate curve amber
... 10 more
```
Files touched (9)
Skip-count (7 left for PR 5/5)
Verified locally
Why this is incremental
Compressor, gate, de-esser, tube, and limiter widgets now participate in theme switching. The remaining file-scope-const pattern in RxChain / KeyboardMap / StripChain / StripWaveform / WaveformWidget falls under PR 5/5 along with the waterfall colormap + slice indicators + spectrum-domain canonical-table extension.
Test plan
73, Jeremy KK7GWY & Claude (AI dev partner)
🤖 Generated with Claude Code