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, Spin, FlatButton, AudioOutput, AudioInput, AppTheme, Settings; type // PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон TOnPASettingsChange = procedure(MaxPower: Double; const BandCal: array of Double) of object; // TX-настройки целиком (любая правка → один callback с актуальной структурой) TOnTXSettingsChange = procedure(const T: TTXSettings) 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; TOnThemeChange = procedure(LightTheme: Boolean) of object; TOnCATChange = procedure( const SerEnabled: array of Boolean; const SerPort: array of string; const SerBaud: array of Integer; const SerDataBits: array of Integer; const SerStopBits: array of Integer; 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) private // ---- Layout ---- FNavPanel: TPanel; FContentPanel: TPanel; FPageAudio: TScrollBox; FPageDisplay: TScrollBox; FPageSpectrum: TScrollBox; FPageWaterfall: TScrollBox; FPagePA: TScrollBox; FPageAdvanced: TScrollBox; FPageCAT: TScrollBox; FPageTransmit: TScrollBox; FNavAudio: TFlatButton; FNavDisplay: TFlatButton; FNavSpectrum: TFlatButton; FNavWaterfall: TFlatButton; FNavPA: TFlatButton; FNavAdvanced: TFlatButton; FNavCAT: TFlatButton; FNavTransmit: TFlatButton; // ---- Audio tab controls ---- FCmbRXDev: TComboBox; FRXDevIndices: array[0..63] of Integer; FRXDevCount: Integer; FCmbTXDev: TComboBox; FTXDevIndices: array[0..63] of Integer; FTXDevCount: Integer; // ---- RX1 sub-tab controls ---- FCmbFFTSize: TComboBox; FCmbWindow: TComboBox; FCmbSpecDet: TComboBox; FCmbSpecAvg: TComboBox; FSpecAvgTime: TFloatSpinEdit; FCmbWfDet: TComboBox; FCmbWfAvg: TComboBox; FWfAvgTime: TFloatSpinEdit; FWfHigh: TFloatSpinEdit; FWfLow: TFloatSpinEdit; FWfAGCOffset: TFloatSpinEdit; FEdRefLevel: TEdit; FEdRange: TEdit; FCmbGridStep: TComboBox; // ---- General display tab controls ---- FChkShowSpectrum: TCheckBox; FChkShowWaterfall: TCheckBox; FCmbFPS: TComboBox; FChkLightTheme: TCheckBox; // ---- PA Settings tab controls ---- FEdMaxPower: TSpinEdit; FEdBandCal: array[0..10] of TFloatSpinEdit; // ---- Transmit tab controls ---- // Текущее состояние, отдаётся в OnTXChange целиком при любой правке. FTX: TTXSettings; // Mic source / options FCmbTXMicSource: TComboBox; FChkTXMicBoost: TCheckBox; FChkTXMicBias: TCheckBox; FChkTXMicLineIn: TCheckBox; FChkTXMicPTT: TCheckBox; FEdTXMicGain: TFloatSpinEdit; FEdTXAttOnTX: TSpinEdit; // Tune (TUN button) FEdTUNLevel: TSpinEdit; FEdTUNFreq: TSpinEdit; // Filter FEdTXFiltLow: TSpinEdit; FEdTXFiltHigh: TSpinEdit; FCmbTXFiltNC: TComboBox; FCmbTXFiltMP: TComboBox; FCmbTXFiltWindow: TComboBox; // Compressor FChkTXCompOn: TCheckBox; FEdTXCompGain: TFloatSpinEdit; // Leveler FChkTXLevOn: TCheckBox; FEdTXLevTop: TFloatSpinEdit; FEdTXLevDecay: TSpinEdit; // ALC FChkTXALCOn: TCheckBox; FEdTXALCMaxGain: TFloatSpinEdit; FEdTXALCDecay: TSpinEdit; // Phase Rotator FChkTXPHROn: TCheckBox; FEdTXPHRStages: TSpinEdit; FEdTXPHRFreq: TFloatSpinEdit; // EQ FChkTXEQOn: TCheckBox; FCmbTXEQBands: TComboBox; FEdTXEQGain: array[0..10] of TFloatSpinEdit; FEdTXEQFreq: array[0..10] of TFloatSpinEdit; // AM FEdTXAMCarrier: TFloatSpinEdit; // FM FEdTXFMDev: TFloatSpinEdit; FEdTXFMLow: TSpinEdit; FEdTXFMHigh: TSpinEdit; FCmbTXFMEmph: TComboBox; // CTCSS FChkTXCTCSSOn: TCheckBox; FEdTXCTCSSFreq: TFloatSpinEdit; // TX Display (отдельный analyzer) FCmbTXFFT: TComboBox; FCmbTXWindow: TComboBox; FCmbTXSpecDet: TComboBox; FCmbTXSpecAvg: TComboBox; FEdTXSpecAvgTime: TFloatSpinEdit; FCmbTXWfDet: TComboBox; FCmbTXWfAvg: TComboBox; FEdTXWfAvgTime: TFloatSpinEdit; // TX Grid (отдельные от RX grid в Display) FEdTXRefLevel: TEdit; FEdTXRange: TEdit; FCmbTXGridStep: TComboBox; // ---- Alex / Antenna tab controls ---- FPageAlex: TScrollBox; FNavAlex: TFlatButton; // Per-band antenna radio buttons (bands 0..10 = 160m..6m) FAlexRxAnt: array[0..10, 1..3] of TRadioButton; FAlexTxAnt: array[0..10, 1..3] of TRadioButton; // Per-band secondary RX (взаимно исключающие checkboxes) FAlexRxByps: array[0..10] of TCheckBox; // BYPS: Ext1+Bypass FAlexRxExt1: array[0..10] of TCheckBox; // EXT1: Ext2+Bypass FAlexRxXvtr: array[0..10] of TCheckBox; // XVTR: XvtrDDC+Bypass // Per-band Do Not TX FAlexDoNotTx: array[0..10] of TCheckBox; // Global TX routing flags FAlexRxBypassOnTx: TCheckBox; FAlexExt1OnTx: TCheckBox; FAlexGndBpf2OnTx: TCheckBox; FAlexEnableXvtrHf: TCheckBox; // Current Alex config state FAlexConfig: TAlexSettings; FAlexBoardType: Integer; // 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; FCATSerialBaud: array[0..3] of TComboBox; FCATSerialDataBits: array[0..3] of TComboBox; FCATSerialStopBits: array[0..3] of TComboBox; FCATSerialParity: array[0..3] of TComboBox; FCATTcpEn: TCheckBox; FCATTcpPort: TSpinEdit; // ---- Close button ---- FBtnClose: TFlatButton; // ---- Callbacks ---- FOnCATChange: TOnCATChange; FOnPAChange: TOnPASettingsChange; FOnDisplayChange: TOnDisplayParamChange; FOnWaterfallChange: TOnWaterfallParamChange; FOnGridChange: TOnGridParamChange; FOnAudioDevChange: TOnAudioDevChange; FOnAudioInDevChange: TOnAudioInDevChange; FOnVisibilityChange: TOnVisibilityChange; FOnFPSChange: TOnFPSChange; FOnThemeChange: TOnThemeChange; FOnTXChange: TOnTXSettingsChange; FLoading: Boolean; procedure BuildUI; procedure BuildAudioTab; procedure BuildDisplayTab; procedure BuildRX1Tab; procedure BuildPATab; procedure BuildCATTab; procedure BuildTransmitTab; procedure BuildAlexTab; procedure CollectAlexFromUI; 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); procedure ApplyTheme(const T: TAppTheme); procedure NavClick(Sender: TObject); procedure SelectPage(APage: TWinControl; ANav: TFlatButton); function MakeLbl(AParent: TWinControl; const Cap: string; ALeft, ATop, AW: Integer): TLabel; function MakeCombo(AParent: TWinControl; ALeft, ATop, AW: Integer; OnChange: TNotifyEvent): TComboBox; function MakeGroupPanel(AParent: TWinControl; const Cap: string; ALeft, ATop, AW, AH: Integer): TPanel; function MakeScrollPage: TScrollBox; function MakeNavButton(const Cap: string; ATop: Integer): TFlatButton; 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 OnLightThemeChkChange(Sender: TObject); procedure OnMaxPowerChange(Sender: TObject); procedure OnBandCalChange(Sender: TObject); procedure OnCATSerialChange(Sender: TObject); procedure OnCATTcpChange(Sender: TObject); procedure FireCATChange; 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; 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); procedure RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput); procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean); procedure LoadFPS(FPS: Integer); procedure LoadLightTheme(ALight: Boolean); procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double); procedure LoadCATSettings( const SerEnabled: array of Boolean; const SerPort: array of string; const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer; 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; 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; property OnThemeChange: TOnThemeChange read FOnThemeChange write FOnThemeChange; property OnTXChange: TOnTXSettingsChange read FOnTXChange write FOnTXChange; end; implementation const CLR_BG = TColor($00121212); CLR_PANEL = TColor($001A1A1A); CLR_TEXT = TColor($00E0E0E0); CLR_TEXTDIM = TColor($00888888); CLR_BORDER = TColor($00303030); CLR_ACCENT = TColor($0040FF80); CLR_INPUT = TColor($00FFFFFF); CLR_INPUT_TEXT = TColor($00202020); {$IFDEF MSWINDOWS} UI_FONT = 'Segoe UI'; {$ELSE} UI_FONT = 'Sans'; {$ENDIF} BTN_H = 24; GRP_TITLE_H = 22; // высота заголовка в group-панелях (title label + divider) CAT_BAUD_VALS: array[0..7] of Integer = (1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200); CAT_BAUD_NAMES: array[0..7] of string = ('1200','2400','4800','9600','19200','38400','57600','115200'); 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 := 1180; Height := 720; Position := poScreenCenter; BorderStyle := bsSizeable; Color := CLR_BG; Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXT; FLoading := False; FRXDevCount := 0; BuildUI; ApplyTheme(DarkTheme); 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 := UI_FONT; Result.Font.Size := 9; 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_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnChange; end; // Создаёт панель-группу с заголовком и разделителем (вместо TGroupBox). // На Windows TGroupBox рисует фрейм системными цветами, TPanel — нет. function TSettingsForm.MakeGroupPanel(AParent: TWinControl; const Cap: string; ALeft, ATop, AW, AH: Integer): TPanel; var TitleLbl: TLabel; Divider: TPanel; begin Result := TPanel.Create(Self); Result.Parent := AParent; Result.SetBounds(ALeft, ATop, AW, AH); Result.BevelOuter := bvNone; Result.Color := CLR_PANEL; TitleLbl := TLabel.Create(Self); TitleLbl.Parent := Result; TitleLbl.Caption := Cap; TitleLbl.SetBounds(8, 5, AW - 16, 14); TitleLbl.Font.Color := CLR_ACCENT; TitleLbl.Font.Name := UI_FONT; TitleLbl.Font.Size := 9; TitleLbl.Font.Style := [fsBold]; Divider := TPanel.Create(Self); Divider.Parent := Result; Divider.SetBounds(0, GRP_TITLE_H, AW + 200, 1); Divider.BevelOuter := bvNone; Divider.Color := CLR_BORDER; end; function TSettingsForm.MakeScrollPage: TScrollBox; begin Result := TScrollBox.Create(Self); Result.Parent := FContentPanel; Result.Align := alClient; Result.BorderStyle := bsNone; Result.Color := CLR_BG; Result.AutoScroll := True; Result.Visible := False; end; function TSettingsForm.MakeNavButton(const Cap: string; ATop: Integer): TFlatButton; begin Result := TFlatButton.Create(Self); Result.Parent := FNavPanel; Result.Caption := Cap; Result.SetBounds(10, ATop, 166, 32); Result.Anchors := [akLeft, akTop, akRight]; Result.OnClick := NavClick; Result.ClrNorm := CLR_PANEL; Result.ClrBorder := CLR_PANEL; Result.ClrHot := TColor($00242424); Result.ClrActive := TColor($00204030); Result.ClrText := CLR_TEXT; Result.ClrTextAct := CLR_ACCENT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.Font.Style := []; end; // --------------------------------------------------------------------------- // BuildUI // --------------------------------------------------------------------------- procedure TSettingsForm.BuildUI; begin FNavPanel := TPanel.Create(Self); FNavPanel.Parent := Self; FNavPanel.Align := alLeft; FNavPanel.Width := 188; FNavPanel.BevelOuter := bvNone; FNavPanel.Color := TColor($00151515); with TLabel.Create(Self) do begin Parent := FNavPanel; Caption := 'Settings'; SetBounds(14, 14, 160, 24); Font.Name := UI_FONT; Font.Size := 14; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FNavPanel; Caption := 'EWSDR'; SetBounds(14, 39, 160, 18); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; FNavAudio := MakeNavButton('Audio', 74); FNavDisplay := MakeNavButton('Display', 110); FNavSpectrum := MakeNavButton('Spectrum', 146); FNavWaterfall := MakeNavButton('Waterfall', 182); FNavTransmit := MakeNavButton('Transmit', 218); FNavPA := MakeNavButton('Power Amplifier', 254); FNavAdvanced := MakeNavButton('Advanced', 290); FNavCAT := MakeNavButton('CAT', 326); FNavAlex := MakeNavButton('Antenna / Alex', 362); FNavXvtr := MakeNavButton('Transverter', 398); FContentPanel := TPanel.Create(Self); FContentPanel.Parent := Self; FContentPanel.Align := alClient; FContentPanel.BevelOuter := bvNone; FContentPanel.Color := CLR_BG; FPageAudio := MakeScrollPage; FPageDisplay := MakeScrollPage; FPageSpectrum := MakeScrollPage; FPageWaterfall := MakeScrollPage; FPageTransmit := MakeScrollPage; FPagePA := MakeScrollPage; FPageAdvanced := MakeScrollPage; FPageCAT := MakeScrollPage; FPageAlex := MakeScrollPage; FPageXvtr := MakeScrollPage; FAlexBoardType := 0; TSettingsManager.DefaultAlex(FAlexConfig); TSettingsManager.DefaultXvtr(FXvtrConfig); BuildAudioTab; BuildDisplayTab; BuildRX1Tab; BuildTransmitTab; BuildPATab; BuildCATTab; BuildAlexTab; BuildXvtrTab; FBtnClose := TFlatButton.Create(Self); FBtnClose.Parent := Self; FBtnClose.Caption := 'Close'; FBtnClose.SetBounds(Width - 106, Height - 40, 92, 28); 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; FBtnClose.Font.Name := UI_FONT; FBtnClose.Font.Size := 9; SelectPage(FPageAudio, FNavAudio); end; procedure TSettingsForm.NavClick(Sender: TObject); begin if Sender = FNavAudio then SelectPage(FPageAudio, FNavAudio) else if Sender = FNavDisplay then SelectPage(FPageDisplay, FNavDisplay) else if Sender = FNavSpectrum then SelectPage(FPageSpectrum, FNavSpectrum) else if Sender = FNavWaterfall then SelectPage(FPageWaterfall, FNavWaterfall) else if Sender = FNavTransmit then SelectPage(FPageTransmit, FNavTransmit) 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 = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr); end; procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton); begin FPageAudio.Visible := APage = FPageAudio; FPageDisplay.Visible := APage = FPageDisplay; FPageSpectrum.Visible := APage = FPageSpectrum; FPageWaterfall.Visible:= APage = FPageWaterfall; FPageTransmit.Visible := APage = FPageTransmit; FPagePA.Visible := APage = FPagePA; FPageAdvanced.Visible := APage = FPageAdvanced; FPageCAT.Visible := APage = FPageCAT; FPageAlex.Visible := APage = FPageAlex; FPageXvtr.Visible := APage = FPageXvtr; FNavAudio.Active := ANav = FNavAudio; FNavDisplay.Active := ANav = FNavDisplay; FNavSpectrum.Active := ANav = FNavSpectrum; FNavWaterfall.Active:= ANav = FNavWaterfall; FNavTransmit.Active := ANav = FNavTransmit; FNavPA.Active := ANav = FNavPA; FNavAdvanced.Active := ANav = FNavAdvanced; FNavCAT.Active := ANav = FNavCAT; FNavAlex.Active := ANav = FNavAlex; FNavXvtr.Active := ANav = FNavXvtr; end; // --------------------------------------------------------------------------- // BuildAudioTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildAudioTab; const MARGIN = 22; PAD = 18; R1 = 42; STEP = 42; LW = 150; CX = PAD + LW + 12; CMB_W = 460; var Grp: TPanel; begin with TLabel.Create(Self) do begin Parent := FPageAudio; Caption := 'Audio'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageAudio; Caption := 'Input and output devices used by the receiver and transmitter.'; SetBounds(MARGIN, 48, 560, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageAudio, 'Audio Devices', MARGIN, 86, 650, 142); MakeLbl(Grp, 'TX input device', PAD, R1 + 5, LW); FCmbTXDev := MakeCombo(Grp, CX, R1, CMB_W, OnTXDevChange); FCmbTXDev.Items.Add('None'); MakeLbl(Grp, 'RX output device', PAD, R1 + STEP + 5, LW); FCmbRXDev := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnRXDevChange); FCmbRXDev.Items.Add('Default'); end; // --------------------------------------------------------------------------- // BuildDisplayTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildDisplayTab; const MARGIN = 22; PAD = 18; R1 = 42; CHKW = 220; LW = 150; CX = PAD + LW + 12; var i: Integer; Grp: TPanel; begin with TLabel.Create(Self) do begin Parent := FPageDisplay; Caption := 'Display'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageDisplay; Caption := 'General visual options. Spectrum and waterfall processing are split into their own pages.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageDisplay, 'Visible Panes', MARGIN, 86, 650, 86); FChkShowSpectrum := TCheckBox.Create(Self); FChkShowSpectrum.Parent := Grp; FChkShowSpectrum.Caption := 'Spectrum'; FChkShowSpectrum.SetBounds(PAD, R1, CHKW, 22); FChkShowSpectrum.Checked := True; FChkShowSpectrum.Font.Color := CLR_TEXT; FChkShowSpectrum.Font.Name := UI_FONT; FChkShowSpectrum.Font.Size := 9; FChkShowSpectrum.OnChange := OnVisibilityChkChange; FChkShowWaterfall := TCheckBox.Create(Self); FChkShowWaterfall.Parent := Grp; FChkShowWaterfall.Caption := 'Waterfall'; FChkShowWaterfall.SetBounds(PAD + CHKW + 20, R1, CHKW, 22); FChkShowWaterfall.Checked := True; FChkShowWaterfall.Font.Color := CLR_TEXT; FChkShowWaterfall.Font.Name := UI_FONT; FChkShowWaterfall.Font.Size := 9; FChkShowWaterfall.OnChange := OnVisibilityChkChange; Grp := MakeGroupPanel(FPageDisplay, 'Performance', MARGIN, 186, 650, 92); MakeLbl(Grp, 'Refresh rate', PAD, R1 + 5, LW); FCmbFPS := MakeCombo(Grp, CX, R1, 140, OnFPSCmbChange); for i := 0 to High(FPS_NAMES) do FCmbFPS.Items.Add(FPS_NAMES[i]); FCmbFPS.ItemIndex := 8; Grp := MakeGroupPanel(FPageDisplay, 'Appearance', MARGIN, 292, 650, 68); FChkLightTheme := TCheckBox.Create(Self); FChkLightTheme.Parent := Grp; FChkLightTheme.Caption := 'Light theme'; FChkLightTheme.SetBounds(PAD, R1, CHKW, 22); FChkLightTheme.Checked := False; FChkLightTheme.Font.Color := CLR_TEXT; FChkLightTheme.Font.Name := UI_FONT; FChkLightTheme.Font.Size := 9; FChkLightTheme.OnChange := OnLightThemeChkChange; end; // --------------------------------------------------------------------------- // BuildRX1Tab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildRX1Tab; const MARGIN = 22; PAD = 18; LW = 150; CX = PAD + LW + 12; CMB_W = 230; CMB_W_LONG = 300; SPW = 96; EDT_W = 96; STEP = 42; R1 = 42; var Grp: TPanel; i: Integer; function MkFloat(AX, AY, AW: Integer; AValue, AMin, AMax, AInc: Double; AOnChange: TNotifyEvent): TFloatSpinEdit; begin Result := TFloatSpinEdit.Create(Self); Result.Parent := Grp; 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_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := AOnChange; end; function MkEdit(AX, AY, AW: Integer; const AText: string; AOnExit: TNotifyEvent): TEdit; begin Result := TEdit.Create(Self); Result.Parent := Grp; 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.Text := AText; Result.OnExit := AOnExit; end; begin with TLabel.Create(Self) do begin Parent := FPageSpectrum; Caption := 'Spectrum'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageSpectrum; Caption := 'Analyzer, detector, averaging and grid parameters for RX1.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageSpectrum, 'Analyzer', MARGIN, 86, 650, 128); MakeLbl(Grp, 'FFT Size:', PAD, R1 + 5, LW); FCmbFFTSize := MakeCombo(Grp, CX, R1, CMB_W, OnFFTSizeChange); for i := 0 to High(FFT_NAMES) do FCmbFFTSize.Items.Add(FFT_NAMES[i]); MakeLbl(Grp, 'Window:', PAD, R1 + STEP + 5, LW); FCmbWindow := MakeCombo(Grp, CX, R1 + STEP, CMB_W_LONG, OnWindowChange); for i := 0 to High(WINDOW_NAMES) do FCmbWindow.Items.Add(WINDOW_NAMES[i]); Grp := MakeGroupPanel(FPageSpectrum, 'Detection && Averaging', MARGIN, 230, 650, 170); MakeLbl(Grp, 'Detector:', PAD, R1 + 5, LW); FCmbSpecDet := MakeCombo(Grp, CX, R1, CMB_W, OnSpecDetChange); for i := 0 to High(DETECTOR_NAMES) do FCmbSpecDet.Items.Add(DETECTOR_NAMES[i]); MakeLbl(Grp, 'Avg Mode:', PAD, R1 + STEP + 5, LW); FCmbSpecAvg := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnSpecAvgModeChange); for i := 0 to High(AVGMODE_NAMES) do FCmbSpecAvg.Items.Add(AVGMODE_NAMES[i]); MakeLbl(Grp, 'Avg Time (ms):', PAD, R1 + 2 * STEP + 5, LW); FSpecAvgTime := MkFloat(CX, R1 + 2 * STEP, SPW, 30.0, 0.0, 5000.0, 10.0, OnSpecAvgTimeChange); Grp := MakeGroupPanel(FPageSpectrum, 'Grid', MARGIN, 416, 650, 170); MakeLbl(Grp, 'Reference level:', PAD, R1 + 5, LW); FEdRefLevel := MkEdit(CX, R1, EDT_W, '-20', OnRefLevelChange); MakeLbl(Grp, 'Range (dB):', PAD, R1 + STEP + 5, LW); FEdRange := MkEdit(CX, R1 + STEP, EDT_W, '110', OnRangeChange); MakeLbl(Grp, 'Grid step:', PAD, R1 + 2 * STEP + 5, LW); FCmbGridStep := MakeCombo(Grp, CX, R1 + 2 * STEP, 130, OnGridStepChange); for i := 0 to High(GRID_STEP_NAMES) do FCmbGridStep.Items.Add(GRID_STEP_NAMES[i]); FCmbGridStep.ItemIndex := 1; with TLabel.Create(Self) do begin Parent := FPageWaterfall; Caption := 'Waterfall'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageWaterfall; Caption := 'Waterfall detector, smoothing and color scale thresholds.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageWaterfall, 'Detection && Averaging', MARGIN, 86, 650, 170); MakeLbl(Grp, 'Detector:', PAD, R1 + 5, LW); FCmbWfDet := MakeCombo(Grp, CX, R1, CMB_W, OnWfDetChange); for i := 0 to High(DETECTOR_NAMES) do FCmbWfDet.Items.Add(DETECTOR_NAMES[i]); MakeLbl(Grp, 'Avg Mode:', PAD, R1 + STEP + 5, LW); FCmbWfAvg := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnWfAvgModeChange); for i := 0 to High(WF_AVGMODE_NAMES) do FCmbWfAvg.Items.Add(WF_AVGMODE_NAMES[i]); MakeLbl(Grp, 'Avg Time (ms):', PAD, R1 + 2 * STEP + 5, LW); FWfAvgTime := MkFloat(CX, R1 + 2 * STEP, SPW, 120.0, 0.0, 5000.0, 10.0, OnWfAvgTimeChange); Grp := MakeGroupPanel(FPageWaterfall, 'Color Scale', MARGIN, 272, 650, 170); MakeLbl(Grp, 'High (dBm):', PAD, R1 + 5, LW); FWfHigh := MkFloat(CX, R1, SPW, -80.0, -160.0, 20.0, 1.0, OnWfThresholdChange); MakeLbl(Grp, 'Low (dBm):', PAD, R1 + STEP + 5, LW); FWfLow := MkFloat(CX, R1 + STEP, SPW, -130.0, -180.0, 0.0, 1.0, OnWfThresholdChange); MakeLbl(Grp, 'AGC Offset (dB):', PAD, R1 + 2 * STEP + 5, LW); FWfAGCOffset := MkFloat(CX, R1 + 2 * STEP, SPW, 0.0, -40.0, 40.0, 0.5, OnWfThresholdChange); end; // --------------------------------------------------------------------------- // BuildTransmitTab — все настройки TX-тракта (по образцу Thetis Setup→Transmit) // --------------------------------------------------------------------------- procedure TSettingsForm.BuildTransmitTab; const MARGIN = 22; PAD = 18; LW = 150; CX = PAD + LW + 12; STEP = 32; R1 = 42; SPW = 96; CMB_W = 200; var Grp: TPanel; Y, i: Integer; function MkSpin(AParent: TWinControl; AX, AY: Integer; AMin, AMax, AVal: Integer): TSpinEdit; begin Result := TSpinEdit.Create(Self); Result.Parent := AParent; Result.SetBounds(AX, AY, SPW, BTN_H); Result.MinValue := AMin; Result.MaxValue := AMax; Result.Value := AVal; Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnTXAnyChange; end; function MkFloat(AParent: TWinControl; AX, AY: Integer; AVal, AMin, AMax, AInc: Double): TFloatSpinEdit; begin Result := TFloatSpinEdit.Create(Self); Result.Parent := AParent; Result.SetBounds(AX, AY, SPW, BTN_H); Result.MinValue := AMin; Result.MaxValue := AMax; Result.Increment := AInc; Result.Value := AVal; Result.DecimalPlaces := 2; Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnTXAnyChange; end; function MkChk(AParent: TWinControl; AX, AY, AW: Integer; const ACap: string): TCheckBox; begin Result := TCheckBox.Create(Self); Result.Parent := AParent; Result.Caption := ACap; Result.SetBounds(AX, AY, AW, 22); Result.Font.Color := CLR_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnTXAnyChange; end; function MkCmb(AParent: TWinControl; AX, AY, AW: Integer): TComboBox; begin Result := MakeCombo(AParent, AX, AY, AW, OnTXAnyChange); end; begin with TLabel.Create(Self) do begin Parent := FPageTransmit; Caption := 'Transmit'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageTransmit; Caption := 'TX-цепь WDSP: микрофон, фильтр, динамическая обработка, модуляция.'; SetBounds(MARGIN, 48, 700, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; // ---- Mic source / options ---- Y := 86; Grp := MakeGroupPanel(FPageTransmit, 'Microphone', MARGIN, Y, 700, 220); MakeLbl(Grp, 'Mic source', PAD, R1 + 5, LW); FCmbTXMicSource := MkCmb(Grp, CX, R1, CMB_W); FCmbTXMicSource.Items.Add('Radio (HW mic)'); FCmbTXMicSource.Items.Add('Sound card'); FCmbTXMicSource.ItemIndex := 0; FChkTXMicBoost := MkChk(Grp, PAD, R1 + STEP, 200, 'Mic boost +20 dB'); FChkTXMicBias := MkChk(Grp, PAD + 200, R1 + STEP, 200, 'Mic bias'); FChkTXMicLineIn := MkChk(Grp, PAD + 400, R1 + STEP, 160, 'Line in'); FChkTXMicPTT := MkChk(Grp, PAD, R1 + 2 * STEP, 220, 'Mic-jack PTT'); MakeLbl(Grp, 'Mic gain (dB)', PAD, R1 + 3 * STEP + 5, LW); FEdTXMicGain := MkFloat(Grp, CX, R1 + 3 * STEP, 10.0, -20.0, 60.0, 0.5); // ATT on TX — RX-ADC ослабление во время передачи (DUC Specific bytes 57/58/59). // Thetis-style default 31 dB. Помещаем на той же строке, что и Mic gain. MakeLbl(Grp, 'ATT on TX (dB)', PAD + 320, R1 + 3 * STEP + 5, 110); FEdTXAttOnTX := MkSpin(Grp, PAD + 320 + 120, R1 + 3 * STEP, 0, 31, 31); // ---- Tune (TUN button) ---- Inc(Y, 220 + 12); Grp := MakeGroupPanel(FPageTransmit, 'Tune (TUN)', MARGIN, Y, 700, 100); MakeLbl(Grp, 'Tune level (%)', PAD, R1 + 5, LW); FEdTUNLevel := MkSpin(Grp, CX, R1, 0, 100, 10); MakeLbl(Grp, 'Tone freq (Hz)', PAD + 320, R1 + 5, 110); FEdTUNFreq := MkSpin(Grp, PAD + 320 + 120, R1, 100, 5000, 750); // ---- TX bandpass filter ---- Inc(Y, 100 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Filter (bandpass)', MARGIN, Y, 700, 200); MakeLbl(Grp, 'Low cut (Hz)', PAD, R1 + 5, LW); FEdTXFiltLow := MkSpin(Grp, CX, R1, 0, 8000, 100); MakeLbl(Grp, 'High cut (Hz)', PAD + 320, R1 + 5, 90); FEdTXFiltHigh := MkSpin(Grp, PAD + 320 + 100, R1, 100, 12000, 2800); MakeLbl(Grp, 'FIR length', PAD, R1 + STEP + 5, LW); FCmbTXFiltNC := MkCmb(Grp, CX, R1 + STEP, 140); FCmbTXFiltNC.Items.AddStrings(['256','512','1024','2048','4096']); FCmbTXFiltNC.ItemIndex := 2; MakeLbl(Grp, 'Phase mode', PAD, R1 + 2 * STEP + 5, LW); FCmbTXFiltMP := MkCmb(Grp, CX, R1 + 2 * STEP, 200); FCmbTXFiltMP.Items.AddStrings(['Linear Phase','Low Latency']); FCmbTXFiltMP.ItemIndex := 1; MakeLbl(Grp, 'Window', PAD, R1 + 3 * STEP + 5, LW); FCmbTXFiltWindow := MkCmb(Grp, CX, R1 + 3 * STEP, 240); for i := 0 to High(WINDOW_NAMES) do FCmbTXFiltWindow.Items.Add(WINDOW_NAMES[i]); FCmbTXFiltWindow.ItemIndex := 1; // ---- Compressor / Leveler / ALC / Phase Rotator ---- Inc(Y, 200 + 12); Grp := MakeGroupPanel(FPageTransmit, 'Dynamic processing', MARGIN, Y, 700, 220); FChkTXCompOn := MkChk(Grp, PAD, R1, 220, 'Compressor'); MakeLbl(Grp, 'Gain (dB)', PAD + 230, R1 + 5, 80); FEdTXCompGain := MkFloat(Grp, PAD + 320, R1, 5.0, 0.0, 20.0, 0.5); FChkTXLevOn := MkChk(Grp, PAD, R1 + STEP, 220, 'Leveler'); MakeLbl(Grp, 'Top (dB)', PAD + 230, R1 + STEP + 5, 80); FEdTXLevTop := MkFloat(Grp, PAD + 320, R1 + STEP, 5.0, 0.0, 20.0, 0.5); MakeLbl(Grp, 'Decay (ms)', PAD + 430, R1 + STEP + 5, 80); FEdTXLevDecay := MkSpin(Grp, PAD + 520, R1 + STEP, 1, 5000, 500); FChkTXALCOn := MkChk(Grp, PAD, R1 + 2 * STEP, 220, 'ALC'); MakeLbl(Grp, 'Max gain', PAD + 230, R1 + 2 * STEP + 5, 80); FEdTXALCMaxGain := MkFloat(Grp, PAD + 320, R1 + 2 * STEP, 0.0, -20.0, 20.0, 0.5); MakeLbl(Grp, 'Decay (ms)', PAD + 430, R1 + 2 * STEP + 5, 80); FEdTXALCDecay := MkSpin(Grp, PAD + 520, R1 + 2 * STEP, 1, 5000, 10); FChkTXPHROn := MkChk(Grp, PAD, R1 + 3 * STEP, 220, 'Phase Rotator'); MakeLbl(Grp, 'Stages', PAD + 230, R1 + 3 * STEP + 5, 80); FEdTXPHRStages := MkSpin(Grp, PAD + 320, R1 + 3 * STEP, 2, 16, 8); MakeLbl(Grp, 'Corner (Hz)', PAD + 430, R1 + 3 * STEP + 5, 80); FEdTXPHRFreq := MkFloat(Grp, PAD + 520, R1 + 3 * STEP, 338.0, 50.0, 3000.0, 1.0); // ---- TX EQ ---- Inc(Y, 220 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Equalizer', MARGIN, Y, 700, 320); FChkTXEQOn := MkChk(Grp, PAD, R1, 200, 'Enable EQ'); MakeLbl(Grp, 'Bands', PAD + 230, R1 + 5, 60); FCmbTXEQBands := MkCmb(Grp, PAD + 290, R1, 80); FCmbTXEQBands.Items.AddStrings(['3','10']); FCmbTXEQBands.ItemIndex := 1; // 11 строк: [0]=Preamp, [1..10]=полосы. Freq+Gain в одной строке. for i := 0 to 10 do begin if i = 0 then MakeLbl(Grp, 'Preamp (dB)', PAD, R1 + STEP + i * 22 + 3, 100) else MakeLbl(Grp, Format('Band %d', [i]), PAD, R1 + STEP + i * 22 + 3, 80); FEdTXEQFreq[i] := MkFloat(Grp, PAD + 90, R1 + STEP + i * 22, 0.0, 0.0, 20000.0, 10.0); FEdTXEQFreq[i].Width := 100; MakeLbl(Grp, 'gain', PAD + 200, R1 + STEP + i * 22 + 3, 30); FEdTXEQGain[i] := MkFloat(Grp, PAD + 230, R1 + STEP + i * 22, 0.0, -15.0, 15.0, 0.5); FEdTXEQGain[i].Width := 80; end; // ---- AM / FM / CTCSS ---- Inc(Y, 320 + 12); Grp := MakeGroupPanel(FPageTransmit, 'AM / FM / CTCSS', MARGIN, Y, 700, 220); MakeLbl(Grp, 'AM carrier level', PAD, R1 + 5, LW); FEdTXAMCarrier := MkFloat(Grp, CX, R1, 0.5, 0.0, 1.0, 0.05); MakeLbl(Grp, 'FM deviation (Hz)', PAD, R1 + STEP + 5, LW); FEdTXFMDev := MkFloat(Grp, CX, R1 + STEP, 5000.0, 1000.0, 25000.0, 100.0); MakeLbl(Grp, 'FM AF low cut', PAD, R1 + 2 * STEP + 5, LW); FEdTXFMLow := MkSpin(Grp, CX, R1 + 2 * STEP, 0, 5000, 300); MakeLbl(Grp, 'FM AF high cut', PAD + 320, R1 + 2 * STEP + 5, 100); FEdTXFMHigh := MkSpin(Grp, PAD + 320 + 110, R1 + 2 * STEP, 100, 8000, 3000); MakeLbl(Grp, 'FM emphasis', PAD, R1 + 3 * STEP + 5, LW); FCmbTXFMEmph := MkCmb(Grp, CX, R1 + 3 * STEP, 180); FCmbTXFMEmph.Items.AddStrings(['Pre-EQ','Post-EQ']); FCmbTXFMEmph.ItemIndex := 1; FChkTXCTCSSOn := MkChk(Grp, PAD, R1 + 4 * STEP, 220, 'CTCSS tone'); MakeLbl(Grp, 'Freq (Hz)', PAD + 230, R1 + 4 * STEP + 5, 80); FEdTXCTCSSFreq := MkFloat(Grp, PAD + 320, R1 + 4 * STEP, 100.0, 50.0, 300.0, 0.1); // ---- TX Display (отдельный analyzer для TX-спектра/водопада) ---- Inc(Y, 220 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Display (Analyzer)', MARGIN, Y, 700, 290); MakeLbl(Grp, 'FFT size', PAD, R1 + 5, LW); FCmbTXFFT := MkCmb(Grp, CX, R1, 140); for i := 0 to High(FFT_NAMES) do FCmbTXFFT.Items.Add(FFT_NAMES[i]); FCmbTXFFT.ItemIndex := 2; MakeLbl(Grp, 'Window', PAD, R1 + STEP + 5, LW); FCmbTXWindow := MkCmb(Grp, CX, R1 + STEP, 240); for i := 0 to High(WINDOW_NAMES) do FCmbTXWindow.Items.Add(WINDOW_NAMES[i]); FCmbTXWindow.ItemIndex := 2; MakeLbl(Grp, 'Spec detector',PAD, R1 + 2 * STEP + 5, LW); FCmbTXSpecDet := MkCmb(Grp, CX, R1 + 2 * STEP, 200); for i := 0 to High(DETECTOR_NAMES) do FCmbTXSpecDet.Items.Add(DETECTOR_NAMES[i]); FCmbTXSpecDet.ItemIndex := 0; MakeLbl(Grp, 'Spec average', PAD, R1 + 3 * STEP + 5, LW); FCmbTXSpecAvg := MkCmb(Grp, CX, R1 + 3 * STEP, 200); for i := 0 to High(AVGMODE_NAMES) do FCmbTXSpecAvg.Items.Add(AVGMODE_NAMES[i]); FCmbTXSpecAvg.ItemIndex := 3; MakeLbl(Grp, 'Avg time (ms)', PAD + 430, R1 + 3 * STEP + 5, 100); FEdTXSpecAvgTime := MkFloat(Grp, PAD + 540, R1 + 3 * STEP, 30.0, 1.0, 5000.0, 1.0); MakeLbl(Grp, 'WF detector', PAD, R1 + 4 * STEP + 5, LW); FCmbTXWfDet := MkCmb(Grp, CX, R1 + 4 * STEP, 200); for i := 0 to High(DETECTOR_NAMES) do FCmbTXWfDet.Items.Add(DETECTOR_NAMES[i]); FCmbTXWfDet.ItemIndex := 0; MakeLbl(Grp, 'WF average', PAD, R1 + 5 * STEP + 5, LW); FCmbTXWfAvg := MkCmb(Grp, CX, R1 + 5 * STEP, 200); for i := 0 to High(WF_AVGMODE_NAMES) do FCmbTXWfAvg.Items.Add(WF_AVGMODE_NAMES[i]); FCmbTXWfAvg.ItemIndex := 3; MakeLbl(Grp, 'Avg time (ms)', PAD + 430, R1 + 5 * STEP + 5, 100); FEdTXWfAvgTime := MkFloat(Grp, PAD + 540, R1 + 5 * STEP, 120.0, 1.0, 5000.0, 1.0); // ---- TX Grid (RefLevel / Range / GridStep — отдельные от RX) ---- Inc(Y, 290 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Grid', MARGIN, Y, 700, 170); MakeLbl(Grp, 'Reference level', PAD, R1 + 5, LW); FEdTXRefLevel := TEdit.Create(Self); FEdTXRefLevel.Parent := Grp; FEdTXRefLevel.SetBounds(CX, R1, 96, BTN_H); FEdTXRefLevel.Color := CLR_INPUT; FEdTXRefLevel.Font.Color := CLR_INPUT_TEXT; FEdTXRefLevel.Font.Name := UI_FONT; FEdTXRefLevel.Font.Size := 9; FEdTXRefLevel.Text := '0'; FEdTXRefLevel.OnExit := OnTXAnyChange; MakeLbl(Grp, 'Range (dB)', PAD, R1 + STEP + 5, LW); FEdTXRange := TEdit.Create(Self); FEdTXRange.Parent := Grp; FEdTXRange.SetBounds(CX, R1 + STEP, 96, BTN_H); FEdTXRange.Color := CLR_INPUT; FEdTXRange.Font.Color := CLR_INPUT_TEXT; FEdTXRange.Font.Name := UI_FONT; FEdTXRange.Font.Size := 9; FEdTXRange.Text := '80'; FEdTXRange.OnExit := OnTXAnyChange; MakeLbl(Grp, 'Grid step', PAD, R1 + 2 * STEP + 5, LW); FCmbTXGridStep := MkCmb(Grp, CX, R1 + 2 * STEP, 130); for i := 0 to High(GRID_STEP_NAMES) do FCmbTXGridStep.Items.Add(GRID_STEP_NAMES[i]); FCmbTXGridStep.ItemIndex := 1; end; procedure TSettingsForm.CollectTXFromUI; var i: Integer; begin if FCmbTXMicSource <> nil then FTX.MicSource := Max(0, FCmbTXMicSource.ItemIndex); if FChkTXMicBoost <> nil then FTX.MicBoost := FChkTXMicBoost.Checked; if FChkTXMicBias <> nil then FTX.MicBias := FChkTXMicBias.Checked; if FChkTXMicLineIn <> nil then FTX.MicLineIn := FChkTXMicLineIn.Checked; if FChkTXMicPTT <> nil then FTX.MicPTTEnabled := FChkTXMicPTT.Checked; if FEdTXMicGain <> nil then FTX.MicGainDB := FEdTXMicGain.Value; if FEdTXAttOnTX <> nil then FTX.AttOnTX := FEdTXAttOnTX.Value; if FEdTUNLevel <> nil then FTX.TUNLevel := FEdTUNLevel.Value; if FEdTUNFreq <> nil then FTX.TUNFreq := FEdTUNFreq.Value; if FEdTXFiltLow <> nil then FTX.FilterLow := FEdTXFiltLow.Value; if FEdTXFiltHigh <> nil then FTX.FilterHigh := FEdTXFiltHigh.Value; if FCmbTXFiltNC <> nil then FTX.FilterNC := StrToIntDef(FCmbTXFiltNC.Text, 1024); if FCmbTXFiltMP <> nil then FTX.FilterMP := Max(0, FCmbTXFiltMP.ItemIndex); if FCmbTXFiltWindow<> nil then FTX.FilterWindow := Max(0, FCmbTXFiltWindow.ItemIndex); if FChkTXCompOn <> nil then FTX.CompressorOn := FChkTXCompOn.Checked; if FEdTXCompGain <> nil then FTX.CompressorGain := FEdTXCompGain.Value; if FChkTXLevOn <> nil then FTX.LevelerOn := FChkTXLevOn.Checked; if FEdTXLevTop <> nil then FTX.LevelerTop := FEdTXLevTop.Value; if FEdTXLevDecay <> nil then FTX.LevelerDecay := FEdTXLevDecay.Value; if FChkTXALCOn <> nil then FTX.ALCOn := FChkTXALCOn.Checked; if FEdTXALCMaxGain <> nil then FTX.ALCMaxGain := FEdTXALCMaxGain.Value; if FEdTXALCDecay <> nil then FTX.ALCDecay := FEdTXALCDecay.Value; if FChkTXPHROn <> nil then FTX.PhaseRotOn := FChkTXPHROn.Checked; if FEdTXPHRStages <> nil then FTX.PhaseRotStages := FEdTXPHRStages.Value; if FEdTXPHRFreq <> nil then FTX.PhaseRotFreq := FEdTXPHRFreq.Value; if FChkTXEQOn <> nil then FTX.EQOn := FChkTXEQOn.Checked; if FCmbTXEQBands <> nil then if FCmbTXEQBands.ItemIndex = 0 then FTX.EQNumBands := 3 else FTX.EQNumBands := 10; for i := 0 to 10 do begin if FEdTXEQGain[i] <> nil then FTX.EQGains[i] := FEdTXEQGain[i].Value; if FEdTXEQFreq[i] <> nil then FTX.EQFreqs[i] := FEdTXEQFreq[i].Value; end; if FEdTXAMCarrier <> nil then FTX.AMCarrierLevel := FEdTXAMCarrier.Value; if FEdTXFMDev <> nil then FTX.FMDeviation := FEdTXFMDev.Value; if FEdTXFMLow <> nil then FTX.FMLowCut := FEdTXFMLow.Value; if FEdTXFMHigh <> nil then FTX.FMHighCut := FEdTXFMHigh.Value; if FCmbTXFMEmph <> nil then FTX.FMEmphPosition := Max(0, FCmbTXFMEmph.ItemIndex); if FChkTXCTCSSOn <> nil then FTX.CTCSSOn := FChkTXCTCSSOn.Checked; if FEdTXCTCSSFreq <> nil then FTX.CTCSSFreq := FEdTXCTCSSFreq.Value; if (FCmbTXFFT <> nil) and (FCmbTXFFT.ItemIndex >= 0) then FTX.TXFFTSize := FFT_SIZES[FCmbTXFFT.ItemIndex]; if FCmbTXWindow <> nil then FTX.TXWindowType := Max(0, FCmbTXWindow.ItemIndex); if FCmbTXSpecDet <> nil then FTX.TXSpecDetector := Max(0, FCmbTXSpecDet.ItemIndex); if (FCmbTXSpecAvg <> nil) and (FCmbTXSpecAvg.ItemIndex >= 0) then FTX.TXSpecAvgMode := AVGMODE_VALUES[FCmbTXSpecAvg.ItemIndex]; if FEdTXSpecAvgTime <> nil then FTX.TXSpecAvgTimeMS := FEdTXSpecAvgTime.Value; if FCmbTXWfDet <> nil then FTX.TXWfDetector := Max(0, FCmbTXWfDet.ItemIndex); if (FCmbTXWfAvg <> nil) and (FCmbTXWfAvg.ItemIndex >= 0) then FTX.TXWfAvgMode := WF_AVGMODE_VALUES[FCmbTXWfAvg.ItemIndex]; if FEdTXWfAvgTime <> nil then FTX.TXWfAvgTimeMS := FEdTXWfAvgTime.Value; if FEdTXRefLevel <> nil then begin Val(FEdTXRefLevel.Text, FTX.TXSpecRefLevel, i); if i <> 0 then FTX.TXSpecRefLevel := 0.0; end; if FEdTXRange <> nil then begin Val(FEdTXRange.Text, FTX.TXSpecRange, i); if i <> 0 then FTX.TXSpecRange := 80.0; end; if (FCmbTXGridStep <> nil) and (FCmbTXGridStep.ItemIndex >= 0) and (FCmbTXGridStep.ItemIndex <= High(GRID_STEPS)) then FTX.TXSpecGridStep := GRID_STEPS[FCmbTXGridStep.ItemIndex]; end; procedure TSettingsForm.FireTXChange; begin if FLoading then Exit; CollectTXFromUI; if Assigned(FOnTXChange) then FOnTXChange(FTX); end; procedure TSettingsForm.OnTXAnyChange(Sender: TObject); begin FireTXChange; end; procedure TSettingsForm.LoadTXSettings(const T: TTXSettings); var i: Integer; begin FLoading := True; try FTX := T; if FCmbTXMicSource <> nil then FCmbTXMicSource.ItemIndex := EnsureRange(T.MicSource, 0, FCmbTXMicSource.Items.Count - 1); if FChkTXMicBoost <> nil then FChkTXMicBoost.Checked := T.MicBoost; if FChkTXMicBias <> nil then FChkTXMicBias.Checked := T.MicBias; if FChkTXMicLineIn <> nil then FChkTXMicLineIn.Checked := T.MicLineIn; if FChkTXMicPTT <> nil then FChkTXMicPTT.Checked := T.MicPTTEnabled; if FEdTXMicGain <> nil then FEdTXMicGain.Value := T.MicGainDB; if FEdTXAttOnTX <> nil then FEdTXAttOnTX.Value := EnsureRange(T.AttOnTX, 0, 31); if FEdTUNLevel <> nil then FEdTUNLevel.Value := EnsureRange(T.TUNLevel, 0, 100); if FEdTUNFreq <> nil then FEdTUNFreq.Value := EnsureRange(T.TUNFreq, 100, 5000); if FEdTXFiltLow <> nil then FEdTXFiltLow.Value := T.FilterLow; if FEdTXFiltHigh <> nil then FEdTXFiltHigh.Value := T.FilterHigh; if FCmbTXFiltNC <> nil then FCmbTXFiltNC.Text := IntToStr(T.FilterNC); if FCmbTXFiltMP <> nil then FCmbTXFiltMP.ItemIndex := EnsureRange(T.FilterMP, 0, FCmbTXFiltMP.Items.Count - 1); if FCmbTXFiltWindow<> nil then FCmbTXFiltWindow.ItemIndex := EnsureRange(T.FilterWindow, 0, FCmbTXFiltWindow.Items.Count - 1); if FChkTXCompOn <> nil then FChkTXCompOn.Checked := T.CompressorOn; if FEdTXCompGain <> nil then FEdTXCompGain.Value := T.CompressorGain; if FChkTXLevOn <> nil then FChkTXLevOn.Checked := T.LevelerOn; if FEdTXLevTop <> nil then FEdTXLevTop.Value := T.LevelerTop; if FEdTXLevDecay <> nil then FEdTXLevDecay.Value := T.LevelerDecay; if FChkTXALCOn <> nil then FChkTXALCOn.Checked := T.ALCOn; if FEdTXALCMaxGain <> nil then FEdTXALCMaxGain.Value := T.ALCMaxGain; if FEdTXALCDecay <> nil then FEdTXALCDecay.Value := T.ALCDecay; if FChkTXPHROn <> nil then FChkTXPHROn.Checked := T.PhaseRotOn; if FEdTXPHRStages <> nil then FEdTXPHRStages.Value := T.PhaseRotStages; if FEdTXPHRFreq <> nil then FEdTXPHRFreq.Value := T.PhaseRotFreq; if FChkTXEQOn <> nil then FChkTXEQOn.Checked := T.EQOn; if FCmbTXEQBands <> nil then if T.EQNumBands <= 3 then FCmbTXEQBands.ItemIndex := 0 else FCmbTXEQBands.ItemIndex := 1; for i := 0 to 10 do begin if FEdTXEQGain[i] <> nil then FEdTXEQGain[i].Value := T.EQGains[i]; if FEdTXEQFreq[i] <> nil then FEdTXEQFreq[i].Value := T.EQFreqs[i]; end; if FEdTXAMCarrier <> nil then FEdTXAMCarrier.Value := T.AMCarrierLevel; if FEdTXFMDev <> nil then FEdTXFMDev.Value := T.FMDeviation; if FEdTXFMLow <> nil then FEdTXFMLow.Value := T.FMLowCut; if FEdTXFMHigh <> nil then FEdTXFMHigh.Value := T.FMHighCut; if FCmbTXFMEmph <> nil then FCmbTXFMEmph.ItemIndex := EnsureRange(T.FMEmphPosition, 0, FCmbTXFMEmph.Items.Count - 1); if FChkTXCTCSSOn <> nil then FChkTXCTCSSOn.Checked := T.CTCSSOn; if FEdTXCTCSSFreq <> nil then FEdTXCTCSSFreq.Value := T.CTCSSFreq; if FCmbTXFFT <> nil then begin FCmbTXFFT.ItemIndex := 2; for i := 0 to High(FFT_SIZES) do if FFT_SIZES[i] = T.TXFFTSize then begin FCmbTXFFT.ItemIndex := i; Break; end; end; if FCmbTXWindow <> nil then FCmbTXWindow.ItemIndex := EnsureRange(T.TXWindowType, 0, FCmbTXWindow.Items.Count - 1); if FCmbTXSpecDet <> nil then FCmbTXSpecDet.ItemIndex := EnsureRange(T.TXSpecDetector, 0, FCmbTXSpecDet.Items.Count - 1); if FCmbTXSpecAvg <> nil then begin FCmbTXSpecAvg.ItemIndex := 3; for i := 0 to High(AVGMODE_VALUES) do if AVGMODE_VALUES[i] = T.TXSpecAvgMode then begin FCmbTXSpecAvg.ItemIndex := i; Break; end; end; if FEdTXSpecAvgTime <> nil then FEdTXSpecAvgTime.Value := T.TXSpecAvgTimeMS; if FCmbTXWfDet <> nil then FCmbTXWfDet.ItemIndex := EnsureRange(T.TXWfDetector, 0, FCmbTXWfDet.Items.Count - 1); if FCmbTXWfAvg <> nil then begin FCmbTXWfAvg.ItemIndex := 3; for i := 0 to High(WF_AVGMODE_VALUES) do if WF_AVGMODE_VALUES[i] = T.TXWfAvgMode then begin FCmbTXWfAvg.ItemIndex := i; Break; end; end; if FEdTXWfAvgTime <> nil then FEdTXWfAvgTime.Value := T.TXWfAvgTimeMS; if FEdTXRefLevel <> nil then FEdTXRefLevel.Text := FormatFloat('0.#', T.TXSpecRefLevel); if FEdTXRange <> nil then FEdTXRange.Text := FormatFloat('0.#', T.TXSpecRange); if FCmbTXGridStep <> nil then begin FCmbTXGridStep.ItemIndex := 1; // дефолт 10 dB for i := 0 to High(GRID_STEPS) do if Abs(GRID_STEPS[i] - T.TXSpecGridStep) < 0.5 then begin FCmbTXGridStep.ItemIndex := i; Break; end; end; finally FLoading := False; end; end; // --------------------------------------------------------------------------- // BuildPATab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildPATab; const MARGIN = 22; LBL_W = 46; ED_W = 72; COL_W = LBL_W + ED_W + 8; COLS = 4; ROW_H = 32; GRP_PAD = 18; R1 = 42; GRP_H = 222; 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_INPUT; E.Font.Color := CLR_INPUT_TEXT; E.Font.Name := UI_FONT; E.Font.Size := 9; 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: TPanel; Lbl: TLabel; Spin: TSpinEdit; i, r, c, X, Y: Integer; begin with TLabel.Create(Self) do begin Parent := FPagePA; Caption := 'Power Amplifier'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPagePA; Caption := 'Output limit and per-band calibration values.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPagePA, 'Power && Calibration', MARGIN, 86, 650, GRP_H); // Max Power Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'Max power (W)'; Lbl.SetBounds(GRP_PAD, R1 + 6, 132, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; Spin := TSpinEdit.Create(Self); Spin.Parent := Grp; Spin.SetBounds(GRP_PAD + 150, R1, ED_W, BTN_H + 2); Spin.Color := CLR_INPUT; Spin.Font.Color := CLR_INPUT_TEXT; Spin.Font.Name := UI_FONT; Spin.Font.Size := 9; Spin.MinValue := 5; Spin.MaxValue := 200; Spin.Increment := 1; Spin.Value := 100; Spin.OnChange := OnMaxPowerChange; FEdMaxPower := Spin; // Calibration label Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'Calibration per band, 38.8 ... 100.0'; Lbl.SetBounds(GRP_PAD, R1 + ROW_H + 12, 320, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; // Band grid 4 columns × 3 rows for i := 0 to 10 do begin r := i div COLS; c := i mod COLS; X := GRP_PAD + c * (COL_W + 4); Y := R1 + ROW_H + 34 + 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 := UI_FONT; Lbl.Font.Size := 9; FEdBandCal[i] := MakeFloatSpin(Grp, X + LBL_W, Y, OnBandCalChange); FEdBandCal[i].Tag := i; end; with TLabel.Create(Self) do begin Parent := FPageAdvanced; Caption := 'Advanced'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageAdvanced; Caption := 'Transmit, CAT and DSP-only settings will appear here when they are wired to runtime settings.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; end; procedure TSettingsForm.ApplyTheme(const T: TAppTheme); procedure StyleBtn(B: TFlatButton); begin B.ClrNorm := T.Panel; B.ClrBorder := T.Panel; B.ClrHot := T.BG; B.ClrActive := T.Panel; B.ClrText := T.Text; B.ClrTextAct := T.FreqActive; B.Invalidate; end; procedure WalkControls(C: TWinControl); var i: Integer; Ctrl: TControl; begin for i := 0 to C.ControlCount - 1 do begin Ctrl := C.Controls[i]; if Ctrl is TFlatButton then StyleBtn(TFlatButton(Ctrl)) else if Ctrl is TScrollBox then begin TScrollBox(Ctrl).Color := T.BG; WalkControls(TScrollBox(Ctrl)); end else if Ctrl is TPanel then begin if TPanel(Ctrl).Height = 1 then TPanel(Ctrl).Color := T.Border else TPanel(Ctrl).Color := T.Panel; WalkControls(TPanel(Ctrl)); end else if Ctrl is TLabel then begin if Ctrl.Font.Size >= 12 then TLabel(Ctrl).Font.Color := T.Text else if fsBold in Ctrl.Font.Style then TLabel(Ctrl).Font.Color := T.FreqActive else TLabel(Ctrl).Font.Color := T.TextDim; end else if Ctrl is TCheckBox then TCheckBox(Ctrl).Font.Color := T.Text else if Ctrl is TListBox then begin TListBox(Ctrl).Color := T.BG; TListBox(Ctrl).Font.Color := T.Text; end else if Ctrl is TWinControl then WalkControls(TWinControl(Ctrl)); end; end; begin Color := T.BG; Font.Color := T.Text; WalkControls(Self); Invalidate; end; // --------------------------------------------------------------------------- // LoadValues // --------------------------------------------------------------------------- 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 Idx := 5; for i := 0 to High(FFT_SIZES) do if FFT_SIZES[i] = FFTSize then begin Idx := i; Break; end; FCmbFFTSize.ItemIndex := Idx; if (WinType >= 0) and (WinType < FCmbWindow.Items.Count) then FCmbWindow.ItemIndex := WinType else FCmbWindow.ItemIndex := 2; if (SpecDet >= 0) and (SpecDet < FCmbSpecDet.Items.Count) then FCmbSpecDet.ItemIndex := SpecDet else FCmbSpecDet.ItemIndex := 0; Idx := 3; 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); if (WfDet >= 0) and (WfDet < FCmbWfDet.Items.Count) then FCmbWfDet.ItemIndex := WfDet else FCmbWfDet.ItemIndex := 0; Idx := 3; 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); FEdRefLevel.Text := FormatFloat('0.#', RefLevel); FEdRange.Text := FormatFloat('0.#', Range); Idx := 1; 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; if AudioOutDevName = '' then FCmbRXDev.ItemIndex := 0 else begin Idx := FCmbRXDev.Items.IndexOf(AudioOutDevName); if Idx >= 0 then FCmbRXDev.ItemIndex := Idx else FCmbRXDev.ItemIndex := 0; end; if AudioInDevName = '' then FCmbTXDev.ItemIndex := 0 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 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; if (FRXDevCount = 0) and (AudioOut.LastError <> '') then FCmbRXDev.Items.Add('PortAudio: ' + AudioOut.LastError); i := FCmbRXDev.Items.IndexOf(OldRXSel); if i >= 0 then FCmbRXDev.ItemIndex := i else FCmbRXDev.ItemIndex := 0; end; 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; if (FTXDevCount = 0) and (AudioIn.LastError <> '') then FCmbTXDev.Items.Add('PortAudio: ' + AudioIn.LastError); 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 FOnAudioDevChange(-1, '') else begin DevName := FCmbRXDev.Items[Idx]; 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 FOnAudioInDevChange(-1, '') 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.OnLightThemeChkChange(Sender: TObject); begin if FLoading then Exit; if FChkLightTheme.Checked then ApplyTheme(LightTheme) else ApplyTheme(DarkTheme); if Assigned(FOnThemeChange) then FOnThemeChange(FChkLightTheme.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 := 8; 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; procedure TSettingsForm.LoadLightTheme(ALight: Boolean); begin FLoading := True; try FChkLightTheme.Checked := ALight; finally FLoading := False; end; if ALight then ApplyTheme(LightTheme) else ApplyTheme(DarkTheme); 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; // --------------------------------------------------------------------------- // BuildCATTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildCATTab; const MARGIN = 22; PAD = 18; LW = 120; CX = PAD + LW + 8; GRP_W = 320; GRP_H = 240; GRP_GAP = 16; CMB_W = 140; EDT_W = 160; R1 = 42; STEP = 32; var i, col, row, gx, gy, j: Integer; Grp: TPanel; Chk: TCheckBox; Ed: TEdit; Cmb: TComboBox; Spin: TSpinEdit; begin with TLabel.Create(Self) do begin Parent := FPageCAT; Caption := 'CAT'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageCAT; Caption := 'Computer Aided Transceiver — serial port and TCP server settings.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; for i := 0 to 3 do begin row := i div 2; col := i mod 2; gx := MARGIN + col * (GRP_W + GRP_GAP); gy := 86 + row * (GRP_H + GRP_GAP); Grp := MakeGroupPanel(FPageCAT, 'Serial Port ' + IntToStr(i + 1), gx, gy, GRP_W, GRP_H); Chk := TCheckBox.Create(Self); Chk.Parent := Grp; Chk.Caption := 'Enable'; Chk.SetBounds(PAD, R1, GRP_W - PAD * 2, 22); Chk.Font.Color := CLR_TEXT; Chk.Font.Name := UI_FONT; Chk.Font.Size := 9; Chk.Tag := i; Chk.OnChange := OnCATSerialChange; FCATSerialEn[i] := Chk; MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW); Ed := TEdit.Create(Self); Ed.Parent := Grp; Ed.SetBounds(CX, R1 + STEP, EDT_W, BTN_H); Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT; Ed.Font.Name := UI_FONT; Ed.Font.Size := 9; Ed.Tag := i; Ed.OnChange := OnCATSerialChange; {$IFDEF MSWINDOWS} Ed.Text := 'COM' + IntToStr(i + 1); {$ELSE} Ed.Text := '/dev/ttyS' + IntToStr(i); {$ENDIF} FCATSerialPort[i] := Ed; MakeLbl(Grp, 'Baud rate:', PAD, R1 + 2 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 2 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; for j := 0 to High(CAT_BAUD_NAMES) do Cmb.Items.Add(CAT_BAUD_NAMES[j]); Cmb.ItemIndex := 3; // 9600 FCATSerialBaud[i] := Cmb; MakeLbl(Grp, 'Data bits:', PAD, R1 + 3 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 3 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('7'); Cmb.Items.Add('8'); Cmb.ItemIndex := 1; // 8 FCATSerialDataBits[i] := Cmb; MakeLbl(Grp, 'Stop bits:', PAD, R1 + 4 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 4 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('1'); Cmb.Items.Add('2'); Cmb.ItemIndex := 0; // 1 FCATSerialStopBits[i] := Cmb; MakeLbl(Grp, 'Parity:', PAD, R1 + 5 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 5 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('None'); Cmb.Items.Add('Odd'); Cmb.Items.Add('Even'); Cmb.ItemIndex := 0; // None FCATSerialParity[i] := Cmb; end; // TCP server group below the two rows of serial port panels gy := 86 + 2 * (GRP_H + GRP_GAP); Grp := MakeGroupPanel(FPageCAT, 'TCP CAT Server', MARGIN, gy, GRP_W * 2 + GRP_GAP, 116); FCATTcpEn := TCheckBox.Create(Self); FCATTcpEn.Parent := Grp; FCATTcpEn.Caption := 'Enable TCP server (default port 19090)'; FCATTcpEn.SetBounds(PAD, R1, 300, 22); FCATTcpEn.Font.Color := CLR_TEXT; FCATTcpEn.Font.Name := UI_FONT; FCATTcpEn.Font.Size := 9; FCATTcpEn.OnChange := OnCATTcpChange; MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW); Spin := TSpinEdit.Create(Self); Spin.Parent := Grp; Spin.SetBounds(CX, R1 + STEP, 100, BTN_H + 2); Spin.Color := CLR_INPUT; Spin.Font.Color := CLR_INPUT_TEXT; Spin.Font.Name := UI_FONT; Spin.Font.Size := 9; Spin.MinValue := 1; Spin.MaxValue := 65535; Spin.Value := 19090; Spin.OnChange := OnCATTcpChange; FCATTcpPort := Spin; end; // --------------------------------------------------------------------------- // CAT event handlers // --------------------------------------------------------------------------- procedure TSettingsForm.FireCATChange; var SerEn: array[0..3] of Boolean; SerPort: array[0..3] of string; SerBaud, SerData, SerStop, SerPar: array[0..3] of Integer; i, Idx: Integer; begin if FLoading then Exit; if not Assigned(FOnCATChange) then Exit; for i := 0 to 3 do begin SerEn[i] := FCATSerialEn[i].Checked; SerPort[i] := FCATSerialPort[i].Text; Idx := FCATSerialBaud[i].ItemIndex; if (Idx >= 0) and (Idx <= High(CAT_BAUD_VALS)) then SerBaud[i] := CAT_BAUD_VALS[Idx] else SerBaud[i] := 9600; if FCATSerialDataBits[i].ItemIndex = 0 then SerData[i] := 7 else SerData[i] := 8; if FCATSerialStopBits[i].ItemIndex = 1 then SerStop[i] := 2 else SerStop[i] := 1; SerPar[i] := FCATSerialParity[i].ItemIndex; end; FOnCATChange(SerEn, SerPort, SerBaud, SerData, SerStop, SerPar, FCATTcpEn.Checked, FCATTcpPort.Value); end; procedure TSettingsForm.OnCATSerialChange(Sender: TObject); begin FireCATChange; end; procedure TSettingsForm.OnCATTcpChange(Sender: TObject); begin FireCATChange; end; // --------------------------------------------------------------------------- // LoadCATSettings // --------------------------------------------------------------------------- procedure TSettingsForm.LoadCATSettings( const SerEnabled: array of Boolean; const SerPort: array of string; const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer; TcpEnabled: Boolean; TcpPort: Integer); var i, j, Idx: Integer; begin FLoading := True; try for i := 0 to 3 do begin if i <= High(SerEnabled) then FCATSerialEn[i].Checked := SerEnabled[i]; if i <= High(SerPort) then FCATSerialPort[i].Text := SerPort[i]; if i <= High(SerBaud) then begin Idx := 3; for j := 0 to High(CAT_BAUD_VALS) do if CAT_BAUD_VALS[j] = SerBaud[i] then begin Idx := j; Break; end; FCATSerialBaud[i].ItemIndex := Idx; end; if i <= High(SerDataBits) then begin if SerDataBits[i] = 7 then FCATSerialDataBits[i].ItemIndex := 0 else FCATSerialDataBits[i].ItemIndex := 1; end; if i <= High(SerStopBits) then begin if SerStopBits[i] = 2 then FCATSerialStopBits[i].ItemIndex := 1 else FCATSerialStopBits[i].ItemIndex := 0; end; if i <= High(SerParity) then FCATSerialParity[i].ItemIndex := EnsureRange(SerParity[i], 0, 2); end; FCATTcpEn.Checked := TcpEnabled; FCATTcpPort.Value := EnsureRange(TcpPort, 1, 65535); finally FLoading := False; end; end; // --------------------------------------------------------------------------- // BuildAlexTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildAlexTab; const MARGIN = 22; PAD = 12; HDR1_H = 22; // height of "RX Ant / TX Ant" header row HDR2_H = 20; // height of "1 2 3 / BYPS EXT1 XVTR" sub-header row ROW_H = 24; BANDS = 11; BAND_X = PAD; RX1_X = PAD + 50; RX2_X = PAD + 76; RX3_X = PAD + 102; BYPS_X = PAD + 136; EXT1_X = PAD + 192; XVTR_X = PAD + 248; TX1_X = PAD + 308; TX2_X = PAD + 334; TX3_X = PAD + 360; DNTX_X = PAD + 398; GRP_W = DNTX_X + 80; GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + BANDS * ROW_H + PAD; DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4; TXGRP_H = GRP_TITLE_H + 4 * 28 + PAD; BAND_NAMES: array[0..10] of string = ('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m'); var Grp, GrpTX: TPanel; b: Integer; Y: Integer; Chk: TCheckBox; RxGrp, TxGrp: TPanel; // In Lazarus, TRadioButton has no GroupIndex; buttons are grouped by parent. // We use small invisible panels as parents so each band's RX and TX groups // are independent of each other. function MkRbGroup(AParent: TWinControl; AX, AY, AW: Integer): TPanel; begin Result := TPanel.Create(Self); Result.Parent := AParent; Result.SetBounds(AX, AY, AW, 20); Result.BevelOuter := bvNone; Result.Color := CLR_PANEL; Result.Caption := ''; end; function MkRb(AParent: TWinControl; AX: Integer): TRadioButton; begin Result := TRadioButton.Create(Self); Result.Parent := AParent; Result.Caption := ''; Result.SetBounds(AX, 0, 22, 20); Result.Font.Color := CLR_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnAlexAnyChange; end; function MkCh(AParent: TWinControl; const ACap: string; AX, AY, AW: Integer): TCheckBox; begin Result := TCheckBox.Create(Self); Result.Parent := AParent; Result.Caption := ACap; Result.SetBounds(AX, AY, AW, 20); Result.Font.Color := CLR_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnAlexAnyChange; end; begin FLoading := True; try with TLabel.Create(Self) do begin Parent := FPageAlex; Caption := 'Antenna / Alex'; SetBounds(MARGIN, 18, 500, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageAlex; Caption := 'Per-band RX/TX antenna and secondary path routing for the Alex filter board.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; // ---- Antenna grid group ---- Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H); // Header row 1 — group labels MakeLbl(Grp, 'Band', BAND_X, GRP_TITLE_H + 2, 44); MakeLbl(Grp, 'RX Antenna', RX1_X, GRP_TITLE_H + 2, 80); MakeLbl(Grp, 'Secondary RX', BYPS_X, GRP_TITLE_H + 2, 170); MakeLbl(Grp, 'TX Antenna', TX1_X, GRP_TITLE_H + 2, 80); MakeLbl(Grp, 'No TX', DNTX_X, GRP_TITLE_H + 2, 56); // Header row 2 — sub-column labels MakeLbl(Grp, '1', RX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); MakeLbl(Grp, '2', RX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); MakeLbl(Grp, '3', RX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); MakeLbl(Grp, 'BYPS', BYPS_X, GRP_TITLE_H + HDR1_H + 2, 52); MakeLbl(Grp, 'EXT1', EXT1_X, GRP_TITLE_H + HDR1_H + 2, 52); MakeLbl(Grp, 'XVTR', XVTR_X, GRP_TITLE_H + HDR1_H + 2, 52); MakeLbl(Grp, '1', TX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); MakeLbl(Grp, '2', TX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); MakeLbl(Grp, '3', TX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20); // Data rows (bands 0..10 = 160m..6m) for b := 0 to BANDS - 1 do begin Y := DATA_Y0 + b * ROW_H; MakeLbl(Grp, BAND_NAMES[b], BAND_X, Y + 2, 44); // RX antenna — 3 radio buttons in a dedicated panel so they are mutually // exclusive only within that band row (Lazarus groups by parent, not GroupIndex) RxGrp := MkRbGroup(Grp, RX1_X, Y, RX3_X - RX1_X + 26); FAlexRxAnt[b, 1] := MkRb(RxGrp, 0); FAlexRxAnt[b, 2] := MkRb(RxGrp, RX2_X - RX1_X); FAlexRxAnt[b, 3] := MkRb(RxGrp, RX3_X - RX1_X); FAlexRxAnt[b, 1].Checked := True; // Secondary RX checkboxes — mutually exclusive, handled in OnAlexRxOnlyChange // Tag encodes: b=BYPS, b+100=EXT1, b+200=XVTR Chk := MkCh(Grp, '', BYPS_X, Y, 22); Chk.Tag := b; Chk.OnChange := OnAlexRxOnlyChange; FAlexRxByps[b] := Chk; Chk := MkCh(Grp, '', EXT1_X, Y, 22); Chk.Tag := b + 100; Chk.OnChange := OnAlexRxOnlyChange; FAlexRxExt1[b] := Chk; Chk := MkCh(Grp, '', XVTR_X, Y, 22); Chk.Tag := b + 200; Chk.OnChange := OnAlexRxOnlyChange; FAlexRxXvtr[b] := Chk; // TX antenna — same pattern: dedicated panel per band row TxGrp := MkRbGroup(Grp, TX1_X, Y, TX3_X - TX1_X + 26); FAlexTxAnt[b, 1] := MkRb(TxGrp, 0); FAlexTxAnt[b, 2] := MkRb(TxGrp, TX2_X - TX1_X); FAlexTxAnt[b, 3] := MkRb(TxGrp, TX3_X - TX1_X); FAlexTxAnt[b, 1].Checked := True; // Do Not TX checkbox FAlexDoNotTx[b] := MkCh(Grp, '', DNTX_X, Y, 22); end; // ---- TX Routing flags group ---- GrpTX := MakeGroupPanel(FPageAlex, 'TX Routing', MARGIN, 86 + GRP_H + 12, 480, TXGRP_H); FAlexRxBypassOnTx := MkCh(GrpTX, 'Rx BYPASS on TX', PAD, GRP_TITLE_H + 8, 440); FAlexExt1OnTx := MkCh(GrpTX, 'Ext 1 on TX', PAD, GRP_TITLE_H + 8 + 28, 440); FAlexGndBpf2OnTx := MkCh(GrpTX, 'Ground BPF2 on TX (ANAN-8000DLE)', PAD, GRP_TITLE_H + 8 + 56, 440); FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF', PAD, GRP_TITLE_H + 8 + 84, 440); finally FLoading := False; end; end; // --------------------------------------------------------------------------- // Alex event handlers and data collectors // --------------------------------------------------------------------------- procedure TSettingsForm.CollectAlexFromUI; var b, a: Integer; begin for b := 0 to 10 do begin FAlexConfig.RxAnt[b] := 1; for a := 1 to 3 do if (FAlexRxAnt[b, a] <> nil) and FAlexRxAnt[b, a].Checked then begin FAlexConfig.RxAnt[b] := a; Break; end; FAlexConfig.TxAnt[b] := 1; for a := 1 to 3 do if (FAlexTxAnt[b, a] <> nil) and FAlexTxAnt[b, a].Checked then begin FAlexConfig.TxAnt[b] := a; Break; end; if (FAlexRxXvtr[b] <> nil) and FAlexRxXvtr[b].Checked then FAlexConfig.RxOnly[b] := 3 else if (FAlexRxExt1[b] <> nil) and FAlexRxExt1[b].Checked then FAlexConfig.RxOnly[b] := 2 else if (FAlexRxByps[b] <> nil) and FAlexRxByps[b].Checked then FAlexConfig.RxOnly[b] := 1 else FAlexConfig.RxOnly[b] := 0; FAlexConfig.DoNotTx[b] := (FAlexDoNotTx[b] <> nil) and FAlexDoNotTx[b].Checked; end; FAlexConfig.RxBypassOnTx := (FAlexRxBypassOnTx <> nil) and FAlexRxBypassOnTx.Checked; FAlexConfig.Ext1OnTx := (FAlexExt1OnTx <> nil) and FAlexExt1OnTx.Checked; FAlexConfig.GndBpf2OnTx := (FAlexGndBpf2OnTx <> nil) and FAlexGndBpf2OnTx.Checked; FAlexConfig.EnableXvtrHf := (FAlexEnableXvtrHf <> nil) and FAlexEnableXvtrHf.Checked; end; procedure TSettingsForm.FireAlexChange; begin if FLoading then Exit; CollectAlexFromUI; if Assigned(FOnAlexChange) then FOnAlexChange(FAlexConfig); end; procedure TSettingsForm.OnAlexAnyChange(Sender: TObject); begin FireAlexChange; end; procedure TSettingsForm.OnAlexRxOnlyChange(Sender: TObject); var Tag, b: Integer; begin if FLoading then Exit; Tag := TCheckBox(Sender).Tag; if Tag >= 200 then begin b := Tag - 200; // XVTR if TCheckBox(Sender).Checked then begin FLoading := True; FAlexRxByps[b].Checked := False; FAlexRxExt1[b].Checked := False; FLoading := False; end; end else if Tag >= 100 then begin b := Tag - 100; // EXT1 if TCheckBox(Sender).Checked then begin FLoading := True; FAlexRxByps[b].Checked := False; FAlexRxXvtr[b].Checked := False; FLoading := False; end; end else begin b := Tag; // BYPS if TCheckBox(Sender).Checked then begin FLoading := True; FAlexRxExt1[b].Checked := False; FAlexRxXvtr[b].Checked := False; FLoading := False; end; end; FireAlexChange; end; procedure TSettingsForm.LoadAlexSettings(const A: TAlexSettings; BoardType: Integer); var b, ant: Integer; begin FLoading := True; try FAlexConfig := A; FAlexBoardType := BoardType; for b := 0 to 10 do begin for ant := 1 to 3 do if FAlexRxAnt[b, ant] <> nil then FAlexRxAnt[b, ant].Checked := (A.RxAnt[b] = ant); for ant := 1 to 3 do if FAlexTxAnt[b, ant] <> nil then FAlexTxAnt[b, ant].Checked := (A.TxAnt[b] = ant); if FAlexRxByps[b] <> nil then FAlexRxByps[b].Checked := (A.RxOnly[b] = 1); if FAlexRxExt1[b] <> nil then FAlexRxExt1[b].Checked := (A.RxOnly[b] = 2); if FAlexRxXvtr[b] <> nil then FAlexRxXvtr[b].Checked := (A.RxOnly[b] = 3); if FAlexDoNotTx[b] <> nil then FAlexDoNotTx[b].Checked := A.DoNotTx[b]; end; if FAlexRxBypassOnTx <> nil then FAlexRxBypassOnTx.Checked := A.RxBypassOnTx; if FAlexExt1OnTx <> nil then FAlexExt1OnTx.Checked := A.Ext1OnTx; if FAlexGndBpf2OnTx <> nil then FAlexGndBpf2OnTx.Checked := A.GndBpf2OnTx; if FAlexEnableXvtrHf <> nil then FAlexEnableXvtrHf.Checked := A.EnableXvtrHf; finally FLoading := False; 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.