Skip to content

feat(theme): Phase 3 PR 3/5 — paint-color migration tool + SpectrumWidget#3113

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

feat(theme): Phase 3 PR 3/5 — paint-color migration tool + SpectrumWidget#3113
ten9876 merged 1 commit into
mainfrom
auto/theme-phase3-meters-spectrum

Conversation

@ten9876

@ten9876 ten9876 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

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

  • 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

  • Linux build clean (68 link steps, no errors)
  • No regressions flagged by compiler
  • 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

…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>
@ten9876 ten9876 requested a review from a team as a code owner May 25, 2026 05:19
@ten9876 ten9876 merged commit 353d6cc into main May 25, 2026
4 checks passed
@ten9876 ten9876 deleted the auto/theme-phase3-meters-spectrum branch May 25, 2026 05:40
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>
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