Phase 3 (batch 16): move XVTR activate/deactivate into the controller

ActivateXvtrBand/DeactivateXvtr followed the Band pattern: the state +
DSP + network now live on TRadioController; the UI renders via events.

- ApplyXvtrToNetwork moved to the controller (XVTR enable bit / DisablePA
  / RX antenna). MainForm.ApplyXvtrToNetwork is a thin delegate.
- ActivateXvtr builds a TBandSettings from the XVTR slot and runs it
  through the shared ApplyBandDSP (mode/filter/AGC/CTUN/FM), then sets
  VFO B (range-clamped) / VFO A / center / drive and pushes to the radio.
  Render is driven by Changed(rfXvtr) + Changed(rfActiveVfo). The slot has
  no stored filter bandwidth, so FilterBWFor(mode, idx) derives it from
  the per-mode table without changing the index.
- DeactivateXvtr saves the slot via SaveCurrentBand (its XVTR branch
  already writes the whole slot) and exits; the HF return (RestoreBand +
  final active-VFO retune) stays in the UI wrapper.
- SetXvtrBand is now a real dispatcher (>=0 activate, <0 deactivate).
- rfBand render is XVTR-aware (clears HF band buttons while in XVTR);
  added an rfXvtr render case for the XVTR button highlight.
- The thin UI wrappers keep only wideband-view + web push. ~150 lines of
  inline logic removed from MainForm; all callers (web/channel/band-click/
  startup) are unchanged and go through the wrappers.

