Phase 5: move global-settings persistence into the controller

MakeGlobalSettings (the central persist builder, ~50 fields) lived in MainForm
and mixed radio state with UI fields, blocking headless persistence. Replace it
with TRadioController.BuildGlobalSettings (radio fields from live state) +
SaveGlobalSettings (self-gated on FDevConnected). The 7 scattered
SaveGlobal(FDevMAC, MakeGlobalSettings) call-sites collapse to FController.
SaveGlobalSettings.

Non-radio fields (theme/FPS/MHz-digits/CAT) are NOT controller state — they ride
through FLoadedGlobal (the loaded persist record the controller already holds for
round-trip). The GUI mirrors them into FLoadedGlobal at their change-points
(SetLightTheme/ApplyFPS/ApplyFreqMhzDigits/OnCATSettingsChange + connect for the
app-global theme). Controller never reads or acts on them.

Behavior-preserving (same blob, same save points); makes the daemon able to
persist radio settings without the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-08 21:00:57 +03:00
co-authored by Claude Opus 4.8
parent e1ecf947b8
commit 2c356beb77
2 changed files with 101 additions and 87 deletions
+23 -87
View File
@@ -367,7 +367,6 @@ type
procedure SaveAllAndExit;
procedure RestoreWindowBounds;
procedure SaveWindowBounds;
function MakeGlobalSettings: TGlobalSettings;
procedure FreqDispBChanged(Sender: TObject; NewFreq: Int64);
procedure BtnBandClick(Sender: TObject);
procedure BtnModeClick(Sender: TObject);
@@ -709,84 +708,6 @@ begin
FController.FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI, Maximized);
end;
function TMainForm.MakeGlobalSettings: TGlobalSettings;
var i: Integer;
begin
Result.Volume := FController.FVolume;
Result.DriveLevel := FController.FDrivePercent;
Result.PAMaxPower := FController.FPAMaxPower;
for i := 0 to BAND_COUNT - 1 do
Result.PABandCal[i] := FController.FPABandCal[i];
for i := 0 to CFG_XVTR_COUNT - 1 do
Result.VHFBandCal[i] := FController.FVHFBandCal[i];
Result.ActiveVfo := FController.FActiveVfo;
Result.NRMode := FController.FNRMode;
Result.NBMode := FController.FNBMode;
Result.SNBEnabled := FController.FSNB;
Result.ANFEnabled := FController.FANF;
Result.AGCSlope := 0;
Result.AGCHangThreshold := 100;
Result.WfAGCEnabled := FController.FWfAGCEnabled;
Result.WfNFEnabled := FController.FWfNFEnabled;
Result.DitherEnabled := FController.FDitherEnabled;
Result.RandomEnabled := FController.FRandomEnabled;
Result.LastBand := FController.FCurrentBand;
Result.LastXvtr := FController.FCurrentXvtr;
Result.SampleRate := FController.FSampleRate;
// Display settings
if FController.FWDSPReady then
begin
Result.FFTSize := FController.FDSPEngine.FFTSize;
Result.WindowType := FController.FDSPEngine.WindowType;
Result.SpecDetector := FController.FDSPEngine.SpecDetector;
Result.SpecAvgMode := FController.FDSPEngine.SpecAvgMode;
Result.SpecAvgTimeMS := FController.FDSPEngine.SpecAvgTimeMS;
Result.WfDetector := FController.FDSPEngine.WfDetector;
Result.WfAvgMode := FController.FDSPEngine.WfAvgMode;
Result.WfAvgTimeMS := FController.FDSPEngine.WfAvgTimeMS;
end
else
begin
Result.FFTSize := 131072;
Result.WindowType := 2;
Result.SpecDetector := 0;
Result.SpecAvgMode := 3;
Result.SpecAvgTimeMS := 30.0;
Result.WfDetector := 0;
Result.WfAvgMode := 3;
Result.WfAvgTimeMS := 120.0;
end;
Result.WfManualHigh := FController.FWfManualHigh;
Result.WfManualLow := FController.FWfManualLow;
Result.WfAGCOffset := FController.FWfAGCOffset;
Result.SpecRefLevel := FController.FSpecRefLevel;
Result.SpecRange := FController.FSpecRange;
Result.SpecGridStep := FController.FSpecGridStep;
Result.AudioSampleRate := FController.FAudioOut.SampleRate;
Result.ShowSpectrum := FController.FShowSpectrum;
Result.ShowWaterfall := FController.FShowWaterfall;
Result.ShowWideband := FController.FShowWideband;
Result.WidebandFill := FController.FWidebandFill;
Result.SpectrumFill := FController.FSpectrumFill;
Result.DisplayDuplex := FController.FDisplayDuplex;
Result.DisplayFPS := FDisplayFPS;
Result.LightTheme := FLightTheme;
Result.FreqMhzDigits := FFreqMhzDigits;
// Audio device names
Result.AudioOutDevice := FController.FAudioOutDevName;
Result.AudioInDevice := FController.FAudioInDevName;
Result.SendAudioToRadio := FController.FSendAudioToRadio;
// CAT settings — carried from the last loaded/saved device config
Result.CATSerialEnabled := FCATLastGlobal.CATSerialEnabled;
Result.CATSerialPort := FCATLastGlobal.CATSerialPort;
Result.CATSerialBaud := FCATLastGlobal.CATSerialBaud;
Result.CATSerialDataBits := FCATLastGlobal.CATSerialDataBits;
Result.CATSerialStopBits := FCATLastGlobal.CATSerialStopBits;
Result.CATSerialParity := FCATLastGlobal.CATSerialParity;
Result.CATTcpEnabled := FCATLastGlobal.CATTcpEnabled;
Result.CATTcpPort := FCATLastGlobal.CATTcpPort;
end;
procedure TMainForm.SaveCurrentBand;
begin
// Логика сохранения (band-cache/XVTR-слот + конфиг) — в контроллере.
@@ -816,7 +737,7 @@ begin
if FController.FDevConnected then
begin
SaveCurrentBand;
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
if FController.FDevConnected then
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
// Запомнить LastFreq A/B XVTR для следующего запуска
@@ -1167,7 +1088,7 @@ begin
if FController.FDevConnected then
begin
SaveCurrentBand;
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
if FController.FDevConnected then
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
end;
@@ -1436,6 +1357,9 @@ begin
// Полная синхронизация device-виджетов после ConnectDevice (load-by-MAC).
// Состояние уже в контроллере; здесь только рендер + UI/CAT/web-побочки.
FCATLastGlobal := FController.FLoadedGlobal;
// Тема — app-global (своё хранилище LoadTheme/SaveTheme), не из device-блоба:
// возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему.
FController.FLoadedGlobal.LightTheme := FLightTheme;
CATApplySettings(FController.FLoadedGlobal);
RebuildXvtrButtons;
PushXvtrToWeb;
@@ -2743,6 +2667,7 @@ procedure TMainForm.SetLightTheme(V: Boolean);
begin
if FLightTheme = V then Exit;
FLightTheme := V;
FController.FLoadedGlobal.LightTheme := V; // зеркало в persist-буфер контроллера
ApplyDarkTheme;
FController.FSettings.SaveTheme(V);
FController.FSettings.Save;
@@ -3492,7 +3417,7 @@ begin
if FController.FDevConnected then
begin
SaveCurrentBand;
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
FController.FSettings.SaveAlex(FController.FDevMAC, FController.FAlexSettings);
// XVTR LastFreq → актуальная видимая частота в момент остановки
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then
@@ -4582,7 +4507,7 @@ begin
if FController.FNetwork.Connected and FController.FRunning then
FController.FNetwork.ConfigureDDCs(1, FController.FSampleRate div 1000, 0, FController.FDitherEnabled, FController.FRandomEnabled);
if FController.FDevConnected then
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
end;
procedure TMainForm.ApplyWebSettings(Enabled: Boolean; Port: Integer;
@@ -4737,7 +4662,7 @@ begin
FController.SetSampleRate(SampleRate);
// SampleRate глобальный — сохраняем в global settings.
if FController.FDevConnected then
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
end;
// ===========================================================================
@@ -5577,7 +5502,7 @@ begin
// выкл = замьютить динамик (byte 1400 bit1) и не слать поток.
FController.FNetwork.SetSpeakerAudio(Enabled);
if FController.FDevConnected then
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
end;
procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall,
@@ -5601,6 +5526,7 @@ begin
if FPS < 1 then FPS := 1;
if FPS > 100 then FPS := 100;
FDisplayFPS := FPS;
FController.FLoadedGlobal.DisplayFPS := FPS; // зеркало в persist-буфер контроллера
FSpectrumTimer.Interval := 1000 div FPS;
// CPU path keeps the historical every-other-frame waterfall update.
// OpenGL uploads only one texture row per frame, so it can match display FPS.
@@ -5621,6 +5547,7 @@ const
begin
Digits := EnsureRange(Digits, 3, 5);
FFreqMhzDigits := Digits;
FController.FLoadedGlobal.FreqMhzDigits := Digits; // зеркало в persist-буфер контроллера
FreqDispA.MinMhzDigits := Digits;
FreqDispB.MinMhzDigits := Digits;
FreqDispA.MaxFreq := MaxFreqs[Digits];
@@ -5828,10 +5755,19 @@ begin
end;
FCATLastGlobal.CATTcpEnabled := TcpEnabled;
FCATLastGlobal.CATTcpPort := TcpPort;
// Зеркалим CAT-поля в persist-буфер контроллера (BuildGlobalSettings берёт их
// сквозь из FLoadedGlobal; на connect они грузятся в FLoadedGlobal → FCATLastGlobal).
FController.FLoadedGlobal.CATSerialEnabled := FCATLastGlobal.CATSerialEnabled;
FController.FLoadedGlobal.CATSerialPort := FCATLastGlobal.CATSerialPort;
FController.FLoadedGlobal.CATSerialBaud := FCATLastGlobal.CATSerialBaud;
FController.FLoadedGlobal.CATSerialDataBits := FCATLastGlobal.CATSerialDataBits;
FController.FLoadedGlobal.CATSerialStopBits := FCATLastGlobal.CATSerialStopBits;
FController.FLoadedGlobal.CATSerialParity := FCATLastGlobal.CATSerialParity;
FController.FLoadedGlobal.CATTcpEnabled := FCATLastGlobal.CATTcpEnabled;
FController.FLoadedGlobal.CATTcpPort := FCATLastGlobal.CATTcpPort;
CATApplySettings(FCATLastGlobal);
FController.FSettings.SaveCATSettings(FCATLastGlobal);
if FController.FDevConnected then
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
FController.SaveGlobalSettings;
FController.FSettings.Save;
end;
+78
View File
@@ -314,6 +314,13 @@ type
function MakeBandSettings: TBandSettings;
procedure SaveCurrentBand;
procedure RestoreBand(BandIdx: Integer);
// Глобальные настройки устройства (persist по MAC). BuildGlobalSettings
// собирает радио-поля из своего состояния, а НЕ-радио поля (тема/FPS/digits/
// CAT) переносит сквозь из FLoadedGlobal — контроллер их не трактует, только
// round-trip в файл. SaveGlobalSettings пишет блоб (гейт по FDevConnected).
function BuildGlobalSettings: TGlobalSettings;
procedure SaveGlobalSettings;
// Применяет DSP-настройки диапазона (mode/filter/AGC/CTUN/FM) + шлёт события
// рендера. НЕ трогает частоты VFO/центр — это переиспользуется как RestoreBand
// (с загрузкой VFO), так и SetActiveVfo (bandstack: смена бэнда без сброса VFO).
@@ -994,6 +1001,77 @@ begin
FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]);
end;
function TRadioController.BuildGlobalSettings: TGlobalSettings;
// Радио-поля — из живого состояния; тема/FPS/digits/CAT — сквозь из FLoadedGlobal
// (контроллер их не трактует). Сидируем загруженным блобом, поверх — радио.
var i: Integer;
begin
Result := FLoadedGlobal; // несёт не-радио поля (тема/FPS/digits/CAT) round-trip
Result.Volume := FVolume;
Result.DriveLevel := FDrivePercent;
Result.PAMaxPower := FPAMaxPower;
for i := 0 to CFG_BAND_COUNT - 1 do Result.PABandCal[i] := FPABandCal[i];
for i := 0 to CFG_XVTR_COUNT - 1 do Result.VHFBandCal[i] := FVHFBandCal[i];
Result.ActiveVfo := FActiveVfo;
Result.NRMode := FNRMode;
Result.NBMode := FNBMode;
Result.SNBEnabled := FSNB;
Result.ANFEnabled := FANF;
Result.AGCSlope := 0;
Result.AGCHangThreshold := 100;
Result.WfAGCEnabled := FWfAGCEnabled;
Result.WfNFEnabled := FWfNFEnabled;
Result.DitherEnabled := FDitherEnabled;
Result.RandomEnabled := FRandomEnabled;
Result.LastBand := FCurrentBand;
Result.LastXvtr := FCurrentXvtr;
Result.SampleRate := FSampleRate;
if FWDSPReady then
begin
Result.FFTSize := FDSPEngine.FFTSize;
Result.WindowType := FDSPEngine.WindowType;
Result.SpecDetector := FDSPEngine.SpecDetector;
Result.SpecAvgMode := FDSPEngine.SpecAvgMode;
Result.SpecAvgTimeMS := FDSPEngine.SpecAvgTimeMS;
Result.WfDetector := FDSPEngine.WfDetector;
Result.WfAvgMode := FDSPEngine.WfAvgMode;
Result.WfAvgTimeMS := FDSPEngine.WfAvgTimeMS;
end
else
begin
Result.FFTSize := 131072;
Result.WindowType := 2;
Result.SpecDetector := 0;
Result.SpecAvgMode := 3;
Result.SpecAvgTimeMS := 30.0;
Result.WfDetector := 0;
Result.WfAvgMode := 3;
Result.WfAvgTimeMS := 120.0;
end;
Result.WfManualHigh := FWfManualHigh;
Result.WfManualLow := FWfManualLow;
Result.WfAGCOffset := FWfAGCOffset;
Result.SpecRefLevel := FSpecRefLevel;
Result.SpecRange := FSpecRange;
Result.SpecGridStep := FSpecGridStep;
Result.AudioSampleRate := FAudioOut.SampleRate;
Result.ShowSpectrum := FShowSpectrum;
Result.ShowWaterfall := FShowWaterfall;
Result.ShowWideband := FShowWideband;
Result.WidebandFill := FWidebandFill;
Result.SpectrumFill := FSpectrumFill;
Result.DisplayDuplex := FDisplayDuplex;
Result.AudioOutDevice := FAudioOutDevName;
Result.AudioInDevice := FAudioInDevName;
Result.SendAudioToRadio := FSendAudioToRadio;
end;
procedure TRadioController.SaveGlobalSettings;
begin
if FDevConnected then FSettings.SaveGlobal(FDevMAC, BuildGlobalSettings);
end;
procedure TRadioController.ApplyBandDSP(const B: TBandSettings);
// DSP-настройки диапазона + события рендера. Частоты VFO/центр НЕ трогает.
begin