add trancverter

This commit is contained in:
2026-05-14 20:24:50 +03:00
parent aea9f02df8
commit 020b9422e0
6 changed files with 1083 additions and 66 deletions
+343 -3
View File
@@ -55,6 +55,7 @@ type
const SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer) of object;
TOnAlexChange = procedure(const A: TAlexSettings) of object;
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
{ TSettingsForm }
TSettingsForm = class(TForm)
@@ -200,6 +201,25 @@ type
// Callback
FOnAlexChange: TOnAlexChange;
// ---- XVTR (Transverter) tab controls ----
FPageXvtr: TScrollBox;
FNavXvtr: TFlatButton;
FXvtrEn: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
FXvtrName: array[0..CFG_XVTR_COUNT-1] of TEdit;
FXvtrBegin: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
FXvtrEnd: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
FXvtrLOOff: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // MHz
FXvtrLOErr: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // Hz (целые)
FXvtrRXGain: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit; // dB
FXvtrRXOnly: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
FXvtrPower: array[0..CFG_XVTR_COUNT-1] of TSpinEdit; // 0..100
FXvtrRF: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
FXvtrDisPA: array[0..CFG_XVTR_COUNT-1] of TCheckBox;
FXvtrRXAnt: array[0..CFG_XVTR_COUNT-1] of TComboBox; // 0=def..3=ANT3
FChkXvtrTunPwr: TCheckBox;
FXvtrConfig: TXvtrSettings;
FOnXvtrChange: TOnXvtrChange;
// ---- CAT tab controls ----
FCATSerialEn: array[0..3] of TCheckBox;
FCATSerialPort: array[0..3] of TEdit;
@@ -240,6 +260,11 @@ type
procedure FireAlexChange;
procedure OnAlexAnyChange(Sender: TObject);
procedure OnAlexRxOnlyChange(Sender: TObject);
procedure BuildXvtrTab;
procedure CollectXvtrFromUI;
procedure FireXvtrChange;
procedure OnXvtrAnyChange(Sender: TObject);
procedure UpdateXvtrRowEnabled(Idx: Integer);
procedure CollectTXFromUI;
procedure FireTXChange;
procedure OnTXAnyChange(Sender: TObject);
@@ -318,9 +343,11 @@ type
TcpEnabled: Boolean; TcpPort: Integer);
procedure LoadTXSettings(const T: TTXSettings);
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
procedure LoadXvtrSettings(const X: TXvtrSettings);
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
@@ -388,8 +415,8 @@ constructor TSettingsForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Caption := 'Settings';
Width := 900;
Height := 660;
Width := 1180;
Height := 720;
Position := poScreenCenter;
BorderStyle := bsSizeable;
Color := CLR_BG;
@@ -540,6 +567,7 @@ begin
FNavAdvanced := MakeNavButton('Advanced', 290);
FNavCAT := MakeNavButton('CAT', 326);
FNavAlex := MakeNavButton('Antenna / Alex', 362);
FNavXvtr := MakeNavButton('Transverter', 398);
FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self;
@@ -556,8 +584,10 @@ begin
FPageAdvanced := MakeScrollPage;
FPageCAT := MakeScrollPage;
FPageAlex := MakeScrollPage;
FPageXvtr := MakeScrollPage;
FAlexBoardType := 0;
TSettingsManager.DefaultAlex(FAlexConfig);
TSettingsManager.DefaultXvtr(FXvtrConfig);
BuildAudioTab;
BuildDisplayTab;
@@ -566,6 +596,7 @@ begin
BuildPATab;
BuildCATTab;
BuildAlexTab;
BuildXvtrTab;
FBtnClose := TFlatButton.Create(Self);
FBtnClose.Parent := Self;
@@ -595,7 +626,8 @@ begin
else if Sender = FNavPA then SelectPage(FPagePA, FNavPA)
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT)
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex);
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr);
end;
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
@@ -609,6 +641,7 @@ begin
FPageAdvanced.Visible := APage = FPageAdvanced;
FPageCAT.Visible := APage = FPageCAT;
FPageAlex.Visible := APage = FPageAlex;
FPageXvtr.Visible := APage = FPageXvtr;
FNavAudio.Active := ANav = FNavAudio;
FNavDisplay.Active := ANav = FNavDisplay;
@@ -619,6 +652,7 @@ begin
FNavAdvanced.Active := ANav = FNavAdvanced;
FNavCAT.Active := ANav = FNavCAT;
FNavAlex.Active := ANav = FNavAlex;
FNavXvtr.Active := ANav = FNavXvtr;
end;
// ---------------------------------------------------------------------------
@@ -2531,4 +2565,310 @@ begin
end;
end;
// ===========================================================================
// XVTR (Transverter) tab
// ===========================================================================
procedure TSettingsForm.BuildXvtrTab;
const
MARGIN = 22;
ROW_H = 30;
HDR_H = 30; // место под заголовок столбца (одна строка + отступ)
// X-координаты колонок (внутри Grp). Подобраны так чтобы помещаться
// в content-area формы (~880px при ширине 1080).
EN_X = 14;
NAME_X = 40; NAME_W = 104; // подпись на band-кнопке (макс. 15 символов)
BEG_X = 152; BEG_W = 80; // 3 десятичных, "144.000"
END_X = 240; END_W = 80;
LO_X = 328; LO_W = 90; // 6 десятичных, "116.000000"
ERR_X = 426; ERR_W = 70; // целое Hz
GAIN_X = 504; GAIN_W = 62; // 1 десятичная, dB
RXO_X = 574; RXO_W = 56; // chk центрирован
PWR_X = 638; PWR_W = 58; // целое %
RF_X = 704; RF_W = 56;
PA_X = 768; PA_W = 66;
ANT_X = 842; ANT_W = 78; // combo default/ANT1/ANT2/ANT3
GRP_W = ANT_X + ANT_W + 14; // 934
var
Grp, GrpOpts: TPanel;
i, Y, GrpY: Integer;
L: TLabel;
function MakeFlt(AParent: TWinControl; AX, AY, AW: Integer;
AMin, AMax, AStep: Double; ADec: Integer): TFloatSpinEdit;
begin
Result := TFloatSpinEdit.Create(Self);
Result.Parent := AParent;
Result.SetBounds(AX, AY, AW, BTN_H);
Result.MinValue := AMin;
Result.MaxValue := AMax;
Result.Increment := AStep;
Result.DecimalPlaces := ADec;
Result.Color := CLR_INPUT;
Result.Font.Color := CLR_INPUT_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnXvtrAnyChange;
end;
function MakeInt(AParent: TWinControl; AX, AY, AW: Integer;
AMin, AMax: Integer): TSpinEdit;
begin
Result := TSpinEdit.Create(Self);
Result.Parent := AParent;
Result.SetBounds(AX, AY, AW, BTN_H);
Result.MinValue := AMin;
Result.MaxValue := AMax;
Result.Color := CLR_INPUT;
Result.Font.Color := CLR_INPUT_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnXvtrAnyChange;
end;
// Чекбокс без подписи (только квадрат). Подпись пишем отдельно над колонкой.
function MakeChk(AParent: TWinControl; AX, AY: Integer): TCheckBox;
begin
Result := TCheckBox.Create(Self);
Result.Parent := AParent;
Result.Caption := '';
Result.SetBounds(AX, AY, 22, 20);
Result.Font.Color := CLR_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnXvtrAnyChange;
end;
function MakeEdt(AParent: TWinControl; AX, AY, AW: Integer): TEdit;
begin
Result := TEdit.Create(Self);
Result.Parent := AParent;
Result.SetBounds(AX, AY, AW, BTN_H);
Result.Color := CLR_INPUT;
Result.Font.Color := CLR_INPUT_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.MaxLength := 15;
Result.OnChange := OnXvtrAnyChange;
end;
function MakeCmb(AParent: TWinControl; AX, AY, AW: Integer): TComboBox;
begin
Result := TComboBox.Create(Self);
Result.Parent := AParent;
Result.SetBounds(AX, AY, AW, BTN_H);
Result.Style := csDropDownList;
Result.Color := CLR_INPUT;
Result.Font.Color := CLR_INPUT_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnXvtrAnyChange;
end;
// Заголовок столбца, центрирован по ширине AW от AX.
function MakeHdr(AParent: TWinControl; const Cap: string;
AX, AY, AW: Integer): TLabel;
begin
Result := TLabel.Create(Self);
Result.Parent := AParent;
Result.Caption := Cap;
Result.SetBounds(AX, AY, AW, 14);
Result.Alignment := taCenter;
Result.Font.Color := CLR_TEXTDIM;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
end;
begin
FLoading := True;
try
// --- Заголовок страницы ---
L := TLabel.Create(Self);
L.Parent := FPageXvtr;
L.Caption := 'Transverters';
L.SetBounds(MARGIN, 18, 500, 28);
L.Font.Name := UI_FONT; L.Font.Size := 16;
L.Font.Style := [fsBold]; L.Font.Color := CLR_TEXT;
L := TLabel.Create(Self);
L.Parent := FPageXvtr;
L.Caption := 'Configure up to 8 transverter (XVTR) bands. When a band is selected ' +
'on the main form, the VFO shows the displayed (RF) frequency while ' +
'the radio is internally tuned to the IF: f_IF = f_displayed LO + LO Error.';
L.SetBounds(MARGIN, 50, GRP_W, 36);
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
L.WordWrap := True;
// --- Group "Slots" ---
GrpY := 100;
Grp := MakeGroupPanel(FPageXvtr, 'Slots',
MARGIN, GrpY, GRP_W, GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14);
// Заголовки колонок (под GRP_TITLE_H + небольшой отступ)
Y := GRP_TITLE_H + 10;
MakeHdr(Grp, 'EN', EN_X - 2, Y, 28);
MakeHdr(Grp, 'Label', NAME_X, Y, NAME_W);
MakeHdr(Grp, 'Begin (MHz)',BEG_X, Y, BEG_W);
MakeHdr(Grp, 'End (MHz)', END_X, Y, END_W);
MakeHdr(Grp, 'LO (MHz)', LO_X, Y, LO_W);
MakeHdr(Grp, 'LO Error Hz',ERR_X, Y, ERR_W);
MakeHdr(Grp, 'RX Gain dB', GAIN_X, Y, GAIN_W);
MakeHdr(Grp, 'RX Only', RXO_X, Y, RXO_W);
MakeHdr(Grp, 'TX Pwr %', PWR_X, Y, PWR_W);
MakeHdr(Grp, 'XVTR RF', RF_X, Y, RF_W);
MakeHdr(Grp, 'Disable PA', PA_X, Y, PA_W);
MakeHdr(Grp, 'RX Antenna', ANT_X, Y, ANT_W);
// Ряды слотов
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
Y := GRP_TITLE_H + HDR_H + i * ROW_H + 2;
// Чекбокс EN центрируем по 24px ширине EN-колонки
FXvtrEn[i] := MakeChk(Grp, EN_X + 5, Y + 2);
FXvtrName[i] := MakeEdt(Grp, NAME_X, Y, NAME_W);
FXvtrBegin[i] := MakeFlt(Grp, BEG_X, Y, BEG_W, 0.0, 50000.0, 0.001, 3);
FXvtrEnd[i] := MakeFlt(Grp, END_X, Y, END_W, 0.0, 50000.0, 0.001, 3);
FXvtrLOOff[i] := MakeFlt(Grp, LO_X, Y, LO_W, -50000.0, 50000.0, 0.001, 6);
FXvtrLOErr[i] := MakeFlt(Grp, ERR_X, Y, ERR_W, -99999.0, 99999.0, 1.0, 0);
FXvtrRXGain[i]:= MakeFlt(Grp, GAIN_X, Y, GAIN_W, -60.0, 60.0, 0.1, 1);
// Чекбокс RX Only центрируем
FXvtrRXOnly[i]:= MakeChk(Grp, RXO_X + (RXO_W - 22) div 2, Y + 2);
FXvtrPower[i] := MakeInt(Grp, PWR_X, Y, PWR_W, 0, 100);
FXvtrRF[i] := MakeChk(Grp, RF_X + (RF_W - 22) div 2, Y + 2);
FXvtrDisPA[i] := MakeChk(Grp, PA_X + (PA_W - 22) div 2, Y + 2);
FXvtrRXAnt[i] := MakeCmb(Grp, ANT_X, Y, ANT_W);
FXvtrRXAnt[i].Items.Add('default');
FXvtrRXAnt[i].Items.Add('ANT1');
FXvtrRXAnt[i].Items.Add('ANT2');
FXvtrRXAnt[i].Items.Add('ANT3');
FXvtrRXAnt[i].ItemIndex := 0;
FXvtrEn[i].Tag := i;
FXvtrEn[i].OnClick := OnXvtrAnyChange;
end;
// --- Group "Options" ---
GrpY := GrpY + GRP_TITLE_H + HDR_H + CFG_XVTR_COUNT * ROW_H + 14 + 18;
GrpOpts := MakeGroupPanel(FPageXvtr, 'Options',
MARGIN, GrpY, GRP_W, GRP_TITLE_H + 36);
FChkXvtrTunPwr := TCheckBox.Create(Self);
FChkXvtrTunPwr.Parent := GrpOpts;
FChkXvtrTunPwr.Caption := 'Use XVTR power for TUN '
+ '(TUN button uses the per-slot TX Power instead of the global TUN Level)';
FChkXvtrTunPwr.SetBounds(14, GRP_TITLE_H + 8, GRP_W - 28, 20);
FChkXvtrTunPwr.Font.Color := CLR_TEXT;
FChkXvtrTunPwr.Font.Name := UI_FONT;
FChkXvtrTunPwr.Font.Size := 9;
FChkXvtrTunPwr.OnChange := OnXvtrAnyChange;
// --- Подсказка про колонки внизу ---
L := TLabel.Create(Self);
L.Parent := FPageXvtr;
L.Caption :=
'EN — enable the slot. Label — band button caption on the main form. ' +
'Begin/End — VFO range (displayed frequency). LO — local oscillator (subtracted from VFO to get IF). ' +
'LO Error — fine correction in Hz. RX Gain — gain offset of the transverter RX side. ' +
'RX Only — block TX on this slot. TX Pwr — drive limit (used as TUN level when "Use XVTR power for TUN" is set). ' +
'XVTR RF — transverter uses the main HF RF path (do NOT engage Alex XVTR DDC IN pin). ' +
'Disable PA — keep the internal PA in RX position during TX (XVTR has its own amplifier). ' +
'RX Antenna — override the per-band antenna for this slot.';
L.SetBounds(MARGIN, GrpY + GRP_TITLE_H + 36 + 16, GRP_W, 96);
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
L.WordWrap := True;
// Применяем начальное состояние disabled-строк
for i := 0 to CFG_XVTR_COUNT - 1 do
UpdateXvtrRowEnabled(i);
finally
FLoading := False;
end;
end;
procedure TSettingsForm.UpdateXvtrRowEnabled(Idx: Integer);
var En: Boolean;
begin
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
if FXvtrEn[Idx] = nil then Exit;
En := FXvtrEn[Idx].Checked;
if FXvtrName[Idx] <> nil then FXvtrName[Idx].Enabled := En;
if FXvtrBegin[Idx] <> nil then FXvtrBegin[Idx].Enabled := En;
if FXvtrEnd[Idx] <> nil then FXvtrEnd[Idx].Enabled := En;
if FXvtrLOOff[Idx] <> nil then FXvtrLOOff[Idx].Enabled := En;
if FXvtrLOErr[Idx] <> nil then FXvtrLOErr[Idx].Enabled := En;
if FXvtrRXGain[Idx]<> nil then FXvtrRXGain[Idx].Enabled:= En;
if FXvtrRXOnly[Idx]<> nil then FXvtrRXOnly[Idx].Enabled:= En;
if FXvtrPower[Idx] <> nil then FXvtrPower[Idx].Enabled := En;
if FXvtrRF[Idx] <> nil then FXvtrRF[Idx].Enabled := En;
if FXvtrDisPA[Idx] <> nil then FXvtrDisPA[Idx].Enabled := En;
if FXvtrRXAnt[Idx] <> nil then FXvtrRXAnt[Idx].Enabled := En;
end;
procedure TSettingsForm.CollectXvtrFromUI;
var i: Integer;
begin
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
if FXvtrEn[i] <> nil then FXvtrConfig.Entries[i].Enabled := FXvtrEn[i].Checked;
if FXvtrName[i] <> nil then FXvtrConfig.Entries[i].ButtonText := Copy(FXvtrName[i].Text, 1, 15);
if FXvtrBegin[i] <> nil then FXvtrConfig.Entries[i].FreqBegin := FXvtrBegin[i].Value * 1.0e6;
if FXvtrEnd[i] <> nil then FXvtrConfig.Entries[i].FreqEnd := FXvtrEnd[i].Value * 1.0e6;
if FXvtrLOOff[i] <> nil then FXvtrConfig.Entries[i].LOOffset := FXvtrLOOff[i].Value * 1.0e6;
if FXvtrLOErr[i] <> nil then FXvtrConfig.Entries[i].LOError := FXvtrLOErr[i].Value;
if FXvtrRXGain[i]<> nil then FXvtrConfig.Entries[i].RXGain := FXvtrRXGain[i].Value;
if FXvtrRXOnly[i]<> nil then FXvtrConfig.Entries[i].RXOnly := FXvtrRXOnly[i].Checked;
if FXvtrPower[i] <> nil then FXvtrConfig.Entries[i].TXPower := FXvtrPower[i].Value;
if FXvtrRF[i] <> nil then FXvtrConfig.Entries[i].XVTRRF := FXvtrRF[i].Checked;
if FXvtrDisPA[i] <> nil then FXvtrConfig.Entries[i].DisablePA := FXvtrDisPA[i].Checked;
if (FXvtrRXAnt[i]<> nil) and (FXvtrRXAnt[i].ItemIndex >= 0) then
FXvtrConfig.Entries[i].RXAntenna := Byte(FXvtrRXAnt[i].ItemIndex);
end;
if FChkXvtrTunPwr <> nil then
FXvtrConfig.UseXVTRTunePower := FChkXvtrTunPwr.Checked;
end;
procedure TSettingsForm.FireXvtrChange;
var i: Integer;
begin
if FLoading then Exit;
CollectXvtrFromUI;
// Перерисовать enabled-состояние строк (если изменилась галка En)
for i := 0 to CFG_XVTR_COUNT - 1 do
UpdateXvtrRowEnabled(i);
if Assigned(FOnXvtrChange) then
FOnXvtrChange(FXvtrConfig);
end;
procedure TSettingsForm.OnXvtrAnyChange(Sender: TObject);
begin
FireXvtrChange;
end;
procedure TSettingsForm.LoadXvtrSettings(const X: TXvtrSettings);
var i: Integer;
begin
FLoading := True;
try
FXvtrConfig := X;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
if FXvtrEn[i] <> nil then FXvtrEn[i].Checked := X.Entries[i].Enabled;
if FXvtrName[i] <> nil then FXvtrName[i].Text := X.Entries[i].ButtonText;
if FXvtrBegin[i] <> nil then FXvtrBegin[i].Value := X.Entries[i].FreqBegin / 1.0e6;
if FXvtrEnd[i] <> nil then FXvtrEnd[i].Value := X.Entries[i].FreqEnd / 1.0e6;
if FXvtrLOOff[i] <> nil then FXvtrLOOff[i].Value := X.Entries[i].LOOffset / 1.0e6;
if FXvtrLOErr[i] <> nil then FXvtrLOErr[i].Value := X.Entries[i].LOError;
if FXvtrRXGain[i]<> nil then FXvtrRXGain[i].Value := X.Entries[i].RXGain;
if FXvtrRXOnly[i]<> nil then FXvtrRXOnly[i].Checked:= X.Entries[i].RXOnly;
if FXvtrPower[i] <> nil then FXvtrPower[i].Value := X.Entries[i].TXPower;
if FXvtrRF[i] <> nil then FXvtrRF[i].Checked := X.Entries[i].XVTRRF;
if FXvtrDisPA[i] <> nil then FXvtrDisPA[i].Checked := X.Entries[i].DisablePA;
if FXvtrRXAnt[i] <> nil then
FXvtrRXAnt[i].ItemIndex := EnsureRange(X.Entries[i].RXAntenna, 0, 3);
UpdateXvtrRowEnabled(i);
end;
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
finally
FLoading := False;
end;
end;
end.