Also fixes a latent filter-restore bug surfaced by the XVTR round-trip
(transverter -> HF -> transverter): the rfMode render calls
UpdateFilterButtons, which resets the non-FM filter to the mode default.
That reset belongs to a mode change (the controller already applies it via
ApplyModeDefaults), not to a passive render, so it clobbered the filter
restored from the band/XVTR cache. rfMode now preserves FFilter/FFilterBW
around UpdateFilterButtons. This also fixes the same loss on plain HF band
switches for non-FM modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 13:49:31 +03:00
co-authored by Claude Opus 4.8
parent e09bc21bba
commit 831e5c3a31
2 changed files with 152 additions and 143 deletions
+32 -142
View File
@@ -1272,6 +1272,7 @@ end;
procedure TMainForm.OnControllerState(Sender: TObject; Field: TRadioField);
var
i: Integer;
SavedFilter, SavedFilterBW: Integer;
begin
FSyncingFromController := True;
try
@@ -1316,8 +1317,14 @@ begin
end;
end;
rfBand:
// В XVTR-режиме HF-кнопки гасим (активна XVTR-кнопка, см. rfXvtr).
for i := 0 to BAND_COUNT - 1 do
StyleButton(BtnBand[i], i = FController.FCurrentBand);
StyleButton(BtnBand[i],
(FController.FCurrentXvtr < 0) and (i = FController.FCurrentBand));
rfXvtr:
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
StyleButton(BtnXvtrBand[i], i = FController.FCurrentXvtr);
rfMute:
begin
StyleButton(BtnMute, FController.FMuted);
@@ -1332,7 +1339,18 @@ begin
rfMode:
begin
for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode);
UpdateFilterButtons; // подписи/дефолт фильтра + FM-панели под режим
// UpdateFilterButtons (для не-FM) мутирует FFilter → сбрасывает на дефолт
// режима. Это поведение нужно при СМЕНЕ режима (web/channel), но не при
// пассивном рендере: RestoreBand/ActivateXvtr уже задали фильтр из кэша.
// На реальной смене режима контроллер выставляет дефолт через
// ApplyModeDefaults ДО rfMode, так что сохраняется именно он — поведение
// не меняется. Последующий rfFilter стилит кнопки под восстановленный фильтр.
SavedFilter := FController.FFilter;
SavedFilterBW := FController.FFilterBW;
UpdateFilterButtons; // подписи + FM-панели под режим
FController.FFilter := SavedFilter;
FController.FFilterBW := SavedFilterBW;
for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FController.FFilter);
SyncSpecViewFreq; // иначе полоса фильтра рисуется под старый режим
end;
rfFilter:
@@ -6386,24 +6404,9 @@ end;
// XVTR DDC In (Alex bits 8+11) управляется через галочку XVTR в Antenna/Alex
// tab для IF-диапазона (Alex.RxOnly[IF_band]=3) — аналогично Thetis.
procedure TMainForm.ApplyXvtrToNetwork;
var
En: Boolean;
DisablePA: Boolean;
RxAnt: Byte;
begin
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then
begin
En := True;
DisablePA := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].DisablePA;
RxAnt := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXAntenna;
end
else
begin
En := False; DisablePA := False; RxAnt := 0;
end;
if FController.FNetwork.Connected then
FController.FNetwork.SetXvtrMode(En, DisablePA, RxAnt);
// Логика перенесена в контроллер; обёртка для существующих вызовов в UI.
FController.ApplyXvtrToNetwork;
end;
// Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые,
@@ -6517,139 +6520,26 @@ end;
// Активирует XVTR-band: устанавливает видимую частоту по LastFreq или
// середине [FreqBegin..FreqEnd], применяет network XVTR-режим.
procedure TMainForm.ActivateXvtrBand(Idx: Integer);
var
Vis: Double;
i: Integer;
E: TXvtrEntry;
begin
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
if not FController.FXvtrSettings.Entries[Idx].Enabled then Exit;
FController.FCurrentXvtr := Idx;
E := FController.FXvtrSettings.Entries[Idx];
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
Vis := E.LastFreq;
if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then
Vis := (E.FreqBegin + E.FreqEnd) / 2.0;
// Отключаем кнопки HF band
for i := 0 to BAND_COUNT - 1 do
StyleButton(BtnBand[i], False);
// Подсвечиваем активную XVTR-кнопку
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
StyleButton(BtnXvtrBand[i], i = Idx);
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
ApplyXvtrToNetwork;
// Восстанавливаем Mode + FM Squelch + CTCSS для этого XVTR-слота
FController.FFMSQOn := E.LastFMSQOn;
FController.FFMSQLevel := E.LastFMSQLevel;
FController.FMode := E.LastMode;
FController.FFilter := E.LastFilterIdx;
for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode);
if FController.FWDSPReady then FController.FDSPEngine.SetMode(FController.FMode);
UpdateFilterButtons; // показывает/скрывает FM-панели, обновляет SQL UI
FController.ApplyModeFilter;
FController.FFMCTCSSOn := E.LastCTCSSOn;
FController.FFMCTCSSAutoActive := E.LastCTCSSAutoActive;
if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn);
SetFMCTCSSTone(E.LastCTCSSToneIdx);
ApplyFMSquelch;
FController.FFMStepOn := E.LastFMStepOn;
SetFMStep(E.LastFMStepIdx);
if BtnFMStep <> nil then StyleButton(BtnFMStep, FController.FFMStepOn);
FController.FFMRptDir := E.LastFMRptDir;
FController.FFMRptAutoActive := E.LastFMRptAutoActive;
FController.FFMRptOffsetHz := E.LastFMRptOffsetHz;
if FController.FFMRptOffsetHz <= 0 then
FController.FFMRptOffsetHz := RptDefaultOffsetHz(E.FreqBegin);
if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS);
if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS);
if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz);
// AGC
FController.FAGCMode := E.LastAGCMode;
FController.FAGCTop := E.LastAGCTop;
for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode);
TrkAGC.Position := FController.FAGCTop;
LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]);
if FController.FWDSPReady then
begin
FController.FDSPEngine.SetAGCTop(FController.FAGCTop);
FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0);
end;
FSpecView.AGCTop := FController.FAGCTop;
// CTUN
FController.FCTun := E.LastCTun;
StyleButton(BtnCTun, FController.FCTun);
// VFO-B: своя сохранённая частота слота, клэмп в диапазон трансвертера.
FController.FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd);
FreqDispB.Frequency := Round(FController.FVfoB);
// VFO на видимую частоту; FController.FCenterFreq тоже visible
FController.FVfoA := Vis;
FController.FCenterFreq := Vis;
FreqDispA.Frequency := Round(FController.FVfoA);
if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0);
// Пересчитываем drive byte для нового XVTR слота и отправляем в сеть
FController.FDriveLevel := CalcDriveByte;
if FController.FNetwork.Connected and FController.FNetwork.Running then
begin
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FController.FDriveLevel, FController.FTransmitting, True, True);
FController.FNetwork.SendFullHP;
end;
SyncSpecViewFreq;
FSpecView.DrawSpectrum;
PbSpectrum.Invalidate;
FSpecView.DrawWaterfall;
PbWaterfall.Invalidate;
if PbRuler <> nil then PbRuler.Invalidate;
// Вход в XVTR-слот (state+DSP+VFO+сеть) — в контроллере; рендер кнопок/частот/
// спектра — через события (rfXvtr/rfBand/rfActiveVfo). Здесь только UI: wideband+web.
FController.ActivateXvtr(Idx);
UpdateWidebandFrequencyView;
if PbWideband <> nil then PbWideband.Invalidate;
if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate;
PushXvtrToWeb;
end;
// Деактивирует XVTR — возвращает к HF-режиму.
// FController.FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand,
// ApplyVfoA, SaveCurrentBand) — он указывает на HF-диапазон, с которого
// пользователь зашёл в трансвертер. RestoreBand вернёт VFO/Mode/Filter/AGC
// /CTUN/Span этого диапазона и обновит подсветку HF band-кнопки.
// Деактивирует XVTR — возвращает к HF-режиму. FController.FCurrentBand указывает
// на HF-диапазон, с которого пользователь зашёл в трансвертер. RestoreBand вернёт
// VFO/Mode/Filter/AGC/CTUN/Span этого диапазона и обновит подсветку HF band-кнопки.
procedure TMainForm.DeactivateXvtr;
var i: Integer;
begin
if FController.FCurrentXvtr < 0 then Exit;
// Сохраняем полное состояние XVTR-слота (раньше сохранялся только LastFreq)
if FController.FCurrentXvtr < CFG_XVTR_COUNT then
begin
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreqB := FController.FVfoB;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQLevel := FController.FFMSQLevel;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSOn := FController.FFMCTCSSOn;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSToneIdx := FController.FFMCTCSSToneIdx;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepOn := FController.FFMStepOn;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepIdx := FController.FFMStepIdx;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptDir := FController.FFMRptDir;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptOffsetHz := FController.FFMRptOffsetHz;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTun := FController.FCTun;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCMode := FController.FAGCMode;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCTop := FController.FAGCTop;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSAutoActive := FController.FFMCTCSSAutoActive;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptAutoActive := FController.FFMRptAutoActive;
if FController.FDevConnected then
begin
FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings);
FController.FSettings.Save;
end;
end;
FController.FCurrentXvtr := -1;
// Сбрасываем подсветку XVTR-кнопок
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
StyleButton(BtnXvtrBand[i], False);
ApplyXvtrToNetwork;
// Восстанавливаем HF band: VFO/Mode/Filter/AGC/CTUN/Span из кэша.
// RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние.
// Сохранение слота + выход (+ гашение XVTR-кнопок через rfXvtr) — в контроллере.
FController.DeactivateXvtr;
// HF-возврат: состояние/DSP из кэша + финальная перестройка активного VFO
// (через UI-путь RestoreBand: ResetWfAvgBuf + controller.RestoreBand + ApplyVfoA/SetVfoB).
if (FController.FCurrentBand >= 0) and (FController.FCurrentBand < CFG_BAND_COUNT) then
RestoreBand(FController.FCurrentBand);
UpdateWidebandFrequencyView;