Phase 3 (batch 9): route FM repeater shift through controller commands

FM Rpt is the first FM-Rpt heavy-class item (XvtrTranslate/ActiveTXFreqHz
blocker already lifted in d895800). Following the established pattern:

- TRadioController.SetFMRpt(Dir): explicit direction, clears auto-active,
  pushes network state; rendered via OnControllerState(rfFMRpt).
- SetFMRptOffset(Hz): updates offset, re-pushes when RPT engaged.
- UpdateRptAutoState: 2 m auto-MINUS logic moved into controller, fires
  Changed(rfFMRpt) instead of inline StyleButton.
- Extracted private PushNetworkState helper (full HP frame); SetCTun now
  reuses it (dedupes the UpdateState+SendFullHP block).

MainForm: Btn/EdFMRpt handlers are thin wrappers (toggle decision +
SaveCurrentBand stay in UI); UpdateRptAutoState delegates to controller;
ApplyFMRpt removed. OnControllerState(rfFMRpt) restyles both buttons and
sets the offset edit text only when unfocused (avoids disrupting typing).
Direct FFMRptDir writes in band/channel/xvtr restore paths left as-is
(migrate with their own commands later). Builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-04 19:56:56 +03:00
co-authored by Claude Opus 4.8
parent f12d2a69ad
commit 967e7cb7b1
2 changed files with 83 additions and 71 deletions
+28 -63
View File
@@ -433,7 +433,6 @@ type
procedure BtnFMRptMinusClick(Sender: TObject); procedure BtnFMRptMinusClick(Sender: TObject);
procedure BtnFMRptPlusClick(Sender: TObject); procedure BtnFMRptPlusClick(Sender: TObject);
procedure EdFMRptOffsetChange(Sender: TObject); procedure EdFMRptOffsetChange(Sender: TObject);
procedure ApplyFMRpt;
procedure UpdateRptAutoState; procedure UpdateRptAutoState;
procedure SetFMStep(Idx: Integer); procedure SetFMStep(Idx: Integer);
procedure CloseFMStepPopup; procedure CloseFMStepPopup;
@@ -1468,6 +1467,22 @@ begin
end; end;
rfFMCTCSSTone: rfFMCTCSSTone:
if FCTCSSDropDown <> nil then FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx); if FCTCSSDropDown <> nil then FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx);
rfFMRpt:
begin
if BtnFMRptMinus <> nil then
begin
StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS);
BtnFMRptMinus.Repaint;
end;
if BtnFMRptPlus <> nil then
begin
StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS);
BtnFMRptPlus.Repaint;
end;
// Текст оффсета не трогаем, пока пользователь печатает в поле.
if (EdFMRptOffset <> nil) and not EdFMRptOffset.Focused then
EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz);
end;
end; end;
finally finally
FSyncingFromController := False; FSyncingFromController := False;
@@ -5241,91 +5256,41 @@ end;
procedure TMainForm.BtnFMRptMinusClick(Sender: TObject); procedure TMainForm.BtnFMRptMinusClick(Sender: TObject);
begin begin
if FSyncingFromController then Exit;
// Toggle решает UI; команда несёт сетевой сдвиг + рендер кнопок (OnControllerState).
if FController.FFMRptDir = RPT_MINUS then if FController.FFMRptDir = RPT_MINUS then
FController.FFMRptDir := RPT_NONE FController.SetFMRpt(RPT_NONE)
else else
FController.FFMRptDir := RPT_MINUS; FController.SetFMRpt(RPT_MINUS);
FController.FFMRptAutoActive := False; // ручное действие снимает авто-флаг
StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS);
StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS);
BtnFMRptMinus.Repaint;
BtnFMRptPlus.Repaint;
ApplyFMRpt;
SaveCurrentBand; SaveCurrentBand;
end; end;
procedure TMainForm.BtnFMRptPlusClick(Sender: TObject); procedure TMainForm.BtnFMRptPlusClick(Sender: TObject);
begin begin
if FSyncingFromController then Exit;
if FController.FFMRptDir = RPT_PLUS then if FController.FFMRptDir = RPT_PLUS then
FController.FFMRptDir := RPT_NONE FController.SetFMRpt(RPT_NONE)
else else
FController.FFMRptDir := RPT_PLUS; FController.SetFMRpt(RPT_PLUS);
FController.FFMRptAutoActive := False; // ручное действие снимает авто-флаг
StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS);
StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS);
BtnFMRptMinus.Repaint;
BtnFMRptPlus.Repaint;
ApplyFMRpt;
SaveCurrentBand; SaveCurrentBand;
end; end;
procedure TMainForm.EdFMRptOffsetChange(Sender: TObject); procedure TMainForm.EdFMRptOffsetChange(Sender: TObject);
var Hz: Double; var Hz: Double;
begin begin
if FSyncingFromController then Exit;
Hz := RptParseOffset(EdFMRptOffset.Text); Hz := RptParseOffset(EdFMRptOffset.Text);
if Hz > 0.0 then if Hz > 0.0 then
begin begin
FController.FFMRptOffsetHz := Hz; FController.SetFMRptOffset(Hz); // применяет, если RPT вкл; OnControllerState
if FController.FFMRptDir <> RPT_NONE then if FController.FFMRptDir <> RPT_NONE then SaveCurrentBand;
begin
ApplyFMRpt;
SaveCurrentBand;
end;
end;
end;
procedure TMainForm.ApplyFMRpt;
begin
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; end;
end; end;
procedure TMainForm.UpdateRptAutoState; procedure TMainForm.UpdateRptAutoState;
var InRange: Boolean;
begin begin
InRange := RptAutoDir(FController.FVfoA) = RPT_MINUS; // Логика авто-MINUS — в контроллере; рендер кнопок — OnControllerState(rfFMRpt).
if InRange then FController.UpdateRptAutoState;
begin
// Auto-enable minus only when currently off.
if FController.FFMRptDir = RPT_NONE then
begin
FController.FFMRptDir := RPT_MINUS;
FController.FFMRptAutoActive := True;
if BtnFMRptMinus <> nil then
begin
StyleButton(BtnFMRptMinus, True);
BtnFMRptMinus.Repaint;
end;
end;
end
else
begin
// Auto-disable only if this minus was set automatically.
if (FController.FFMRptDir = RPT_MINUS) and FController.FFMRptAutoActive then
begin
FController.FFMRptDir := RPT_NONE;
FController.FFMRptAutoActive := False;
if BtnFMRptMinus <> nil then
begin
StyleButton(BtnFMRptMinus, False);
BtnFMRptMinus.Repaint;
end;
end;
end;
end; end;
procedure TMainForm.CloseFMStepPopup; procedure TMainForm.CloseFMStepPopup;
+55 -8
View File
@@ -110,6 +110,7 @@ type
procedure Changed(Field: TRadioField); procedure Changed(Field: TRadioField);
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг) procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
procedure PushNetworkState; // полный HP-кадр в радио (RX/TX freq+drive)
public public
// ============================================================ // ============================================================
// ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm). // ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm).
@@ -277,7 +278,9 @@ type
procedure SetFMCTCSSTone(Idx: Integer); procedure SetFMCTCSSTone(Idx: Integer);
procedure SetFMSquelch(On_: Boolean); procedure SetFMSquelch(On_: Boolean);
procedure SetFMSquelchLevel(V: Integer); procedure SetFMSquelchLevel(V: Integer);
procedure SetFMRpt(Dir: Integer); procedure SetFMRpt(Dir: Integer); // явное направление; снимает авто-флаг
procedure SetFMRptOffset(Hz: Double); // сдвиг репитера; применяет, если RPT вкл
procedure UpdateRptAutoState; // авто-MINUS для 2 м под FVfoA
// Каналы // Каналы
procedure ApplyChannel(Idx: Integer); procedure ApplyChannel(Idx: Integer);
@@ -323,6 +326,18 @@ begin
if Assigned(FOnStateChanged) then FOnStateChanged(Self, Field); if Assigned(FOnStateChanged) then FOnStateChanged(Self, Field);
end; end;
procedure TRadioController.PushNetworkState;
// Полный HP-кадр в радио: RX/TX частоты (через трансвертор), drive, состояние
// MOX. Вызывается командами, которые меняют центр/частоту/репитер-сдвиг.
begin
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
procedure TRadioController.Invoke(M: TThreadMethod); procedure TRadioController.Invoke(M: TThreadMethod);
begin begin
if Assigned(FOnInvoke) then FOnInvoke(M) if Assigned(FOnInvoke) then FOnInvoke(M)
@@ -586,12 +601,7 @@ begin
// движок — FCenterFreq остаётся как есть, спектр не прыгает.) // движок — FCenterFreq остаётся как есть, спектр не прыгает.)
FCenterFreq := FVfoA; FCenterFreq := FVfoA;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then PushNetworkState;
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end; end;
Changed(rfCTun); Changed(rfCTun);
end; end;
@@ -682,7 +692,44 @@ begin
end; end;
procedure TRadioController.SetFMRpt(Dir: Integer); procedure TRadioController.SetFMRpt(Dir: Integer);
begin FFMRptDir := Dir; Changed(rfFMRpt); { TODO wiring } end; begin
FFMRptDir := Dir;
FFMRptAutoActive := False; // явная установка направления — ручное действие
PushNetworkState; // TX-сдвиг применяется через ActiveTXFreqHz
Changed(rfFMRpt);
end;
procedure TRadioController.SetFMRptOffset(Hz: Double);
begin
if Hz <= 0.0 then Exit;
FFMRptOffsetHz := Hz;
if FFMRptDir <> RPT_NONE then PushNetworkState;
Changed(rfFMRpt);
end;
procedure TRadioController.UpdateRptAutoState;
// Авто-включение MINUS-сдвига в 2-метровом репитерном сегменте и авто-снятие
// при уходе из него (только если MINUS был выставлен автоматически).
begin
if RptAutoDir(FVfoA) = RPT_MINUS then
begin
if FFMRptDir = RPT_NONE then
begin
FFMRptDir := RPT_MINUS;
FFMRptAutoActive := True;
Changed(rfFMRpt);
end;
end
else
begin
if (FFMRptDir = RPT_MINUS) and FFMRptAutoActive then
begin
FFMRptDir := RPT_NONE;
FFMRptAutoActive := False;
Changed(rfFMRpt);
end;
end;
end;
procedure TRadioController.ApplyChannel(Idx: Integer); procedure TRadioController.ApplyChannel(Idx: Integer);
begin Changed(rfChannel); { TODO wiring } end; begin Changed(rfChannel); { TODO wiring } end;