mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
1186 lines
38 KiB
ObjectPascal
1186 lines
38 KiB
ObjectPascal
unit SettingsForm;
|
||
|
||
{
|
||
SettingsForm.pas — диалог настроек EWSDR SDR приложения.
|
||
|
||
Вкладки: General | Audio | Display | DSP | Transmit | PA Settings | CAT
|
||
Display имеет подвкладки: General | RX1 | RX2 | TX
|
||
|
||
Все изменения применяются МГНОВЕННО через events:
|
||
OnDisplayChange — изменены FFT/Window/Detector/Avg параметры
|
||
OnWaterfallChange — изменены параметры водопада
|
||
OnGridChange — изменена сетка спектра
|
||
OnAudioChange — изменено аудио устройство/буфер
|
||
OnSampleRateChange — изменён audio output sample rate (только PortAudio, DDC не трогается)
|
||
|
||
Для применения настроек: создайте форму, зарегистрируйте callbacks,
|
||
потом вызовите Show или ShowModal.
|
||
}
|
||
|
||
{$IFDEF FPC}
|
||
{$MODE Delphi}
|
||
{$ENDIF}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, Math,
|
||
Forms, Controls, Graphics, Dialogs,
|
||
StdCtrls, ExtCtrls, ComCtrls, Spin,
|
||
FlatButton, AudioOutput, AudioInput;
|
||
|
||
type
|
||
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
|
||
TOnPASettingsChange = procedure(MaxPower: Double;
|
||
const BandCal: array of Double) of object;
|
||
|
||
TOnDisplayParamChange = procedure(FFTSize, WindowType, SpecDetector,
|
||
SpecAvgMode: Integer; SpecAvgTimeMS: Double) of object;
|
||
TOnWaterfallParamChange = procedure(WfDetector, WfAvgMode: Integer;
|
||
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double) of object;
|
||
TOnGridParamChange = procedure(RefLevel, Range, GridStep: Double) of object;
|
||
TOnAudioDevChange = procedure(DevIndex: Integer; const DevName: string) of object;
|
||
TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object;
|
||
TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall: Boolean) of object;
|
||
TOnFPSChange = procedure(FPS: Integer) of object;
|
||
|
||
{ TSettingsForm }
|
||
TSettingsForm = class(TForm)
|
||
private
|
||
// ---- Tabs ----
|
||
FPageCtrl: TPageControl;
|
||
FTabGeneral: TTabSheet;
|
||
FTabAudio: TTabSheet;
|
||
FTabDisplay: TTabSheet;
|
||
FTabDSP: TTabSheet;
|
||
FTabTransmit: TTabSheet;
|
||
FTabPA: TTabSheet;
|
||
FTabCAT: TTabSheet;
|
||
|
||
// ---- Audio tab controls ----
|
||
FCmbRXDev: TComboBox;
|
||
FRXDevIndices: array[0..63] of Integer;
|
||
FRXDevCount: Integer;
|
||
FCmbTXDev: TComboBox;
|
||
FTXDevIndices: array[0..63] of Integer;
|
||
FTXDevCount: Integer;
|
||
|
||
// ---- Display sub-tabs ----
|
||
FDispPageCtrl: TPageControl;
|
||
FTabDispGeneral: TTabSheet;
|
||
FTabDispRX1: TTabSheet;
|
||
FTabDispRX2: TTabSheet;
|
||
FTabDispTX: TTabSheet;
|
||
|
||
// ---- RX1 sub-tab controls ----
|
||
// FFT group
|
||
FCmbFFTSize: TComboBox;
|
||
FCmbWindow: TComboBox;
|
||
// Spectrum group
|
||
FCmbSpecDet: TComboBox;
|
||
FCmbSpecAvg: TComboBox;
|
||
FSpecAvgTime: TFloatSpinEdit;
|
||
// Waterfall group
|
||
FCmbWfDet: TComboBox;
|
||
FCmbWfAvg: TComboBox;
|
||
FWfAvgTime: TFloatSpinEdit;
|
||
FWfHigh: TFloatSpinEdit;
|
||
FWfLow: TFloatSpinEdit;
|
||
FWfAGCOffset: TFloatSpinEdit;
|
||
// Grid group
|
||
FEdRefLevel: TEdit;
|
||
FEdRange: TEdit;
|
||
FCmbGridStep: TComboBox;
|
||
|
||
// ---- General display tab controls ----
|
||
FChkShowSpectrum: TCheckBox;
|
||
FChkShowWaterfall: TCheckBox;
|
||
FCmbFPS: TComboBox;
|
||
|
||
// ---- PA Settings tab controls ----
|
||
FEdMaxPower: TSpinEdit;
|
||
FEdBandCal: array[0..10] of TFloatSpinEdit;
|
||
|
||
// ---- Close button ----
|
||
FBtnClose: TFlatButton;
|
||
|
||
// ---- Callbacks ----
|
||
FOnPAChange: TOnPASettingsChange;
|
||
FOnDisplayChange: TOnDisplayParamChange;
|
||
FOnWaterfallChange: TOnWaterfallParamChange;
|
||
FOnGridChange: TOnGridParamChange;
|
||
FOnAudioDevChange: TOnAudioDevChange;
|
||
FOnAudioInDevChange: TOnAudioInDevChange;
|
||
FOnVisibilityChange: TOnVisibilityChange;
|
||
FOnFPSChange: TOnFPSChange;
|
||
|
||
// ---- Suppress change events during load ----
|
||
FLoading: Boolean;
|
||
|
||
procedure BuildUI;
|
||
procedure BuildAudioTab;
|
||
procedure BuildDisplayTab;
|
||
procedure BuildRX1Tab;
|
||
procedure BuildPATab;
|
||
procedure ApplyTheme;
|
||
|
||
// Helpers
|
||
function MakeLbl(AParent: TWinControl; const Cap: string;
|
||
ALeft, ATop, AW: Integer): TLabel;
|
||
function MakeCombo(AParent: TWinControl; ALeft, ATop, AW: Integer;
|
||
OnChange: TNotifyEvent): TComboBox;
|
||
function MakeGroupBox(AParent: TWinControl; const Cap: string;
|
||
ALeft, ATop, AW, AH: Integer): TGroupBox;
|
||
|
||
// Event handlers
|
||
procedure OnFFTSizeChange(Sender: TObject);
|
||
procedure OnWindowChange(Sender: TObject);
|
||
procedure OnSpecDetChange(Sender: TObject);
|
||
procedure OnSpecAvgModeChange(Sender: TObject);
|
||
procedure OnSpecAvgTimeChange(Sender: TObject);
|
||
procedure OnWfDetChange(Sender: TObject);
|
||
procedure OnWfAvgModeChange(Sender: TObject);
|
||
procedure OnWfAvgTimeChange(Sender: TObject);
|
||
procedure OnWfThresholdChange(Sender: TObject);
|
||
procedure OnRefLevelChange(Sender: TObject);
|
||
procedure OnRangeChange(Sender: TObject);
|
||
procedure OnGridStepChange(Sender: TObject);
|
||
procedure OnRXDevChange(Sender: TObject);
|
||
procedure OnTXDevChange(Sender: TObject);
|
||
procedure OnVisibilityChkChange(Sender: TObject);
|
||
procedure OnFPSCmbChange(Sender: TObject);
|
||
procedure OnMaxPowerChange(Sender: TObject);
|
||
procedure OnBandCalChange(Sender: TObject);
|
||
procedure BtnCloseClick(Sender: TObject);
|
||
|
||
procedure FireDisplayChange;
|
||
procedure FireWaterfallChange;
|
||
procedure FireGridChange;
|
||
|
||
function GetSpecAvgTimeMS: Double;
|
||
function GetWfAvgTimeMS: Double;
|
||
function GetWfHigh: Double;
|
||
function GetWfLow: Double;
|
||
function GetWfAGCOffset: Double;
|
||
function GetRefLevel: Double;
|
||
function GetRange: Double;
|
||
function GetGridStep: Double;
|
||
function GetFFTSize: Integer;
|
||
|
||
public
|
||
constructor Create(AOwner: TComponent); override;
|
||
|
||
// Заполнить элементы управления текущими значениями (без firing events)
|
||
procedure LoadValues(
|
||
FFTSize, WinType, SpecDet, SpecAvgMode: Integer;
|
||
SpecAvgTimeMS: Double;
|
||
WfDet, WfAvgMode: Integer;
|
||
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double;
|
||
RefLevel, Range, GridStep: Double;
|
||
const AudioOutDevName: string;
|
||
const AudioInDevName: string);
|
||
|
||
// Перечислить PA устройства ввода и вывода
|
||
procedure RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput);
|
||
// Загрузить текущие значения видимости (без fire событий)
|
||
procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean);
|
||
// Загрузить текущий FPS (без fire события)
|
||
procedure LoadFPS(FPS: Integer);
|
||
// Загрузить PA настройки (без fire событий)
|
||
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
|
||
|
||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
|
||
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
|
||
property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange;
|
||
property OnAudioDevChange: TOnAudioDevChange read FOnAudioDevChange write FOnAudioDevChange;
|
||
property OnAudioInDevChange: TOnAudioInDevChange read FOnAudioInDevChange write FOnAudioInDevChange;
|
||
property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange;
|
||
property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange;
|
||
end;
|
||
|
||
implementation
|
||
|
||
const
|
||
CLR_BG = TColor($00121212);
|
||
CLR_PANEL = TColor($001A1A1A);
|
||
CLR_GROUP = TColor($00202020);
|
||
CLR_TEXT = TColor($00E0E0E0);
|
||
CLR_TEXTDIM = TColor($00888888);
|
||
CLR_BORDER = TColor($00303030);
|
||
CLR_ACCENT = TColor($0040FF80);
|
||
BTN_H = 24;
|
||
|
||
FFT_SIZES: array[0..6] of Integer = (4096, 8192, 16384, 32768, 65536, 131072, 262144);
|
||
FFT_NAMES: array[0..6] of string = ('4096','8192','16384','32768','65536','131072','262144');
|
||
|
||
WINDOW_NAMES: array[0..6] of string = (
|
||
'Rectangular', '4-term Blackman-Harris', 'Hann',
|
||
'Flat-top', 'Hamming', 'Kaiser', '7-term Blackman-Harris');
|
||
|
||
DETECTOR_NAMES: array[0..4] of string = (
|
||
'Peak', 'Rosenfell', 'Average', 'Sample', 'RMS');
|
||
|
||
AVGMODE_NAMES: array[0..3] of string = (
|
||
'None', 'Recursive', 'Time Window', 'Log Recursive');
|
||
AVGMODE_VALUES: array[0..3] of Integer = (0, 1, 2, 3);
|
||
WF_AVGMODE_NAMES: array[0..3] of string = (
|
||
'None', 'Recursive', 'Time Window', 'Log Recursive');
|
||
WF_AVGMODE_VALUES: array[0..3] of Integer = (0, 1, 2, 3);
|
||
|
||
GRID_STEPS: array[0..4] of Double = (5.0, 10.0, 20.0, 30.0, 40.0);
|
||
GRID_STEP_NAMES: array[0..4] of string = ('5 dB','10 dB','20 dB','30 dB','40 dB');
|
||
|
||
FPS_VALUES: array[0..12] of Integer = (5, 10, 15, 20, 25, 30, 40, 50, 60, 75, 80, 90, 100);
|
||
FPS_NAMES: array[0..12] of string = ('5 fps','10 fps','15 fps','20 fps','25 fps','30 fps',
|
||
'40 fps','50 fps','60 fps','75 fps','80 fps','90 fps','100 fps');
|
||
|
||
PA_BAND_NAMES: array[0..10] of string =
|
||
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
||
|
||
{ TSettingsForm }
|
||
|
||
constructor TSettingsForm.Create(AOwner: TComponent);
|
||
begin
|
||
inherited CreateNew(AOwner);
|
||
Caption := 'Settings';
|
||
Width := 760;
|
||
Height := 640;
|
||
Position := poScreenCenter;
|
||
BorderStyle := bsSizeable;
|
||
Color := CLR_BG;
|
||
Font.Name := 'Courier New';
|
||
Font.Size := 8;
|
||
Font.Color := CLR_TEXT;
|
||
FLoading := False;
|
||
FRXDevCount := 0;
|
||
BuildUI;
|
||
ApplyTheme;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
function TSettingsForm.MakeLbl(AParent: TWinControl; const Cap: string;
|
||
ALeft, ATop, AW: Integer): TLabel;
|
||
begin
|
||
Result := TLabel.Create(Self);
|
||
Result.Parent := AParent;
|
||
Result.Caption := Cap;
|
||
Result.Left := ALeft;
|
||
Result.Top := ATop;
|
||
if AW > 0 then Result.Width := AW;
|
||
Result.Font.Color := CLR_TEXTDIM;
|
||
Result.Font.Name := 'Courier New';
|
||
Result.Font.Size := 8;
|
||
end;
|
||
|
||
function TSettingsForm.MakeCombo(AParent: TWinControl; ALeft, ATop, AW: Integer;
|
||
OnChange: TNotifyEvent): TComboBox;
|
||
begin
|
||
Result := TComboBox.Create(Self);
|
||
Result.Parent := AParent;
|
||
Result.Left := ALeft;
|
||
Result.Top := ATop;
|
||
Result.Width := AW;
|
||
Result.Height := BTN_H;
|
||
Result.Style := csDropDownList;
|
||
Result.Color := CLR_PANEL;
|
||
Result.Font.Color := CLR_TEXT;
|
||
Result.Font.Name := 'Courier New';
|
||
Result.Font.Size := 8;
|
||
Result.OnChange := OnChange;
|
||
end;
|
||
|
||
function TSettingsForm.MakeGroupBox(AParent: TWinControl; const Cap: string;
|
||
ALeft, ATop, AW, AH: Integer): TGroupBox;
|
||
begin
|
||
Result := TGroupBox.Create(Self);
|
||
Result.Parent := AParent;
|
||
Result.Caption := Cap;
|
||
Result.SetBounds(ALeft, ATop, AW, AH);
|
||
Result.Color := CLR_GROUP;
|
||
Result.Font.Color := CLR_ACCENT;
|
||
Result.Font.Name := 'Courier New';
|
||
Result.Font.Size := 8;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// BuildUI
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.BuildUI;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
FPageCtrl := TPageControl.Create(Self);
|
||
FPageCtrl.Parent := Self;
|
||
FPageCtrl.SetBounds(4, 4, Width - 12, Height - 48);
|
||
FPageCtrl.Anchors := [akLeft, akTop, akRight, akBottom];
|
||
FPageCtrl.Color := CLR_BG;
|
||
FPageCtrl.Font.Color := CLR_TEXT;
|
||
|
||
FTabGeneral := FPageCtrl.AddTabSheet; FTabGeneral.Caption := 'General';
|
||
FTabAudio := FPageCtrl.AddTabSheet; FTabAudio.Caption := 'Audio';
|
||
FTabDisplay := FPageCtrl.AddTabSheet; FTabDisplay.Caption := 'Display';
|
||
FTabDSP := FPageCtrl.AddTabSheet; FTabDSP.Caption := 'DSP';
|
||
FTabTransmit := FPageCtrl.AddTabSheet; FTabTransmit.Caption := 'Transmit';
|
||
FTabPA := FPageCtrl.AddTabSheet; FTabPA.Caption := 'PA Settings';
|
||
FTabCAT := FPageCtrl.AddTabSheet; FTabCAT.Caption := 'CAT';
|
||
|
||
for i := 0 to FPageCtrl.PageCount - 1 do
|
||
FPageCtrl.Pages[i].Color := CLR_BG;
|
||
|
||
BuildAudioTab;
|
||
BuildDisplayTab;
|
||
BuildPATab;
|
||
|
||
// Close button
|
||
FBtnClose := TFlatButton.Create(Self);
|
||
FBtnClose.Parent := Self;
|
||
FBtnClose.Caption := 'CLOSE';
|
||
FBtnClose.SetBounds(Width - 90, Height - 38, 82, BTN_H);
|
||
FBtnClose.Anchors := [akRight, akBottom];
|
||
FBtnClose.OnClick := BtnCloseClick;
|
||
FBtnClose.ClrNorm := CLR_PANEL;
|
||
FBtnClose.ClrBorder := TColor($00404040);
|
||
FBtnClose.ClrHot := TColor($00303030);
|
||
FBtnClose.ClrActive := TColor($00003300);
|
||
FBtnClose.ClrText := CLR_TEXT;
|
||
FBtnClose.ClrTextAct:= CLR_ACCENT;
|
||
end;
|
||
|
||
procedure TSettingsForm.BuildAudioTab;
|
||
const
|
||
R1 = 10; // top padding inside groups
|
||
LW = 110; // label width
|
||
var
|
||
Grp: TGroupBox;
|
||
|
||
procedure MkGrp(out G: TGroupBox; const Cap: string);
|
||
begin
|
||
G := TGroupBox.Create(Self);
|
||
G.Parent := FTabAudio;
|
||
G.Caption := Cap;
|
||
G.Color := CLR_GROUP;
|
||
G.Font.Color := CLR_ACCENT;
|
||
G.Font.Name := 'Courier New';
|
||
G.Font.Size := 8;
|
||
G.AutoSize := True;
|
||
G.Align := alTop;
|
||
end;
|
||
|
||
procedure MkSep;
|
||
var S: TPanel;
|
||
begin
|
||
S := TPanel.Create(Self);
|
||
S.Parent := FTabAudio;
|
||
S.BevelOuter := bvNone;
|
||
S.Color := CLR_BG;
|
||
S.Align := alTop;
|
||
S.Height := 12;
|
||
end;
|
||
|
||
begin
|
||
// ── Top padding ───────────────────────────────────────────────────────────
|
||
MkSep;
|
||
|
||
// ── TX Audio Input ────────────────────────────────────────────────────────
|
||
MkGrp(Grp, 'TX Audio Input');
|
||
MakeLbl(Grp, 'Input Device:', 10, R1+4, LW);
|
||
FCmbTXDev := MakeCombo(Grp, LW+14, R1, 440, OnTXDevChange);
|
||
FCmbTXDev.Items.Add('None');
|
||
|
||
MkSep;
|
||
|
||
// ── RX Audio Output ───────────────────────────────────────────────────────
|
||
MkGrp(Grp, 'RX Audio Output');
|
||
MakeLbl(Grp, 'Output Device:', 10, R1+4, LW);
|
||
FCmbRXDev := MakeCombo(Grp, LW+14, R1, 440, OnRXDevChange);
|
||
FCmbRXDev.Items.Add('Default');
|
||
end;
|
||
|
||
procedure TSettingsForm.BuildDisplayTab;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
FDispPageCtrl := TPageControl.Create(Self);
|
||
FDispPageCtrl.Parent := FTabDisplay;
|
||
FDispPageCtrl.Align := alClient;
|
||
FDispPageCtrl.Color := CLR_BG;
|
||
FDispPageCtrl.Font.Color := CLR_TEXT;
|
||
|
||
FTabDispGeneral := FDispPageCtrl.AddTabSheet; FTabDispGeneral.Caption := 'General';
|
||
FTabDispRX1 := FDispPageCtrl.AddTabSheet; FTabDispRX1.Caption := 'RX1';
|
||
FTabDispRX2 := FDispPageCtrl.AddTabSheet; FTabDispRX2.Caption := 'RX2';
|
||
FTabDispTX := FDispPageCtrl.AddTabSheet; FTabDispTX.Caption := 'TX';
|
||
|
||
FTabDispGeneral.Color := CLR_BG;
|
||
FTabDispRX1.Color := CLR_BG;
|
||
FTabDispRX2.Color := CLR_BG;
|
||
FTabDispTX.Color := CLR_BG;
|
||
|
||
// ---- General sub-tab: visibility checkboxes ----
|
||
FChkShowSpectrum := TCheckBox.Create(Self);
|
||
FChkShowSpectrum.Parent := FTabDispGeneral;
|
||
FChkShowSpectrum.Caption := 'Show Spectrum';
|
||
FChkShowSpectrum.SetBounds(12, 16, 200, 22);
|
||
FChkShowSpectrum.Checked := True;
|
||
FChkShowSpectrum.Font.Color := CLR_TEXT;
|
||
FChkShowSpectrum.Font.Name := 'Courier New';
|
||
FChkShowSpectrum.Font.Size := 8;
|
||
FChkShowSpectrum.OnChange := OnVisibilityChkChange;
|
||
|
||
FChkShowWaterfall := TCheckBox.Create(Self);
|
||
FChkShowWaterfall.Parent := FTabDispGeneral;
|
||
FChkShowWaterfall.Caption := 'Show Waterfall';
|
||
FChkShowWaterfall.SetBounds(12, 44, 200, 22);
|
||
FChkShowWaterfall.Checked := True;
|
||
FChkShowWaterfall.Font.Color := CLR_TEXT;
|
||
FChkShowWaterfall.Font.Name := 'Courier New';
|
||
FChkShowWaterfall.Font.Size := 8;
|
||
FChkShowWaterfall.OnChange := OnVisibilityChkChange;
|
||
|
||
// FPS label + combo
|
||
with TLabel.Create(Self) do
|
||
begin
|
||
Parent := FTabDispGeneral;
|
||
Caption := 'Display FPS:';
|
||
SetBounds(12, 76, 100, 22);
|
||
Font.Color := CLR_TEXTDIM;
|
||
Font.Name := 'Courier New';
|
||
Font.Size := 8;
|
||
end;
|
||
FCmbFPS := MakeCombo(FTabDispGeneral, 116, 74, 100, OnFPSCmbChange);
|
||
for i := 0 to High(FPS_NAMES) do
|
||
FCmbFPS.Items.Add(FPS_NAMES[i]);
|
||
FCmbFPS.ItemIndex := 3; // 20 fps default
|
||
|
||
BuildRX1Tab;
|
||
end;
|
||
|
||
procedure TSettingsForm.BuildRX1Tab;
|
||
const
|
||
R1 = 12;
|
||
LX = 12;
|
||
LW = 112;
|
||
CX = LX + LW + 8;
|
||
STEP = 34;
|
||
SEP = 12;
|
||
GH_FFT = 100;
|
||
GH_SPEC = 136;
|
||
GH_WF = 170;
|
||
GH_GRID = 100;
|
||
var
|
||
PnlFFT, PnlSpec, PnlWF: TPanel;
|
||
Grp: TGroupBox;
|
||
i: Integer;
|
||
|
||
function MkGrp(AParent: TWinControl; const Cap: string; AH: Integer): TGroupBox;
|
||
begin
|
||
Result := TGroupBox.Create(Self);
|
||
Result.Parent := AParent;
|
||
Result.Caption := Cap;
|
||
Result.Color := CLR_GROUP;
|
||
Result.Font.Color := CLR_ACCENT;
|
||
Result.Font.Name := 'Courier New';
|
||
Result.Font.Size := 8;
|
||
Result.Align := alTop;
|
||
Result.Height := AH;
|
||
end;
|
||
|
||
procedure MkSep(AParent: TWinControl; AH: Integer);
|
||
var S: TPanel;
|
||
begin
|
||
S := TPanel.Create(Self);
|
||
S.Parent := AParent;
|
||
S.BevelOuter := bvNone;
|
||
S.Color := CLR_BG;
|
||
S.Align := alTop;
|
||
S.Height := AH;
|
||
end;
|
||
|
||
function MkFloat(AParent: TWinControl; AX, AY, AW: Integer; AValue: Double;
|
||
AMin, AMax, AInc: Double; AOnChange: TNotifyEvent): TFloatSpinEdit;
|
||
begin
|
||
Result := TFloatSpinEdit.Create(Self);
|
||
Result.Parent := AParent;
|
||
Result.SetBounds(AX, AY, AW, BTN_H);
|
||
Result.Value := AValue;
|
||
Result.MinValue := AMin;
|
||
Result.MaxValue := AMax;
|
||
Result.Increment := AInc;
|
||
Result.DecimalPlaces := 1;
|
||
Result.Color := CLR_PANEL;
|
||
Result.Font.Color := CLR_TEXT;
|
||
Result.Font.Name := 'Courier New';
|
||
Result.Font.Size := 8;
|
||
Result.OnChange := AOnChange;
|
||
end;
|
||
|
||
begin
|
||
MkSep(FTabDispRX1, 8);
|
||
|
||
PnlFFT := TPanel.Create(Self);
|
||
PnlFFT.Parent := FTabDispRX1;
|
||
PnlFFT.BevelOuter := bvNone;
|
||
PnlFFT.Color := CLR_BG;
|
||
PnlFFT.Align := alTop;
|
||
PnlFFT.Height := GH_FFT;
|
||
Grp := MkGrp(PnlFFT, 'FFT / Analyzer', GH_FFT);
|
||
MakeLbl(Grp, 'FFT Size:', LX, R1+4, LW);
|
||
FCmbFFTSize := MakeCombo(Grp, CX, R1, 130, OnFFTSizeChange);
|
||
for i := 0 to High(FFT_NAMES) do FCmbFFTSize.Items.Add(FFT_NAMES[i]);
|
||
MakeLbl(Grp, 'Window:', LX, R1+STEP+4, LW);
|
||
FCmbWindow := MakeCombo(Grp, CX, R1+STEP, 250, OnWindowChange);
|
||
for i := 0 to High(WINDOW_NAMES) do FCmbWindow.Items.Add(WINDOW_NAMES[i]);
|
||
|
||
MkSep(FTabDispRX1, SEP);
|
||
|
||
PnlSpec := TPanel.Create(Self);
|
||
PnlSpec.Parent := FTabDispRX1;
|
||
PnlSpec.BevelOuter := bvNone;
|
||
PnlSpec.Color := CLR_BG;
|
||
PnlSpec.Align := alTop;
|
||
PnlSpec.Height := GH_SPEC;
|
||
Grp := MkGrp(PnlSpec, 'Spectrum', GH_SPEC);
|
||
MakeLbl(Grp, 'Detector:', LX, R1+4, LW);
|
||
FCmbSpecDet := MakeCombo(Grp, CX, R1, 172, OnSpecDetChange);
|
||
for i := 0 to High(DETECTOR_NAMES) do FCmbSpecDet.Items.Add(DETECTOR_NAMES[i]);
|
||
MakeLbl(Grp, 'Avg Mode:', LX, R1+STEP+4, LW);
|
||
FCmbSpecAvg := MakeCombo(Grp, CX, R1+STEP, 172, OnSpecAvgModeChange);
|
||
for i := 0 to High(AVGMODE_NAMES) do FCmbSpecAvg.Items.Add(AVGMODE_NAMES[i]);
|
||
MakeLbl(Grp, 'Avg Time (ms):', LX, R1+2*STEP+4, LW);
|
||
FSpecAvgTime := MkFloat(Grp, CX, R1+2*STEP, 92, 120.0, 0.0, 5000.0, 10.0, OnSpecAvgTimeChange);
|
||
|
||
MkSep(FTabDispRX1, SEP);
|
||
|
||
PnlWF := TPanel.Create(Self);
|
||
PnlWF.Parent := FTabDispRX1;
|
||
PnlWF.BevelOuter := bvNone;
|
||
PnlWF.Color := CLR_BG;
|
||
PnlWF.Align := alTop;
|
||
PnlWF.Height := GH_WF;
|
||
Grp := MkGrp(PnlWF, 'Waterfall', GH_WF);
|
||
MakeLbl(Grp, 'Detector:', LX, R1+4, LW);
|
||
FCmbWfDet := MakeCombo(Grp, CX, R1, 172, OnWfDetChange);
|
||
for i := 0 to High(DETECTOR_NAMES) do FCmbWfDet.Items.Add(DETECTOR_NAMES[i]);
|
||
MakeLbl(Grp, 'Avg Mode:', LX, R1+STEP+4, LW);
|
||
FCmbWfAvg := MakeCombo(Grp, CX, R1+STEP, 172, OnWfAvgModeChange);
|
||
for i := 0 to High(WF_AVGMODE_NAMES) do FCmbWfAvg.Items.Add(WF_AVGMODE_NAMES[i]);
|
||
MakeLbl(Grp, 'Avg Time (ms):', LX, R1+2*STEP+4, LW);
|
||
FWfAvgTime := MkFloat(Grp, CX, R1+2*STEP, 92, 120.0, 0.0, 5000.0, 10.0, OnWfAvgTimeChange);
|
||
MakeLbl(Grp, 'High (dBm):', LX, R1+3*STEP+4, LW);
|
||
FWfHigh := MkFloat(Grp, CX, R1+3*STEP, 92, -80.0, -160.0, 20.0, 1.0, OnWfThresholdChange);
|
||
MakeLbl(Grp, 'Low (dBm):', CX+120, R1+3*STEP+4, 72);
|
||
FWfLow := MkFloat(Grp, CX+196, R1+3*STEP, 92, -130.0, -180.0, 0.0, 1.0, OnWfThresholdChange);
|
||
MakeLbl(Grp, 'AGC Offset:', LX, R1+4*STEP+4, LW);
|
||
FWfAGCOffset := MkFloat(Grp, CX, R1+4*STEP, 92, 0.0, -40.0, 40.0, 0.5, OnWfThresholdChange);
|
||
|
||
MkSep(FTabDispRX1, SEP);
|
||
|
||
Grp := MkGrp(FTabDispRX1, 'Spectrum Grid', GH_GRID);
|
||
MakeLbl(Grp, 'Ref Level (dBm):', LX, R1+4, 116);
|
||
FEdRefLevel := TEdit.Create(Self);
|
||
FEdRefLevel.Parent := Grp;
|
||
FEdRefLevel.SetBounds(LX+120, R1, 80, BTN_H);
|
||
FEdRefLevel.Color := CLR_PANEL;
|
||
FEdRefLevel.Font.Color := CLR_TEXT;
|
||
FEdRefLevel.Font.Name := 'Courier New';
|
||
FEdRefLevel.Font.Size := 8;
|
||
FEdRefLevel.OnExit := OnRefLevelChange;
|
||
FEdRefLevel.Text := '-20';
|
||
MakeLbl(Grp, 'Range (dB):', LX, R1+STEP+4, LW);
|
||
FEdRange := TEdit.Create(Self);
|
||
FEdRange.Parent := Grp;
|
||
FEdRange.SetBounds(CX, R1+STEP, 80, BTN_H);
|
||
FEdRange.Color := CLR_PANEL;
|
||
FEdRange.Font.Color := CLR_TEXT;
|
||
FEdRange.Font.Name := 'Courier New';
|
||
FEdRange.Font.Size := 8;
|
||
FEdRange.OnExit := OnRangeChange;
|
||
FEdRange.Text := '110';
|
||
MakeLbl(Grp, 'Grid Step:', CX+90, R1+STEP+4, 72);
|
||
FCmbGridStep := MakeCombo(Grp, CX+90+76, R1+STEP, 80, OnGridStepChange);
|
||
for i := 0 to High(GRID_STEP_NAMES) do FCmbGridStep.Items.Add(GRID_STEP_NAMES[i]);
|
||
FCmbGridStep.ItemIndex := 1;
|
||
end;
|
||
|
||
procedure TSettingsForm.ApplyTheme;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Color := CLR_BG;
|
||
for i := 0 to FPageCtrl.PageCount - 1 do
|
||
FPageCtrl.Pages[i].Color := CLR_BG;
|
||
FDispPageCtrl.Color := CLR_BG;
|
||
for i := 0 to FDispPageCtrl.PageCount - 1 do
|
||
FDispPageCtrl.Pages[i].Color := CLR_BG;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// LoadValues — заполняет элементы без fire событий
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.LoadValues(
|
||
FFTSize, WinType, SpecDet, SpecAvgMode: Integer;
|
||
SpecAvgTimeMS: Double;
|
||
WfDet, WfAvgMode: Integer;
|
||
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double;
|
||
RefLevel, Range, GridStep: Double;
|
||
const AudioOutDevName: string;
|
||
const AudioInDevName: string);
|
||
var
|
||
i, Idx: Integer;
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
// FFT Size
|
||
Idx := 0; // default 4096
|
||
for i := 0 to High(FFT_SIZES) do
|
||
if FFT_SIZES[i] = FFTSize then begin Idx := i; Break; end;
|
||
FCmbFFTSize.ItemIndex := Idx;
|
||
|
||
// Window Type
|
||
if (WinType >= 0) and (WinType < FCmbWindow.Items.Count) then
|
||
FCmbWindow.ItemIndex := WinType
|
||
else
|
||
FCmbWindow.ItemIndex := 2; // Hann
|
||
|
||
// Spectrum Detector
|
||
if (SpecDet >= 0) and (SpecDet < FCmbSpecDet.Items.Count) then
|
||
FCmbSpecDet.ItemIndex := SpecDet
|
||
else
|
||
FCmbSpecDet.ItemIndex := 0;
|
||
|
||
// Spectrum Avg Mode (WDSP value → combo index)
|
||
Idx := 0; // default None
|
||
for i := 0 to High(AVGMODE_VALUES) do
|
||
if AVGMODE_VALUES[i] = SpecAvgMode then begin Idx := i; Break; end;
|
||
FCmbSpecAvg.ItemIndex := Idx;
|
||
FSpecAvgTime.Value := EnsureRange(SpecAvgTimeMS, 0.0, 5000.0);
|
||
|
||
// Waterfall Detector
|
||
if (WfDet >= 0) and (WfDet < FCmbWfDet.Items.Count) then
|
||
FCmbWfDet.ItemIndex := WfDet
|
||
else
|
||
FCmbWfDet.ItemIndex := 2; // Average
|
||
|
||
// Waterfall Avg Mode
|
||
Idx := 1; // default Recursive
|
||
for i := 0 to High(WF_AVGMODE_VALUES) do
|
||
if WF_AVGMODE_VALUES[i] = WfAvgMode then begin Idx := i; Break; end;
|
||
FCmbWfAvg.ItemIndex := Idx;
|
||
FWfAvgTime.Value := EnsureRange(WfAvgTimeMS, 0.0, 5000.0);
|
||
FWfHigh.Value := EnsureRange(WfHigh, -160.0, 20.0);
|
||
FWfLow.Value := EnsureRange(WfLow, -180.0, 0.0);
|
||
FWfAGCOffset.Value := EnsureRange(WfAGCOffset, -40.0, 40.0);
|
||
|
||
// Grid
|
||
FEdRefLevel.Text := FormatFloat('0.#', RefLevel);
|
||
FEdRange.Text := FormatFloat('0.#', Range);
|
||
Idx := 1; // 10 dB default
|
||
for i := 0 to High(GRID_STEPS) do
|
||
if Abs(GRID_STEPS[i] - GridStep) < 0.5 then begin Idx := i; Break; end;
|
||
FCmbGridStep.ItemIndex := Idx;
|
||
|
||
// RX Audio Output
|
||
if AudioOutDevName = '' then
|
||
FCmbRXDev.ItemIndex := 0 // 'Default'
|
||
else
|
||
begin
|
||
Idx := FCmbRXDev.Items.IndexOf(AudioOutDevName);
|
||
if Idx >= 0 then FCmbRXDev.ItemIndex := Idx
|
||
else FCmbRXDev.ItemIndex := 0;
|
||
end;
|
||
|
||
// TX Audio Input
|
||
if AudioInDevName = '' then
|
||
FCmbTXDev.ItemIndex := 0 // 'None'
|
||
else
|
||
begin
|
||
Idx := FCmbTXDev.Items.IndexOf(AudioInDevName);
|
||
if Idx >= 0 then FCmbTXDev.ItemIndex := Idx
|
||
else FCmbTXDev.ItemIndex := 0;
|
||
end;
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// RefreshAudioDevices
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput);
|
||
var
|
||
Names: TStringList;
|
||
Cnt, i: Integer;
|
||
OldRXSel: string;
|
||
OldTXSel: string;
|
||
begin
|
||
Names := TStringList.Create;
|
||
try
|
||
// ── RX Output devices ─────────────────────────────────────────────────
|
||
if AudioOut <> nil then
|
||
begin
|
||
OldRXSel := FCmbRXDev.Text;
|
||
FCmbRXDev.Items.Clear;
|
||
FCmbRXDev.Items.Add('Default');
|
||
FRXDevCount := 0;
|
||
|
||
Cnt := 0;
|
||
Names.Clear;
|
||
if AudioOut.EnumOutputDevices(Names, FRXDevIndices, Cnt) then
|
||
begin
|
||
FRXDevCount := Cnt;
|
||
for i := 0 to Cnt - 1 do
|
||
FCmbRXDev.Items.Add(Names[i]);
|
||
end;
|
||
|
||
i := FCmbRXDev.Items.IndexOf(OldRXSel);
|
||
if i >= 0 then FCmbRXDev.ItemIndex := i
|
||
else FCmbRXDev.ItemIndex := 0;
|
||
end;
|
||
|
||
// ── TX Input devices ──────────────────────────────────────────────────
|
||
if AudioIn <> nil then
|
||
begin
|
||
OldTXSel := FCmbTXDev.Text;
|
||
FCmbTXDev.Items.Clear;
|
||
FCmbTXDev.Items.Add('None');
|
||
FTXDevCount := 0;
|
||
|
||
Cnt := 0;
|
||
Names.Clear;
|
||
if AudioIn.EnumInputDevices(Names, FTXDevIndices, Cnt) then
|
||
begin
|
||
FTXDevCount := Cnt;
|
||
for i := 0 to Cnt - 1 do
|
||
FCmbTXDev.Items.Add(Names[i]);
|
||
end;
|
||
|
||
i := FCmbTXDev.Items.IndexOf(OldTXSel);
|
||
if i >= 0 then FCmbTXDev.ItemIndex := i
|
||
else FCmbTXDev.ItemIndex := 0;
|
||
end;
|
||
finally
|
||
Names.Free;
|
||
end;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Value accessors
|
||
// ---------------------------------------------------------------------------
|
||
|
||
function TSettingsForm.GetFFTSize: Integer;
|
||
var Idx: Integer;
|
||
begin
|
||
Idx := FCmbFFTSize.ItemIndex;
|
||
if (Idx >= 0) and (Idx <= High(FFT_SIZES)) then
|
||
Result := FFT_SIZES[Idx]
|
||
else
|
||
Result := 4096;
|
||
end;
|
||
|
||
function TSettingsForm.GetSpecAvgTimeMS: Double;
|
||
begin
|
||
Result := EnsureRange(FSpecAvgTime.Value, 0.0, 5000.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetWfAvgTimeMS: Double;
|
||
begin
|
||
Result := EnsureRange(FWfAvgTime.Value, 0.0, 5000.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetWfHigh: Double;
|
||
begin
|
||
Result := EnsureRange(FWfHigh.Value, -160.0, 20.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetWfLow: Double;
|
||
begin
|
||
Result := EnsureRange(FWfLow.Value, -180.0, 0.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetWfAGCOffset: Double;
|
||
begin
|
||
Result := EnsureRange(FWfAGCOffset.Value, -40.0, 40.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetRefLevel: Double;
|
||
var V: Double; Err: Integer;
|
||
begin
|
||
Val(FEdRefLevel.Text, V, Err);
|
||
if Err <> 0 then V := -20.0;
|
||
Result := EnsureRange(V, -200.0, 0.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetRange: Double;
|
||
var V: Double; Err: Integer;
|
||
begin
|
||
Val(FEdRange.Text, V, Err);
|
||
if Err <> 0 then V := 110.0;
|
||
Result := EnsureRange(V, 10.0, 200.0);
|
||
end;
|
||
|
||
function TSettingsForm.GetGridStep: Double;
|
||
var Idx: Integer;
|
||
begin
|
||
Idx := FCmbGridStep.ItemIndex;
|
||
if (Idx >= 0) and (Idx <= High(GRID_STEPS)) then
|
||
Result := GRID_STEPS[Idx]
|
||
else
|
||
Result := 10.0;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Fire helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.FireDisplayChange;
|
||
var
|
||
AvgIdx: Integer;
|
||
AvgVal: Integer;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnDisplayChange) then Exit;
|
||
AvgIdx := FCmbSpecAvg.ItemIndex;
|
||
if (AvgIdx >= 0) and (AvgIdx <= High(AVGMODE_VALUES)) then
|
||
AvgVal := AVGMODE_VALUES[AvgIdx]
|
||
else
|
||
AvgVal := 3;
|
||
FOnDisplayChange(
|
||
GetFFTSize,
|
||
FCmbWindow.ItemIndex,
|
||
FCmbSpecDet.ItemIndex,
|
||
AvgVal,
|
||
GetSpecAvgTimeMS);
|
||
end;
|
||
|
||
procedure TSettingsForm.FireWaterfallChange;
|
||
var
|
||
AvgIdx: Integer;
|
||
AvgVal: Integer;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnWaterfallChange) then Exit;
|
||
AvgIdx := FCmbWfAvg.ItemIndex;
|
||
if (AvgIdx >= 0) and (AvgIdx <= High(WF_AVGMODE_VALUES)) then
|
||
AvgVal := WF_AVGMODE_VALUES[AvgIdx]
|
||
else
|
||
AvgVal := 1;
|
||
FOnWaterfallChange(
|
||
FCmbWfDet.ItemIndex,
|
||
AvgVal,
|
||
GetWfAvgTimeMS,
|
||
GetWfHigh,
|
||
GetWfLow,
|
||
GetWfAGCOffset);
|
||
end;
|
||
|
||
procedure TSettingsForm.FireGridChange;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnGridChange) then Exit;
|
||
FOnGridChange(GetRefLevel, GetRange, GetGridStep);
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Event handlers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.OnFFTSizeChange(Sender: TObject);
|
||
begin FireDisplayChange; end;
|
||
|
||
procedure TSettingsForm.OnWindowChange(Sender: TObject);
|
||
begin FireDisplayChange; end;
|
||
|
||
procedure TSettingsForm.OnSpecDetChange(Sender: TObject);
|
||
begin FireDisplayChange; end;
|
||
|
||
procedure TSettingsForm.OnSpecAvgModeChange(Sender: TObject);
|
||
begin FireDisplayChange; end;
|
||
|
||
procedure TSettingsForm.OnSpecAvgTimeChange(Sender: TObject);
|
||
begin FireDisplayChange; end;
|
||
|
||
procedure TSettingsForm.OnWfDetChange(Sender: TObject);
|
||
begin FireWaterfallChange; end;
|
||
|
||
procedure TSettingsForm.OnWfAvgModeChange(Sender: TObject);
|
||
begin FireWaterfallChange; end;
|
||
|
||
procedure TSettingsForm.OnWfAvgTimeChange(Sender: TObject);
|
||
begin FireWaterfallChange; end;
|
||
|
||
procedure TSettingsForm.OnWfThresholdChange(Sender: TObject);
|
||
begin FireWaterfallChange; end;
|
||
|
||
procedure TSettingsForm.OnRefLevelChange(Sender: TObject);
|
||
begin FireGridChange; end;
|
||
|
||
procedure TSettingsForm.OnRangeChange(Sender: TObject);
|
||
begin FireGridChange; end;
|
||
|
||
procedure TSettingsForm.OnGridStepChange(Sender: TObject);
|
||
begin FireGridChange; end;
|
||
|
||
procedure TSettingsForm.OnRXDevChange(Sender: TObject);
|
||
var
|
||
Idx: Integer;
|
||
DevIdx: Integer;
|
||
DevName: string;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnAudioDevChange) then Exit;
|
||
Idx := FCmbRXDev.ItemIndex;
|
||
if Idx <= 0 then
|
||
begin
|
||
// "Default"
|
||
FOnAudioDevChange(-1, '');
|
||
end
|
||
else
|
||
begin
|
||
DevName := FCmbRXDev.Items[Idx];
|
||
// Map combo index to PA device index
|
||
// Combo: 0=Default, 1..N = real devices
|
||
DevIdx := Idx - 1;
|
||
if (DevIdx >= 0) and (DevIdx < FRXDevCount) then
|
||
FOnAudioDevChange(FRXDevIndices[DevIdx], DevName)
|
||
else
|
||
FOnAudioDevChange(-1, '');
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.OnTXDevChange(Sender: TObject);
|
||
var
|
||
Idx: Integer;
|
||
DevIdx: Integer;
|
||
DevName: string;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnAudioInDevChange) then Exit;
|
||
Idx := FCmbTXDev.ItemIndex;
|
||
if Idx <= 0 then
|
||
begin
|
||
// 'None'
|
||
FOnAudioInDevChange(-1, '');
|
||
end
|
||
else
|
||
begin
|
||
DevName := FCmbTXDev.Items[Idx];
|
||
DevIdx := Idx - 1;
|
||
if (DevIdx >= 0) and (DevIdx < FTXDevCount) then
|
||
FOnAudioInDevChange(FTXDevIndices[DevIdx], DevName)
|
||
else
|
||
FOnAudioInDevChange(-1, '');
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean);
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
FChkShowSpectrum.Checked := ShowSpectrum;
|
||
FChkShowWaterfall.Checked := ShowWaterfall;
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.OnVisibilityChkChange(Sender: TObject);
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnVisibilityChange) then Exit;
|
||
FOnVisibilityChange(FChkShowSpectrum.Checked, FChkShowWaterfall.Checked);
|
||
end;
|
||
|
||
procedure TSettingsForm.OnFPSCmbChange(Sender: TObject);
|
||
var
|
||
Idx: Integer;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnFPSChange) then Exit;
|
||
Idx := FCmbFPS.ItemIndex;
|
||
if (Idx >= 0) and (Idx <= High(FPS_VALUES)) then
|
||
FOnFPSChange(FPS_VALUES[Idx]);
|
||
end;
|
||
|
||
procedure TSettingsForm.LoadFPS(FPS: Integer);
|
||
var
|
||
i, Idx: Integer;
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
Idx := 3; // 20 fps default
|
||
for i := 0 to High(FPS_VALUES) do
|
||
if FPS_VALUES[i] = FPS then begin Idx := i; Break; end;
|
||
FCmbFPS.ItemIndex := Idx;
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// BuildPATab — вкладка PA Settings: MaxPower + per-band calibration
|
||
// ---------------------------------------------------------------------------
|
||
|
||
procedure TSettingsForm.BuildPATab;
|
||
const
|
||
LBL_W = 46; // ширина метки диапазона
|
||
ED_W = 72; // ширина SpinEdit (с кнопками)
|
||
COL_W = LBL_W + ED_W + 8; // ширина одной пары
|
||
COLS = 4; // колонок на строку
|
||
ROW_H = 32; // высота строки (spin edits чуть выше plain edit)
|
||
GRP_PAD = 12; // отступ внутри группы
|
||
GRP_TOP = 24; // высота caption группы
|
||
ROWS = 3; // строк для 11 диапазонов (ceil(11/4))
|
||
|
||
function MakeFloatSpin(AParent: TWinControl; ALeft, ATop: Integer;
|
||
AChange: TNotifyEvent): TFloatSpinEdit;
|
||
var E: TFloatSpinEdit;
|
||
begin
|
||
E := TFloatSpinEdit.Create(Self);
|
||
E.Parent := AParent;
|
||
E.SetBounds(ALeft, ATop, ED_W, BTN_H + 2);
|
||
E.Color := CLR_PANEL;
|
||
E.Font.Color := CLR_TEXT;
|
||
E.Font.Name := 'Courier New';
|
||
E.Font.Size := 8;
|
||
E.MinValue := 38.8;
|
||
E.MaxValue := 100.0;
|
||
E.Increment := 0.1;
|
||
E.DecimalPlaces := 1;
|
||
E.Value := 100.0;
|
||
E.OnChange := AChange;
|
||
Result := E;
|
||
end;
|
||
|
||
var
|
||
Grp: TGroupBox;
|
||
Sep: TPanel;
|
||
Lbl: TLabel;
|
||
Spin: TSpinEdit;
|
||
i, r, c, X, Y: Integer;
|
||
begin
|
||
// ── Top padding ───────────────────────────────────────────────────────────
|
||
Sep := TPanel.Create(Self);
|
||
Sep.Parent := FTabPA;
|
||
Sep.BevelOuter := bvNone;
|
||
Sep.Color := CLR_BG;
|
||
Sep.Align := alTop;
|
||
Sep.Height := 10;
|
||
|
||
// ── Group Power & Calibration ─────────────────────────────────────────────
|
||
Grp := TGroupBox.Create(Self);
|
||
Grp.Parent := FTabPA;
|
||
Grp.Caption := 'Power & Calibration';
|
||
Grp.Color := CLR_GROUP;
|
||
Grp.Font.Color := CLR_ACCENT;
|
||
Grp.Font.Name := 'Courier New';
|
||
Grp.Font.Size := 8;
|
||
Grp.Align := alTop;
|
||
Grp.Height := GRP_TOP + ROW_H + 20 + ROWS * ROW_H + GRP_PAD + 16;
|
||
Grp.AutoSize := False;
|
||
|
||
// Max Power строка
|
||
Lbl := TLabel.Create(Self);
|
||
Lbl.Parent := Grp;
|
||
Lbl.Caption := 'Max Power (W):';
|
||
Lbl.SetBounds(GRP_PAD, GRP_TOP + 6, 120, 18);
|
||
Lbl.Font.Color := CLR_TEXTDIM;
|
||
Lbl.Font.Name := 'Courier New';
|
||
Lbl.Font.Size := 8;
|
||
|
||
Spin := TSpinEdit.Create(Self);
|
||
Spin.Parent := Grp;
|
||
Spin.SetBounds(GRP_PAD + 124, GRP_TOP, ED_W, BTN_H + 2);
|
||
Spin.Color := CLR_PANEL;
|
||
Spin.Font.Color := CLR_TEXT;
|
||
Spin.Font.Name := 'Courier New';
|
||
Spin.Font.Size := 8;
|
||
Spin.MinValue := 5;
|
||
Spin.MaxValue := 200;
|
||
Spin.Increment := 1;
|
||
Spin.Value := 100;
|
||
Spin.OnChange := OnMaxPowerChange;
|
||
FEdMaxPower := Spin;
|
||
|
||
// Метка для калибровки
|
||
Lbl := TLabel.Create(Self);
|
||
Lbl.Parent := Grp;
|
||
Lbl.Caption := 'Calibration per band (38.8 ... 100.0):';
|
||
Lbl.SetBounds(GRP_PAD, GRP_TOP + ROW_H + 8, 280, 18);
|
||
Lbl.Font.Color := CLR_TEXTDIM;
|
||
Lbl.Font.Name := 'Courier New';
|
||
Lbl.Font.Size := 8;
|
||
|
||
// Grid 4 columns × 3 rows for 11 bands
|
||
for i := 0 to 10 do
|
||
begin
|
||
r := i div COLS;
|
||
c := i mod COLS;
|
||
X := GRP_PAD + c * (COL_W + 4);
|
||
Y := GRP_TOP + ROW_H + 26 + r * ROW_H;
|
||
|
||
Lbl := TLabel.Create(Self);
|
||
Lbl.Parent := Grp;
|
||
Lbl.Caption := PA_BAND_NAMES[i] + ':';
|
||
Lbl.SetBounds(X, Y + 5, LBL_W, 18);
|
||
Lbl.Font.Color := CLR_TEXTDIM;
|
||
Lbl.Font.Name := 'Courier New';
|
||
Lbl.Font.Size := 8;
|
||
|
||
FEdBandCal[i] := MakeFloatSpin(Grp, X + LBL_W, Y, OnBandCalChange);
|
||
FEdBandCal[i].Tag := i;
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.LoadPASettings(MaxPower: Double;
|
||
const BandCal: array of Double);
|
||
var i, n: Integer;
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
FEdMaxPower.Value := Round(EnsureRange(MaxPower, 5.0, 200.0));
|
||
n := Min(10, High(BandCal));
|
||
for i := 0 to n do
|
||
FEdBandCal[i].Value := EnsureRange(BandCal[i], 38.8, 100.0);
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.OnMaxPowerChange(Sender: TObject);
|
||
var
|
||
BandCal: array[0..10] of Double;
|
||
i: Integer;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnPAChange) then Exit;
|
||
for i := 0 to 10 do
|
||
BandCal[i] := FEdBandCal[i].Value;
|
||
FOnPAChange(FEdMaxPower.Value, BandCal);
|
||
end;
|
||
|
||
procedure TSettingsForm.OnBandCalChange(Sender: TObject);
|
||
var
|
||
BandCal: array[0..10] of Double;
|
||
i: Integer;
|
||
begin
|
||
if FLoading then Exit;
|
||
if not Assigned(FOnPAChange) then Exit;
|
||
for i := 0 to 10 do
|
||
BandCal[i] := FEdBandCal[i].Value;
|
||
FOnPAChange(FEdMaxPower.Value, BandCal);
|
||
end;
|
||
|
||
procedure TSettingsForm.BtnCloseClick(Sender: TObject);
|
||
begin
|
||
Close;
|
||
end;
|
||
|
||
end.
|