Summary
src/gui/MainWindow.cpp has grown to 15,369 lines / 685 KB in a single
translation unit. It is the project's central signal-routing hub, but its size
now imposes real friction:
- Every change funnels through one file that requires CODEOWNERS review,
so unrelated work (e.g. external-controller / MIDI customization) competes for
the same approval surface and the same merge conflicts.
- Large blast radius per edit — a 685 KB TU means each change recompiles the
whole file, and reviewers must reason about a 15k-line file to judge a 20-line
change. This raises the risk of code-quality regressions slipping through.
This issue proposes a phased, low-risk refactor to reduce the code sitting in
MainWindow.cpp and to decouple self-contained subsystems (especially external
controllers) from the monolith. No user-visible behavior change is intended.
Current anatomy (where the mass is)
178 #includes · 190 member variables · 433 method declarations · 604
connect() calls.
| Lines |
Location |
What it is |
| ~3,400 |
constructor (MainWindow.cpp:1221–4637) |
237 connect() calls, 184 lambdas, ~30 ── section ── wiring blocks |
| 971 |
buildMenuBar (:6531) |
menu construction + 73 connects |
| 904 |
wirePanadapter (:10299) |
per-pan signal wiring |
| 846 |
buildUI (:7502) |
widget construction |
| 653 |
registerShortcutActions (:12271) |
keyboard shortcut table |
| 441 |
registerMidiParams (:14428) |
MIDI controller bindings |
| 320 |
wireVfoWidget (:11375) |
per-VFO wiring |
| 227 |
buildControlDevicesSnapshot (:5569) |
controller snapshot |
The constructor alone is ~22% of the file.
Proposed phases (ranked by impact ÷ risk)
Phase 0 — Extract static free-function helpers (quick win, zero risk)
Lines ~470–734 already hold ~15 file-scope static helpers
(buildNetworkTooltip, buildTnfTooltip, formatTnfFrequency,
memorySpotLabel, buildBandStackIndicatorPixmap, etc.) that have no
MainWindow dependency — pure formatters. Move them to
MainWindowHelpers.{h,cpp} (or fold into existing util headers). ~250 lines out,
no class involvement. Doubles as a warm-up that proves the multi-file CMake
pattern.
Phase 1 — Split the class across multiple .cpp files (highest impact, near-zero risk)
A single C++ class can be defined across several translation units with no
header change, no API change, and no behavior change — just move method bodies
into new files added to CMakeLists.txt. The codebase already has the precedent
of wireXxx() helpers (wirePanadapter, wireVfoWidget, wireAetherDspWidget,
wireRadioSetupDialogSignals).
Proposed files, mapped onto the existing ── section ── blocks:
MainWindow_Controllers.cpp (do this first — solves the acute pain) —
registerMidiParams (441), buildControlDevicesSnapshot (227), the
external-controllers constructor block (~:3424), plus HID/shortcut glue.
Gives controller customization its own file → smaller diffs, isolated review
surface, less collateral risk in unrelated UI code.
MainWindow_Wiring.cpp — turn the constructor's ~30 wiring blocks into
void MainWindow::wireXxx() helpers; the constructor shrinks to
wireDiscovery(); wireSmartLink(); wireSpots(); ….
MainWindow_Menu.cpp — buildMenuBar (971) + registerShortcutActions (653).
MainWindow_Swr.cpp — the 14 SWR-sweep methods (~700 lines, :13165–:13765).
MainWindow_FreeDV.cpp — RADE/FDV/FreeDV cluster (:13766–:14223).
MainWindow_SHistory.cpp — the SHistory-marker cluster (9 methods).
This is purely mechanical (cut/paste bodies, no logic edits) and trivially
reviewable. It does not reduce total LOC but directly attacks the "everything
needs one approval" bottleneck and speeds incremental builds.
Phase 2 — Promote self-contained subsystems to controller classes (medium effort, real LOC reduction)
The SWR sweep and FreeDV clusters are independent state machines living in
MainWindow only by history. Extracting a SwrSweepController /
FreeDvSessionController that own their own timers and state would pull ~1,000+
lines and their member variables out of the 190-member header. Do this after
Phase 1 proves out, per subsystem.
Phase 3 — Table-drive menu/shortcut builders (optional, later)
buildMenuBar and registerShortcutActions are largely repetitive
addAction/lambda boilerplate. A data table ({id, label, slot}) iterated in a
loop could cut these substantially — but this is genuine logic change with real
regression risk. Lowest priority; only if the boilerplate keeps growing.
Suggested execution order
- Phase 0 (warm-up, proves CMake multi-file pattern)
- Phase 1 starting with
MainWindow_Controllers.cpp (acute pain)
- Remainder of Phase 1 wiring split
- Phase 2 opportunistically, per subsystem
Constraints / guardrails
- No behavior change in Phases 0–1 — these are mechanical moves only.
- Preserve the GUI↔Radio no-feedback-loop wiring exactly (signal-blocker
guards, m_updatingFromModel).
- Don't break slice 0 RX flow or the multi-pan / multi-client filtering.
- One PR per phase/file to keep diffs reviewable.
Acceptance criteria
Summary
src/gui/MainWindow.cpphas grown to 15,369 lines / 685 KB in a singletranslation unit. It is the project's central signal-routing hub, but its size
now imposes real friction:
so unrelated work (e.g. external-controller / MIDI customization) competes for
the same approval surface and the same merge conflicts.
whole file, and reviewers must reason about a 15k-line file to judge a 20-line
change. This raises the risk of code-quality regressions slipping through.
This issue proposes a phased, low-risk refactor to reduce the code sitting in
MainWindow.cppand to decouple self-contained subsystems (especially externalcontrollers) from the monolith. No user-visible behavior change is intended.
Current anatomy (where the mass is)
178
#includes · 190 member variables · 433 method declarations · 604connect()calls.MainWindow.cpp:1221–4637)connect()calls, 184 lambdas, ~30── section ──wiring blocksbuildMenuBar(:6531)wirePanadapter(:10299)buildUI(:7502)registerShortcutActions(:12271)registerMidiParams(:14428)wireVfoWidget(:11375)buildControlDevicesSnapshot(:5569)The constructor alone is ~22% of the file.
Proposed phases (ranked by impact ÷ risk)
Phase 0 — Extract static free-function helpers (quick win, zero risk)
Lines ~470–734 already hold ~15 file-scope
statichelpers(
buildNetworkTooltip,buildTnfTooltip,formatTnfFrequency,memorySpotLabel,buildBandStackIndicatorPixmap, etc.) that have noMainWindowdependency — pure formatters. Move them toMainWindowHelpers.{h,cpp}(or fold into existing util headers). ~250 lines out,no class involvement. Doubles as a warm-up that proves the multi-file CMake
pattern.
Phase 1 — Split the class across multiple .cpp files (highest impact, near-zero risk)
A single C++ class can be defined across several translation units with no
header change, no API change, and no behavior change — just move method bodies
into new files added to
CMakeLists.txt. The codebase already has the precedentof
wireXxx()helpers (wirePanadapter,wireVfoWidget,wireAetherDspWidget,wireRadioSetupDialogSignals).Proposed files, mapped onto the existing
── section ──blocks:MainWindow_Controllers.cpp(do this first — solves the acute pain) —registerMidiParams(441),buildControlDevicesSnapshot(227), theexternal-controllers constructor block (~
:3424), plus HID/shortcut glue.Gives controller customization its own file → smaller diffs, isolated review
surface, less collateral risk in unrelated UI code.
MainWindow_Wiring.cpp— turn the constructor's ~30 wiring blocks intovoid MainWindow::wireXxx()helpers; the constructor shrinks towireDiscovery(); wireSmartLink(); wireSpots(); ….MainWindow_Menu.cpp—buildMenuBar(971) +registerShortcutActions(653).MainWindow_Swr.cpp— the 14 SWR-sweep methods (~700 lines,:13165–:13765).MainWindow_FreeDV.cpp— RADE/FDV/FreeDV cluster (:13766–:14223).MainWindow_SHistory.cpp— the SHistory-marker cluster (9 methods).This is purely mechanical (cut/paste bodies, no logic edits) and trivially
reviewable. It does not reduce total LOC but directly attacks the "everything
needs one approval" bottleneck and speeds incremental builds.
Phase 2 — Promote self-contained subsystems to controller classes (medium effort, real LOC reduction)
The SWR sweep and FreeDV clusters are independent state machines living in
MainWindowonly by history. Extracting aSwrSweepController/FreeDvSessionControllerthat own their own timers and state would pull ~1,000+lines and their member variables out of the 190-member header. Do this after
Phase 1 proves out, per subsystem.
Phase 3 — Table-drive menu/shortcut builders (optional, later)
buildMenuBarandregisterShortcutActionsare largely repetitiveaddAction/lambda boilerplate. A data table ({id, label, slot}) iterated in aloop could cut these substantially — but this is genuine logic change with real
regression risk. Lowest priority; only if the boilerplate keeps growing.
Suggested execution order
MainWindow_Controllers.cpp(acute pain)Constraints / guardrails
guards,
m_updatingFromModel).Acceptance criteria
MainWindow.cppreduced from 15.4k lines toward a target (e.g. < 6k forthe core constructor + lifecycle TU).
isolated review surface.
wireXxx()calls.