Skip to content

feat(theme): Phase 3 PR 4/5 — file-scope const QColor migration in comp/gate/curve widgets#3116

Merged
ten9876 merged 1 commit into
mainfrom
auto/theme-phase3-pr4
May 25, 2026
Merged

feat(theme): Phase 3 PR 4/5 — file-scope const QColor migration in comp/gate/curve widgets#3116
ten9876 merged 1 commit into
mainfrom
auto/theme-phase3-pr4

Conversation

@ten9876

@ten9876 ten9876 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

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

  • Linux build clean across all 308 targets (main app + every test)
  • 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

…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>
@ten9876 ten9876 requested a review from a team as a code owner May 25, 2026 05:52
@ten9876 ten9876 merged commit 7c4574a into main May 25, 2026
4 checks passed
@ten9876 ten9876 deleted the auto/theme-phase3-pr4 branch May 25, 2026 06:09
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>
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.

1 participant