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, FlatButton, FlatCheckBox, FlatComboBox, FlatEdit, FlatSpinEdit, FlatFloatSpinEdit, FlatRadioButton, EqualizerControl, 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; // VHF (XVTR) PA calibration: 8 slots, 38.8..100.0 TOnVHFCalChange = procedure(const VHFCal: array of Double) of object; // TX-настройки целиком (любая правка → один callback с актуальной структурой) TOnTXSettingsChange = procedure(const T: TTXSettings) of object; TOnFreqMhzDigitsChange = procedure(Digits: Integer) 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; TOnAudioBufferChange = procedure(BufferSize: Integer) of object; TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill: Boolean) of object; TOnFPSChange = procedure(FPS: Integer) of object; TOnThemeChange = procedure(LightTheme: Boolean) of object; TOnWfAGCNFChange = procedure(WfAGC, WfNF: Boolean) of object; TOnADCChange = procedure(Dither, Random: Boolean) of object; TOnWebSettingsChange = procedure(Enabled: Boolean; Port: Integer; const BindAddr, User, Pass: string) 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: TFlatComboBox; FRXDevIndices: array[0..63] of Integer; FRXDevCount: Integer; FCmbTXDev: TFlatComboBox; FTXDevIndices: array[0..63] of Integer; FTXDevCount: Integer; FCmbAudioBuffer: TFlatComboBox; // ---- RX1 sub-tab controls ---- FCmbFFTSize: TFlatComboBox; FCmbWindow: TFlatComboBox; FCmbSpecDet: TFlatComboBox; FCmbSpecAvg: TFlatComboBox; FSpecAvgTime: TFlatFloatSpinEdit; FCmbWfDet: TFlatComboBox; FCmbWfAvg: TFlatComboBox; FWfAvgTime: TFlatFloatSpinEdit; FWfHigh: TFlatFloatSpinEdit; FWfLow: TFlatFloatSpinEdit; FWfAGCOffset: TFlatFloatSpinEdit; FChkWfAGC: TFlatCheckBox; FChkWfNF: TFlatCheckBox; FEdRefLevel: TFlatEdit; FEdRange: TFlatEdit; FCmbGridStep: TFlatComboBox; // ---- General display tab controls ---- FChkShowSpectrum: TFlatCheckBox; FChkShowWaterfall: TFlatCheckBox; FChkShowWideband: TFlatCheckBox; FChkWidebandFill: TFlatCheckBox; FCmbFPS: TFlatComboBox; FChkLightTheme: TFlatCheckBox; FCmbFreqMhzDigits: TFlatComboBox; // ---- PA Settings tab controls ---- FEdMaxPower: TFlatSpinEdit; FEdBandCal: array[0..10] of TFlatFloatSpinEdit; FEdVHFCal: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; FVHFCalLabels: array[0..CFG_XVTR_COUNT-1] of TLabel; // ---- Transmit tab controls ---- // Текущее состояние, отдаётся в OnTXChange целиком при любой правке. FTX: TTXSettings; FCmbTXMicJack: TFlatComboBox; // "Mic In" / "Line In" FChkTXMicBoost: TFlatCheckBox; // +20 dB (только Mic In) FLblLineInGain: TLabel; // метка "Line In gain" (только Line In) FEdLineInGain: TFlatFloatSpinEdit; // -34.5..+12 dB, шаг 1.5 (только Line In) FChkTXMicBias: TFlatCheckBox; FChkTXMicPTT: TFlatCheckBox; FCmbTXMicTR: TFlatComboBox; // "Tip" / "Ring" (только Orion) FLblTipRing: TLabel; // метка (только Orion) FEdTXMicGain: TFlatFloatSpinEdit; FEdTXAttOnTX: TFlatSpinEdit; // Tune (TUN button) FEdTUNLevel: TFlatSpinEdit; FEdTUNFreq: TFlatSpinEdit; // Filter FEdTXFiltLow: TFlatSpinEdit; FEdTXFiltHigh: TFlatSpinEdit; FCmbTXFiltNC: TFlatComboBox; FCmbTXFiltMP: TFlatComboBox; FCmbTXFiltWindow: TFlatComboBox; // Compressor FChkTXCompOn: TFlatCheckBox; FEdTXCompGain: TFlatFloatSpinEdit; // Leveler FChkTXLevOn: TFlatCheckBox; FEdTXLevTop: TFlatFloatSpinEdit; FEdTXLevDecay: TFlatSpinEdit; // ALC FChkTXALCOn: TFlatCheckBox; FEdTXALCMaxGain: TFlatFloatSpinEdit; FEdTXALCDecay: TFlatSpinEdit; // Phase Rotator FChkTXPHROn: TFlatCheckBox; FEdTXPHRStages: TFlatSpinEdit; FEdTXPHRFreq: TFlatFloatSpinEdit; // EQ FTXEQControl: TEqualizerControl; // AM FEdTXAMCarrier: TFlatFloatSpinEdit; // FM FEdTXFMDev: TFlatFloatSpinEdit; FEdTXFMLow: TFlatSpinEdit; FEdTXFMHigh: TFlatSpinEdit; FCmbTXFMEmph: TFlatComboBox; // CTCSS FChkTXCTCSSOn: TFlatCheckBox; FEdTXCTCSSFreq: TFlatFloatSpinEdit; // TX Display (отдельный analyzer) FCmbTXFFT: TFlatComboBox; FCmbTXWindow: TFlatComboBox; FCmbTXSpecDet: TFlatComboBox; FCmbTXSpecAvg: TFlatComboBox; FEdTXSpecAvgTime: TFlatFloatSpinEdit; FCmbTXWfDet: TFlatComboBox; FCmbTXWfAvg: TFlatComboBox; FEdTXWfAvgTime: TFlatFloatSpinEdit; // TX Grid (отдельные от RX grid в Display) FEdTXRefLevel: TFlatEdit; FEdTXRange: TFlatEdit; FCmbTXGridStep: TFlatComboBox; // ---- 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 TFlatRadioButton; FAlexTxAnt: array[0..10, 1..3] of TFlatRadioButton; // Per-band secondary RX (взаимно исключающие checkboxes) FAlexRxByps: array[0..10] of TFlatCheckBox; // BYPS: Ext1+Bypass FAlexRxExt1: array[0..10] of TFlatCheckBox; // EXT1: Ext2+Bypass FAlexRxXvtr: array[0..10] of TFlatCheckBox; // XVTR: XvtrDDC+Bypass // Per-band Do Not TX FAlexDoNotTx: array[0..10] of TFlatCheckBox; // Global TX routing flags FAlexRxBypassOnTx: TFlatCheckBox; FAlexExt1OnTx: TFlatCheckBox; FAlexGndBpf2OnTx: TFlatCheckBox; FAlexEnableXvtrHf: TFlatCheckBox; // 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 TFlatCheckBox; FXvtrName: array[0..CFG_XVTR_COUNT-1] of TFlatEdit; FXvtrBegin: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // MHz FXvtrEnd: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // MHz FXvtrLOOff: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // MHz FXvtrLOErr: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // Hz (целые) FXvtrRXGain: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // dB FXvtrRXOnly: array[0..CFG_XVTR_COUNT-1] of TFlatCheckBox; FXvtrPower: array[0..CFG_XVTR_COUNT-1] of TFlatSpinEdit; // 0..100 FXvtrDisPA: array[0..CFG_XVTR_COUNT-1] of TFlatCheckBox; FXvtrRXAnt: array[0..CFG_XVTR_COUNT-1] of TFlatComboBox; // 0=def..3=ANT3 FChkXvtrTunPwr: TFlatCheckBox; FXvtrConfig: TXvtrSettings; FOnXvtrChange: TOnXvtrChange; // ---- CAT tab controls ---- FCATSerialEn: array[0..3] of TFlatCheckBox; FCATSerialPort: array[0..3] of TFlatEdit; FCATSerialBaud: array[0..3] of TFlatComboBox; FCATSerialDataBits: array[0..3] of TFlatComboBox; FCATSerialStopBits: array[0..3] of TFlatComboBox; FCATSerialParity: array[0..3] of TFlatComboBox; FCATTcpEn: TFlatCheckBox; FCATTcpPort: TFlatSpinEdit; // ---- Advanced tab controls ---- FChkDither: TFlatCheckBox; FChkRandom: TFlatCheckBox; FChkWebEnabled: TFlatCheckBox; FEdWebPort: TFlatSpinEdit; FEdWebBind: TFlatEdit; FEdWebUser: TFlatEdit; FEdWebPass: TFlatEdit; // ---- Close button ---- FBtnClose: TFlatButton; // ---- Callbacks ---- FOnCATChange: TOnCATChange; FOnPAChange: TOnPASettingsChange; FOnVHFCalChange: TOnVHFCalChange; FOnADCChange: TOnADCChange; FOnWebSettingsChange: TOnWebSettingsChange; FOnDisplayChange: TOnDisplayParamChange; FOnWaterfallChange: TOnWaterfallParamChange; FOnGridChange: TOnGridParamChange; FOnAudioDevChange: TOnAudioDevChange; FOnAudioInDevChange: TOnAudioInDevChange; FOnAudioBufferChange: TOnAudioBufferChange; FOnVisibilityChange: TOnVisibilityChange; FOnFPSChange: TOnFPSChange; FOnThemeChange: TOnThemeChange; FOnWfAGCNFChange: TOnWfAGCNFChange; FOnFreqMhzDigitsChange: TOnFreqMhzDigitsChange; FOnTXChange: TOnTXSettingsChange; FLoading: Boolean; procedure BuildUI; procedure BuildAudioTab; procedure BuildDisplayTab; procedure BuildRX1Tab; procedure BuildPATab; procedure BuildCATTab; procedure BuildTransmitTab; procedure BuildAlexTab; procedure BuildAdvancedTab; procedure OnADCChkChange(Sender: TObject); procedure OnWebAnyChange(Sender: TObject); 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 OnMicJackChange(Sender: TObject); procedure UpdateMicJackVisibility; 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): TFlatComboBox; 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 OnWfAGCNFChkChange(Sender: TObject); procedure OnRefLevelChange(Sender: TObject); procedure OnRangeChange(Sender: TObject); procedure OnGridStepChange(Sender: TObject); procedure OnRXDevChange(Sender: TObject); procedure OnTXDevChange(Sender: TObject); procedure OnAudioBufferCmbChange(Sender: TObject); procedure OnVisibilityChkChange(Sender: TObject); procedure OnFPSCmbChange(Sender: TObject); procedure OnLightThemeChkChange(Sender: TObject); procedure OnFreqMhzDigitsCmbChange(Sender: TObject); procedure OnMaxPowerChange(Sender: TObject); procedure OnBandCalChange(Sender: TObject); procedure OnVHFCalEdit(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 LoadAudioBufferSize(BufferSize: Integer); procedure LoadVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill: Boolean); procedure LoadFPS(FPS: Integer); procedure LoadLightTheme(ALight: Boolean); procedure LoadFreqMhzDigits(Digits: Integer); procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double); procedure LoadVHFCalSettings(const VHFCal: 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; BoardType: Integer = 0); procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer); procedure LoadXvtrSettings(const X: TXvtrSettings); procedure LoadWfAGCNF(AGC, NF: Boolean); procedure LoadADCSettings(Dither, Random: Boolean); procedure LoadWebSettings(Enabled: Boolean; Port: Integer; const BindAddr, User, Pass: string); 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 OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange; 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 OnAudioBufferChange: TOnAudioBufferChange read FOnAudioBufferChange write FOnAudioBufferChange; property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange; property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange; property OnThemeChange: TOnThemeChange read FOnThemeChange write FOnThemeChange; property OnFreqMhzDigitsChange: TOnFreqMhzDigitsChange read FOnFreqMhzDigitsChange write FOnFreqMhzDigitsChange; property OnTXChange: TOnTXSettingsChange read FOnTXChange write FOnTXChange; property OnWfAGCNFChange: TOnWfAGCNFChange read FOnWfAGCNFChange write FOnWfAGCNFChange; property OnADCChange: TOnADCChange read FOnADCChange write FOnADCChange; property OnWebSettingsChange: TOnWebSettingsChange read FOnWebSettingsChange write FOnWebSettingsChange; 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): TFlatComboBox; begin Result := TFlatComboBox.Create(Self); Result.Parent := AParent; Result.Left := ALeft; Result.Top := ATop; Result.Width := AW; Result.Height := BTN_H; 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; BuildAdvancedTab; 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, 184); 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'); MakeLbl(Grp, 'Output buffer', PAD, R1 + STEP * 2 + 5, LW); FCmbAudioBuffer := MakeCombo(Grp, CX, R1 + STEP * 2, 160, OnAudioBufferCmbChange); FCmbAudioBuffer.Items.Add('128 frames'); FCmbAudioBuffer.Items.Add('256 frames'); FCmbAudioBuffer.Items.Add('512 frames'); FCmbAudioBuffer.ItemIndex := 0; 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, 112); FChkShowSpectrum := TFlatCheckBox.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 := TFlatCheckBox.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; FChkShowWideband := TFlatCheckBox.Create(Self); FChkShowWideband.Parent := Grp; FChkShowWideband.Caption := 'Wideband'; FChkShowWideband.SetBounds(PAD + (CHKW + 20) * 2, R1, CHKW, 22); FChkShowWideband.Checked := False; FChkShowWideband.Font.Color := CLR_TEXT; FChkShowWideband.Font.Name := UI_FONT; FChkShowWideband.Font.Size := 9; FChkShowWideband.OnChange := OnVisibilityChkChange; FChkWidebandFill := TFlatCheckBox.Create(Self); FChkWidebandFill.Parent := Grp; FChkWidebandFill.Caption := 'Wideband gradient fill'; FChkWidebandFill.SetBounds(PAD + (CHKW + 20) * 2, R1 + 28, CHKW, 22); FChkWidebandFill.Checked := False; FChkWidebandFill.Font.Color := CLR_TEXT; FChkWidebandFill.Font.Name := UI_FONT; FChkWidebandFill.Font.Size := 9; FChkWidebandFill.OnChange := OnVisibilityChkChange; Grp := MakeGroupPanel(FPageDisplay, 'Performance', MARGIN, 212, 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, 318, 650, 68); FChkLightTheme := TFlatCheckBox.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; Grp := MakeGroupPanel(FPageDisplay, 'VFO Display', MARGIN, 404, 650, 92); MakeLbl(Grp, 'Frequency range', PAD, R1 + 5, LW); FCmbFreqMhzDigits := MakeCombo(Grp, CX, R1, 240, OnFreqMhzDigitsCmbChange); FCmbFreqMhzDigits.Items.Add('up to 999 MHz (3 digits)'); FCmbFreqMhzDigits.Items.Add('up to 9.999 GHz (4 digits)'); FCmbFreqMhzDigits.Items.Add('up to 99.999 GHz (5 digits)'); FCmbFreqMhzDigits.ItemIndex := 0; 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): TFlatFloatSpinEdit; begin Result := TFlatFloatSpinEdit.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): TFlatEdit; begin Result := TFlatEdit.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); Grp := MakeGroupPanel(FPageWaterfall, 'AGC && Noise Floor', MARGIN, 458, 650, 140); FChkWfAGC := TFlatCheckBox.Create(Self); FChkWfAGC.Parent := Grp; FChkWfAGC.Caption := 'Waterfall AGC'; FChkWfAGC.SetBounds(PAD, R1, 200, 22); FChkWfAGC.Checked := True; FChkWfAGC.Font.Color := CLR_TEXT; FChkWfAGC.Font.Name := UI_FONT; FChkWfAGC.Font.Size := 9; FChkWfAGC.OnChange := OnWfAGCNFChkChange; FChkWfNF := TFlatCheckBox.Create(Self); FChkWfNF.Parent := Grp; FChkWfNF.Caption := 'Waterfall NF'; FChkWfNF.SetBounds(PAD, R1 + STEP, 200, 22); FChkWfNF.Checked := False; FChkWfNF.Font.Color := CLR_TEXT; FChkWfNF.Font.Name := UI_FONT; FChkWfNF.Font.Size := 9; FChkWfNF.OnChange := OnWfAGCNFChkChange; end; // --------------------------------------------------------------------------- // BuildTransmitTab — все настройки TX-тракта (по образцу Thetis Setup→Transmit) // --------------------------------------------------------------------------- procedure TSettingsForm.BuildTransmitTab; const MARGIN = 22; PAD = 18; LW = 150; CX = PAD + LW + 12; STEP = 42; R1 = 42; SPW = 96; GRP_W = 760; var Grp: TPanel; Y, i: Integer; function MkSpin(AParent: TWinControl; AX, AY: Integer; AMin, AMax, AVal: Integer): TFlatSpinEdit; begin Result := TFlatSpinEdit.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): TFlatFloatSpinEdit; begin Result := TFlatFloatSpinEdit.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): TFlatCheckBox; begin Result := TFlatCheckBox.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): TFlatComboBox; 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 options ---- // Mic source (Radio vs SoundCard) определяется автоматически по источнику PTT. Y := 86; Grp := MakeGroupPanel(FPageTransmit, 'Microphone', MARGIN, Y, GRP_W, 188); // Row 0: Mic jack selector | Boost (Mic In) или LineInGain (Line In) | Bias | PTT MakeLbl(Grp, 'Mic jack', PAD, R1 + 5, 65); FCmbTXMicJack := MkCmb(Grp, PAD + 68, R1, 110); FCmbTXMicJack.Items.Add('Mic In'); FCmbTXMicJack.Items.Add('Line In'); FCmbTXMicJack.ItemIndex := 0; FCmbTXMicJack.OnChange := OnMicJackChange; FChkTXMicBoost := MkChk(Grp, PAD + 190, R1, 165, 'Mic boost +20 dB'); FLblLineInGain := MakeLbl(Grp, 'Line In gain', PAD + 190, R1 + 5, 80); FEdLineInGain := MkFloat(Grp, PAD + 190 + 82, R1, 0.0, -34.5, 12.0, 1.5); FEdLineInGain.DecimalPlaces := 1; FChkTXMicBias := MkChk(Grp, PAD + 375, R1, 105, 'Mic bias'); FChkTXMicPTT := MkChk(Grp, PAD + 490, R1, 160, 'Mic-jack PTT'); // Row 1: Connector Tip/Ring (только Orion boards 4 и 5) FLblTipRing := MakeLbl(Grp, 'Connector', PAD, R1 + STEP + 5, 70); FCmbTXMicTR := MkCmb(Grp, PAD + 73, R1 + STEP, 90); FCmbTXMicTR.Items.Add('Tip'); // MicTipRing=False, bit3=0 FCmbTXMicTR.Items.Add('Ring'); // MicTipRing=True, bit3=1 FCmbTXMicTR.ItemIndex := 0; FLblTipRing.Visible := False; FCmbTXMicTR.Visible := False; // Row 2: Mic gain | ATT on TX MakeLbl(Grp, 'Mic gain (dB)', PAD, R1 + 2 * STEP + 5, LW); FEdTXMicGain := MkFloat(Grp, CX, R1 + 2 * STEP, 10.0, -20.0, 60.0, 0.5); MakeLbl(Grp, 'ATT on TX (dB)', PAD + 320, R1 + 2 * STEP + 5, 110); FEdTXAttOnTX := MkSpin(Grp, PAD + 320 + 120, R1 + 2 * STEP, 0, 31, 31); // ---- Tune (TUN button) ---- Inc(Y, 188 + 12); Grp := MakeGroupPanel(FPageTransmit, 'Tune (TUN)', MARGIN, Y, GRP_W, 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, GRP_W, 220); 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, 220 + 12); Grp := MakeGroupPanel(FPageTransmit, 'Dynamic processing', MARGIN, Y, GRP_W, 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, GRP_W, 390); FTXEQControl := TEqualizerControl.Create(Self); FTXEQControl.Parent := Grp; FTXEQControl.SetBounds(PAD, R1, GRP_W - 2 * PAD, 326); FTXEQControl.Font.Name := UI_FONT; FTXEQControl.Font.Size := 9; FTXEQControl.SetThemeColors(CLR_PANEL, TColor($00202020), CLR_BORDER, TColor($00383838), CLR_TEXT, CLR_TEXTDIM, CLR_ACCENT, TColor($0055DDEE)); FTXEQControl.OnChange := OnTXAnyChange; // ---- AM / FM / CTCSS ---- Inc(Y, 390 + 12); Grp := MakeGroupPanel(FPageTransmit, 'AM / FM / CTCSS', MARGIN, Y, GRP_W, 260); 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, 260 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Display (Analyzer)', MARGIN, Y, GRP_W, 318); 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, 318 + 12); Grp := MakeGroupPanel(FPageTransmit, 'TX Grid', MARGIN, Y, GRP_W, 170); MakeLbl(Grp, 'Reference level', PAD, R1 + 5, LW); FEdTXRefLevel := TFlatEdit.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 := TFlatEdit.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 FCmbTXMicJack <> nil then FTX.MicLineIn := FCmbTXMicJack.ItemIndex = 1; if FChkTXMicBoost <> nil then FTX.MicBoost := FChkTXMicBoost.Checked; if FEdLineInGain <> nil then FTX.LineInGainDB := FEdLineInGain.Value; if FChkTXMicBias <> nil then FTX.MicBias := FChkTXMicBias.Checked; if FChkTXMicPTT <> nil then FTX.MicPTTEnabled := FChkTXMicPTT.Checked; if FCmbTXMicTR <> nil then FTX.MicTipRing := FCmbTXMicTR.ItemIndex = 1; 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 FTXEQControl <> nil then begin FTX.EQOn := FTXEQControl.EQEnabled; FTX.EQNumBands := FTXEQControl.BandCount; FTX.EQGains[0] := FTXEQControl.PreampGain; for i := 1 to 10 do begin FTX.EQGains[i] := FTXEQControl.BandGain[i]; FTX.EQFreqs[i] := FTXEQControl.BandFreq[i]; end; 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.UpdateMicJackVisibility; var LineIn: Boolean; begin if FCmbTXMicJack = nil then Exit; LineIn := FCmbTXMicJack.ItemIndex = 1; if FChkTXMicBoost <> nil then FChkTXMicBoost.Visible := not LineIn; if FLblLineInGain <> nil then FLblLineInGain.Visible := LineIn; if FEdLineInGain <> nil then FEdLineInGain.Visible := LineIn; end; procedure TSettingsForm.OnMicJackChange(Sender: TObject); begin UpdateMicJackVisibility; FireTXChange; end; procedure TSettingsForm.LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0); var i: Integer; IsOrion: Boolean; begin FLoading := True; try FTX := T; if FCmbTXMicJack <> nil then FCmbTXMicJack.ItemIndex := IfThen(T.MicLineIn, 1, 0); UpdateMicJackVisibility; if FChkTXMicBoost <> nil then FChkTXMicBoost.Checked := T.MicBoost; if FEdLineInGain <> nil then FEdLineInGain.Value := EnsureRange(T.LineInGainDB, -34.5, 12.0); if FChkTXMicBias <> nil then FChkTXMicBias.Checked := T.MicBias; if FChkTXMicPTT <> nil then FChkTXMicPTT.Checked := T.MicPTTEnabled; // Tip/Ring (Orion boards 4 и 5) IsOrion := BoardType in [4, 5]; if FLblTipRing <> nil then FLblTipRing.Visible := IsOrion; if FCmbTXMicTR <> nil then begin FCmbTXMicTR.Visible := IsOrion; FCmbTXMicTR.ItemIndex := IfThen(T.MicTipRing, 1, 0); end; 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 FTXEQControl <> nil then begin FTXEQControl.EQEnabled := T.EQOn; FTXEQControl.BandCount := T.EQNumBands; FTXEQControl.PreampGain := T.EQGains[0]; for i := 1 to 10 do FTXEQControl.SetBand(i, T.EQFreqs[i], T.EQGains[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 = 54; ED_W = 72; COL_W = LBL_W + ED_W + 14; COLS = 4; VHF_LBL_W = 170; VHF_COL_W = VHF_LBL_W + ED_W + 48; VHF_COLS = 2; ROW_H = 32; GRP_PAD = 18; R1 = 42; GRP_H = 222; function MakeFloatSpin(AParent: TWinControl; ALeft, ATop: Integer; AChange: TNotifyEvent): TFlatFloatSpinEdit; var E: TFlatFloatSpinEdit; begin E := TFlatFloatSpinEdit.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: TFlatSpinEdit; 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, 700, 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 := TFlatSpinEdit.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; // ── VHF Calibration (Transverters) ────────────────────────────────────── Grp := MakeGroupPanel(FPagePA, 'VHF Calibration (Transverters)', MARGIN, 86 + GRP_H + 14, 700, R1 + 4 * ROW_H + 44); Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'Calibration per transverter slot, 38.8 ... 100.0'; Lbl.SetBounds(GRP_PAD, R1, 420, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; for i := 0 to CFG_XVTR_COUNT - 1 do begin r := i div VHF_COLS; c := i mod VHF_COLS; X := GRP_PAD + c * VHF_COL_W; Y := R1 + 24 + r * ROW_H; Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'VHF' + IntToStr(i) + ':'; Lbl.AutoSize := False; Lbl.SetBounds(X, Y + 5, VHF_LBL_W, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; Lbl.ShowHint := True; FVHFCalLabels[i] := Lbl; FEdVHFCal[i] := MakeFloatSpin(Grp, X + VHF_LBL_W + 8, Y, OnVHFCalEdit); FEdVHFCal[i].Tag := i; end; end; procedure TSettingsForm.BuildAdvancedTab; const MARGIN = 22; GRP_PAD = 18; LBL_W = 110; ED_W = 200; ROW_H = 42; R1 = 42; var Grp: TPanel; Chk: TFlatCheckBox; Ed: TFlatEdit; Spin: TFlatSpinEdit; Y: Integer; begin 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; // ── ADC ─────────────────────────────────────────────────────────────────── Grp := MakeGroupPanel(FPageAdvanced, 'ADC', MARGIN, 86, 420, 128); Chk := TFlatCheckBox.Create(Self); Chk.Parent := Grp; Chk.Caption := 'Dither Enabled'; Chk.SetBounds(GRP_PAD, R1, 260, 22); Chk.Font.Name := UI_FONT; Chk.Font.Size := 9; Chk.Font.Color := CLR_TEXT; Chk.Checked := True; Chk.OnChange := OnADCChkChange; FChkDither := Chk; Chk := TFlatCheckBox.Create(Self); Chk.Parent := Grp; Chk.Caption := 'Random Enabled'; Chk.SetBounds(GRP_PAD, R1 + ROW_H, 260, 22); Chk.Font.Name := UI_FONT; Chk.Font.Size := 9; Chk.Font.Color := CLR_TEXT; Chk.Checked := True; Chk.OnChange := OnADCChkChange; FChkRandom := Chk; // ── Web Server ──────────────────────────────────────────────────────────── Grp := MakeGroupPanel(FPageAdvanced, 'Web Server', MARGIN, 230, 500, 252); Y := R1; Chk := TFlatCheckBox.Create(Self); Chk.Parent := Grp; Chk.Caption := 'Enabled'; Chk.SetBounds(GRP_PAD, Y, 260, 22); Chk.Font.Name := UI_FONT; Chk.Font.Size := 9; Chk.Font.Color := CLR_TEXT; Chk.Checked := True; Chk.OnChange := OnWebAnyChange; FChkWebEnabled := Chk; Y := Y + ROW_H; MakeLbl(Grp, 'Port:', GRP_PAD, Y + 4, LBL_W); Spin := TFlatSpinEdit.Create(Self); Spin.Parent := Grp; Spin.SetBounds(GRP_PAD + LBL_W + 12, Y, 90, BTN_H); 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 := 8080; Spin.OnChange := OnWebAnyChange; FEdWebPort := Spin; Y := Y + ROW_H; MakeLbl(Grp, 'Interface:', GRP_PAD, Y + 4, LBL_W); Ed := TFlatEdit.Create(Self); Ed.Parent := Grp; Ed.SetBounds(GRP_PAD + LBL_W + 12, Y, ED_W, BTN_H); Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT; Ed.Font.Name := UI_FONT; Ed.Font.Size := 9; Ed.Text := '0.0.0.0'; Ed.OnChange := OnWebAnyChange; FEdWebBind := Ed; Y := Y + ROW_H; MakeLbl(Grp, 'Username:', GRP_PAD, Y + 4, LBL_W); Ed := TFlatEdit.Create(Self); Ed.Parent := Grp; Ed.SetBounds(GRP_PAD + LBL_W + 12, Y, ED_W, BTN_H); Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT; Ed.Font.Name := UI_FONT; Ed.Font.Size := 9; Ed.Text := 'admin'; Ed.OnChange := OnWebAnyChange; FEdWebUser := Ed; Y := Y + ROW_H; MakeLbl(Grp, 'Password:', GRP_PAD, Y + 4, LBL_W); Ed := TFlatEdit.Create(Self); Ed.Parent := Grp; Ed.SetBounds(GRP_PAD + LBL_W + 12, Y, ED_W, BTN_H); Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT; Ed.Font.Name := UI_FONT; Ed.Font.Size := 9; Ed.PasswordChar := '*'; Ed.Text := 'hpsdr'; Ed.OnChange := OnWebAnyChange; FEdWebPass := Ed; end; procedure TSettingsForm.OnADCChkChange(Sender: TObject); begin if FLoading then Exit; if Assigned(FOnADCChange) then FOnADCChange(FChkDither.Checked, FChkRandom.Checked); end; procedure TSettingsForm.OnWebAnyChange(Sender: TObject); begin if FLoading then Exit; if Assigned(FOnWebSettingsChange) then FOnWebSettingsChange( FChkWebEnabled.Checked, FEdWebPort.Value, FEdWebBind.Text, FEdWebUser.Text, FEdWebPass.Text); end; procedure TSettingsForm.LoadADCSettings(Dither, Random: Boolean); begin FLoading := True; try FChkDither.Checked := Dither; FChkRandom.Checked := Random; finally FLoading := False; end; end; procedure TSettingsForm.LoadWebSettings(Enabled: Boolean; Port: Integer; const BindAddr, User, Pass: string); begin FLoading := True; try FChkWebEnabled.Checked := Enabled; FEdWebPort.Value := EnsureRange(Port, 1, 65535); FEdWebBind.Text := BindAddr; FEdWebUser.Text := User; FEdWebPass.Text := Pass; finally FLoading := False; 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 TEqualizerControl then TEqualizerControl(Ctrl).SetAppTheme(T) else 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 TFlatCheckBox then TFlatCheckBox(Ctrl).SetAppTheme(T) else if Ctrl is TFlatComboBox then TFlatComboBox(Ctrl).SetAppTheme(T) else if Ctrl is TFlatEdit then TFlatEdit(Ctrl).SetAppTheme(T) else if Ctrl is TFlatSpinEdit then TFlatSpinEdit(Ctrl).SetAppTheme(T) else if Ctrl is TFlatFloatSpinEdit then TFlatFloatSpinEdit(Ctrl).SetAppTheme(T) else if Ctrl is TFlatRadioButton then TFlatRadioButton(Ctrl).SetAppTheme(T) 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; // --------------------------------------------------------------------------- // LoadAudioBufferSize // --------------------------------------------------------------------------- procedure TSettingsForm.LoadAudioBufferSize(BufferSize: Integer); begin FLoading := True; try case BufferSize of 256: FCmbAudioBuffer.ItemIndex := 1; 512: FCmbAudioBuffer.ItemIndex := 2; else FCmbAudioBuffer.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; FLoading := True; 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 FLoading := False; 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.OnWfAGCNFChkChange(Sender: TObject); begin if FLoading then Exit; if not Assigned(FOnWfAGCNFChange) then Exit; FOnWfAGCNFChange(FChkWfAGC.Checked, FChkWfNF.Checked); 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.OnAudioBufferCmbChange(Sender: TObject); var BufferSize: Integer; begin if FLoading then Exit; if not Assigned(FOnAudioBufferChange) then Exit; case FCmbAudioBuffer.ItemIndex of 1: BufferSize := 256; 2: BufferSize := 512; else BufferSize := 128; end; FOnAudioBufferChange(BufferSize); end; procedure TSettingsForm.LoadVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill: Boolean); begin FLoading := True; try FChkShowSpectrum.Checked := ShowSpectrum; FChkShowWaterfall.Checked := ShowWaterfall; FChkShowWideband.Checked := ShowWideband; FChkWidebandFill.Checked := WidebandFill; finally FLoading := False; end; end; procedure TSettingsForm.LoadWfAGCNF(AGC, NF: Boolean); begin FLoading := True; try FChkWfAGC.Checked := AGC; FChkWfNF.Checked := NF; 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, FChkShowWideband.Checked, FChkWidebandFill.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.LoadFreqMhzDigits(Digits: Integer); begin FLoading := True; try FCmbFreqMhzDigits.ItemIndex := EnsureRange(Digits - 3, 0, 2); finally FLoading := False; end; end; procedure TSettingsForm.OnFreqMhzDigitsCmbChange(Sender: TObject); begin if FLoading then Exit; if Assigned(FOnFreqMhzDigitsChange) then FOnFreqMhzDigitsChange(FCmbFreqMhzDigits.ItemIndex + 3); 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.LoadVHFCalSettings(const VHFCal: array of Double); var i, n: Integer; begin FLoading := True; try n := Min(CFG_XVTR_COUNT - 1, High(VHFCal)); for i := 0 to n do FEdVHFCal[i].Value := EnsureRange(VHFCal[i], 38.8, 100.0); finally FLoading := False; end; end; procedure TSettingsForm.OnVHFCalEdit(Sender: TObject); var VHFCal: array[0..CFG_XVTR_COUNT-1] of Double; i: Integer; begin if FLoading then Exit; if not Assigned(FOnVHFCalChange) then Exit; for i := 0 to CFG_XVTR_COUNT - 1 do VHFCal[i] := FEdVHFCal[i].Value; FOnVHFCalChange(VHFCal); 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: TFlatCheckBox; Ed: TFlatEdit; Cmb: TFlatComboBox; Spin: TFlatSpinEdit; 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 := TFlatCheckBox.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 := TFlatEdit.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 := TFlatCheckBox.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 := TFlatSpinEdit.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: TFlatCheckBox; 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): TFlatRadioButton; begin Result := TFlatRadioButton.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): TFlatCheckBox; begin Result := TFlatCheckBox.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 := TFlatCheckBox(Sender).Tag; if Tag >= 200 then begin b := Tag - 200; // XVTR if TFlatCheckBox(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 TFlatCheckBox(Sender).Checked then begin FLoading := True; FAlexRxByps[b].Checked := False; FAlexRxXvtr[b].Checked := False; FLoading := False; end; end else begin b := Tag; // BYPS if TFlatCheckBox(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 = 38; // место под двухстрочные заголовки столбцов // 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; // целое % PA_X = 704; PA_W = 66; ANT_X = 778; 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): TFlatFloatSpinEdit; begin Result := TFlatFloatSpinEdit.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): TFlatSpinEdit; begin Result := TFlatSpinEdit.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): TFlatCheckBox; begin Result := TFlatCheckBox.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): TFlatEdit; begin Result := TFlatEdit.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): TFlatComboBox; begin Result := TFlatComboBox.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.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.AutoSize := False; Result.SetBounds(AX, AY, AW, 28); Result.Alignment := taCenter; Result.Layout := tlCenter; Result.WordWrap := True; 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.' + LineEnding + 'The radio is internally tuned to the IF: f_IF = f_displayed - LO + LO Error.'; L.SetBounds(MARGIN, 50, GRP_W, 34); 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 + 6; MakeHdr(Grp, 'EN', EN_X + 2, Y, 28); MakeHdr(Grp, 'Label', NAME_X, Y, NAME_W); MakeHdr(Grp, 'Begin' + LineEnding + '(MHz)', BEG_X, Y, BEG_W); MakeHdr(Grp, 'End' + LineEnding + '(MHz)', END_X, Y, END_W); MakeHdr(Grp, 'LO' + LineEnding + '(MHz)', LO_X, Y, LO_W); MakeHdr(Grp, 'LO Error' + LineEnding + 'Hz', ERR_X, Y, ERR_W); MakeHdr(Grp, 'RX Gain' + LineEnding + 'dB', GAIN_X, Y, GAIN_W); MakeHdr(Grp, 'RX' + LineEnding + 'Only', RXO_X, Y, RXO_W); MakeHdr(Grp, 'TX Pwr' + LineEnding + '%', PWR_X, Y, PWR_W); MakeHdr(Grp, 'Disable' + LineEnding + 'PA', PA_X, Y, PA_W); MakeHdr(Grp, 'RX' + LineEnding + '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); 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 := TFlatCheckBox.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. Begin/End - displayed VFO range. ' + 'LO - local oscillator. LO Error - fine correction in Hz.' + LineEnding + 'RX Gain - transverter RX gain offset. RX Only - block TX. TX Pwr - drive limit. ' + 'XVTR RF - use the main HF RF path.' + LineEnding + 'Disable PA - keep the internal PA in RX position during TX. ' + 'RX Antenna - override the per-band antenna for this slot.'; L.SetBounds(MARGIN, GrpY + GRP_TITLE_H + 36 + 16, GRP_W, 52); 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 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 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 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); // Sync VHF calibration label in PA tab if FVHFCalLabels[i] <> nil then begin if X.Entries[i].ButtonText <> '' then FVHFCalLabels[i].Caption := 'VHF' + IntToStr(i) + ' (' + X.Entries[i].ButtonText + '):' else FVHFCalLabels[i].Caption := 'VHF' + IntToStr(i) + ':'; FVHFCalLabels[i].Hint := FVHFCalLabels[i].Caption; end; end; if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower; finally FLoading := False; end; end; end.