unit SettingsForm; { SettingsForm.pas — диалог настроек EWSDR SDR приложения. Вкладки: General | Audio | Display | DSP | Transmit | PA Settings | CAT Display имеет подвкладки: General | RX1 | RX2 | TX Все изменения применяются МГНОВЕННО через events: OnDisplayChange — изменены FFT/Window/Detector/Avg параметры OnWaterfallChange — изменены параметры водопада OnGridChange — изменена сетка спектра OnAudioChange — изменено аудио устройство/буфер OnSampleRateChange — изменён audio output sample rate (только PortAudio, DDC не трогается) Для применения настроек: создайте форму, зарегистрируйте callbacks, потом вызовите Show или ShowModal. } {$IFDEF FPC} {$MODE Delphi} {$ENDIF} interface uses Classes, SysUtils, Math, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Spin, FlatButton, AudioOutput, AudioInput; type // PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон TOnPASettingsChange = procedure(MaxPower: Double; const BandCal: array of Double) of object; TOnDisplayParamChange = procedure(FFTSize, WindowType, SpecDetector, SpecAvgMode: Integer; SpecAvgTimeMS: Double) of object; TOnWaterfallParamChange = procedure(WfDetector, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double) of object; TOnGridParamChange = procedure(RefLevel, Range, GridStep: Double) of object; TOnAudioDevChange = procedure(DevIndex: Integer; const DevName: string) of object; TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object; TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall: Boolean) of object; TOnFPSChange = procedure(FPS: Integer) of object; 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; { TSettingsForm } TSettingsForm = class(TForm) private // ---- Layout ---- FNavPanel: TPanel; FContentPanel: TPanel; FPageAudio: TScrollBox; FPageDisplay: TScrollBox; FPageSpectrum: TScrollBox; FPageWaterfall: TScrollBox; FPagePA: TScrollBox; FPageAdvanced: TScrollBox; FPageCAT: TScrollBox; FNavAudio: TFlatButton; FNavDisplay: TFlatButton; FNavSpectrum: TFlatButton; FNavWaterfall: TFlatButton; FNavPA: TFlatButton; FNavAdvanced: TFlatButton; FNavCAT: TFlatButton; // ---- Audio tab controls ---- FCmbRXDev: TComboBox; FRXDevIndices: array[0..63] of Integer; FRXDevCount: Integer; FCmbTXDev: TComboBox; FTXDevIndices: array[0..63] of Integer; FTXDevCount: Integer; // ---- RX1 sub-tab controls ---- FCmbFFTSize: TComboBox; FCmbWindow: TComboBox; FCmbSpecDet: TComboBox; FCmbSpecAvg: TComboBox; FSpecAvgTime: TFloatSpinEdit; FCmbWfDet: TComboBox; FCmbWfAvg: TComboBox; FWfAvgTime: TFloatSpinEdit; FWfHigh: TFloatSpinEdit; FWfLow: TFloatSpinEdit; FWfAGCOffset: TFloatSpinEdit; FEdRefLevel: TEdit; FEdRange: TEdit; FCmbGridStep: TComboBox; // ---- General display tab controls ---- FChkShowSpectrum: TCheckBox; FChkShowWaterfall: TCheckBox; FCmbFPS: TComboBox; // ---- PA Settings tab controls ---- FEdMaxPower: TSpinEdit; FEdBandCal: array[0..10] of TFloatSpinEdit; // ---- CAT tab controls ---- FCATSerialEn: array[0..3] of TCheckBox; FCATSerialPort: array[0..3] of TEdit; FCATSerialBaud: array[0..3] of TComboBox; FCATSerialDataBits: array[0..3] of TComboBox; FCATSerialStopBits: array[0..3] of TComboBox; FCATSerialParity: array[0..3] of TComboBox; FCATTcpEn: TCheckBox; FCATTcpPort: TSpinEdit; // ---- Close button ---- FBtnClose: TFlatButton; // ---- Callbacks ---- FOnCATChange: TOnCATChange; FOnPAChange: TOnPASettingsChange; FOnDisplayChange: TOnDisplayParamChange; FOnWaterfallChange: TOnWaterfallParamChange; FOnGridChange: TOnGridParamChange; FOnAudioDevChange: TOnAudioDevChange; FOnAudioInDevChange: TOnAudioInDevChange; FOnVisibilityChange: TOnVisibilityChange; FOnFPSChange: TOnFPSChange; FLoading: Boolean; procedure BuildUI; procedure BuildAudioTab; procedure BuildDisplayTab; procedure BuildRX1Tab; procedure BuildPATab; procedure BuildCATTab; procedure ApplyTheme; procedure NavClick(Sender: TObject); procedure SelectPage(APage: TWinControl; ANav: TFlatButton); function MakeLbl(AParent: TWinControl; const Cap: string; ALeft, ATop, AW: Integer): TLabel; function MakeCombo(AParent: TWinControl; ALeft, ATop, AW: Integer; OnChange: TNotifyEvent): TComboBox; function MakeGroupPanel(AParent: TWinControl; const Cap: string; ALeft, ATop, AW, AH: Integer): TPanel; function MakeScrollPage: TScrollBox; function MakeNavButton(const Cap: string; ATop: Integer): TFlatButton; procedure OnFFTSizeChange(Sender: TObject); procedure OnWindowChange(Sender: TObject); procedure OnSpecDetChange(Sender: TObject); procedure OnSpecAvgModeChange(Sender: TObject); procedure OnSpecAvgTimeChange(Sender: TObject); procedure OnWfDetChange(Sender: TObject); procedure OnWfAvgModeChange(Sender: TObject); procedure OnWfAvgTimeChange(Sender: TObject); procedure OnWfThresholdChange(Sender: TObject); procedure OnRefLevelChange(Sender: TObject); procedure OnRangeChange(Sender: TObject); procedure OnGridStepChange(Sender: TObject); procedure OnRXDevChange(Sender: TObject); procedure OnTXDevChange(Sender: TObject); procedure OnVisibilityChkChange(Sender: TObject); procedure OnFPSCmbChange(Sender: TObject); procedure OnMaxPowerChange(Sender: TObject); procedure OnBandCalChange(Sender: TObject); procedure OnCATSerialChange(Sender: TObject); procedure OnCATTcpChange(Sender: TObject); procedure FireCATChange; procedure BtnCloseClick(Sender: TObject); procedure FireDisplayChange; procedure FireWaterfallChange; procedure FireGridChange; function GetSpecAvgTimeMS: Double; function GetWfAvgTimeMS: Double; function GetWfHigh: Double; function GetWfLow: Double; function GetWfAGCOffset: Double; function GetRefLevel: Double; function GetRange: Double; function GetGridStep: Double; function GetFFTSize: Integer; public constructor Create(AOwner: TComponent); override; procedure LoadValues( FFTSize, WinType, SpecDet, SpecAvgMode: Integer; SpecAvgTimeMS: Double; WfDet, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double; RefLevel, Range, GridStep: Double; const AudioOutDevName: string; const AudioInDevName: string); procedure RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput); procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean); procedure LoadFPS(FPS: Integer); procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double); procedure LoadCATSettings( const SerEnabled: array of Boolean; const SerPort: array of string; const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer; TcpEnabled: Boolean; TcpPort: Integer); property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange; property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange; property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange; property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange; property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange; property OnAudioDevChange: TOnAudioDevChange read FOnAudioDevChange write FOnAudioDevChange; property OnAudioInDevChange: TOnAudioInDevChange read FOnAudioInDevChange write FOnAudioInDevChange; property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange; property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange; end; implementation const CLR_BG = TColor($00121212); CLR_PANEL = TColor($001A1A1A); CLR_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 := 900; Height := 660; Position := poScreenCenter; BorderStyle := bsSizeable; Color := CLR_BG; Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXT; FLoading := False; FRXDevCount := 0; BuildUI; ApplyTheme; end; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function TSettingsForm.MakeLbl(AParent: TWinControl; const Cap: string; ALeft, ATop, AW: Integer): TLabel; begin Result := TLabel.Create(Self); Result.Parent := AParent; Result.Caption := Cap; Result.Left := ALeft; Result.Top := ATop; if AW > 0 then Result.Width := AW; Result.Font.Color := CLR_TEXTDIM; Result.Font.Name := UI_FONT; Result.Font.Size := 9; end; function TSettingsForm.MakeCombo(AParent: TWinControl; ALeft, ATop, AW: Integer; OnChange: TNotifyEvent): TComboBox; begin Result := TComboBox.Create(Self); Result.Parent := AParent; Result.Left := ALeft; Result.Top := ATop; Result.Width := AW; Result.Height := BTN_H; Result.Style := csDropDownList; Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := OnChange; end; // Создаёт панель-группу с заголовком и разделителем (вместо TGroupBox). // На Windows TGroupBox рисует фрейм системными цветами, TPanel — нет. function TSettingsForm.MakeGroupPanel(AParent: TWinControl; const Cap: string; ALeft, ATop, AW, AH: Integer): TPanel; var TitleLbl: TLabel; Divider: TPanel; begin Result := TPanel.Create(Self); Result.Parent := AParent; Result.SetBounds(ALeft, ATop, AW, AH); Result.BevelOuter := bvNone; Result.Color := CLR_PANEL; TitleLbl := TLabel.Create(Self); TitleLbl.Parent := Result; TitleLbl.Caption := Cap; TitleLbl.SetBounds(8, 5, AW - 16, 14); TitleLbl.Font.Color := CLR_ACCENT; TitleLbl.Font.Name := UI_FONT; TitleLbl.Font.Size := 9; TitleLbl.Font.Style := [fsBold]; Divider := TPanel.Create(Self); Divider.Parent := Result; Divider.SetBounds(0, GRP_TITLE_H, AW + 200, 1); Divider.BevelOuter := bvNone; Divider.Color := CLR_BORDER; end; function TSettingsForm.MakeScrollPage: TScrollBox; begin Result := TScrollBox.Create(Self); Result.Parent := FContentPanel; Result.Align := alClient; Result.BorderStyle := bsNone; Result.Color := CLR_BG; Result.AutoScroll := True; Result.Visible := False; end; function TSettingsForm.MakeNavButton(const Cap: string; ATop: Integer): TFlatButton; begin Result := TFlatButton.Create(Self); Result.Parent := FNavPanel; Result.Caption := Cap; Result.SetBounds(10, ATop, 166, 32); Result.Anchors := [akLeft, akTop, akRight]; Result.OnClick := NavClick; Result.ClrNorm := CLR_PANEL; Result.ClrBorder := CLR_PANEL; Result.ClrHot := TColor($00242424); Result.ClrActive := TColor($00204030); Result.ClrText := CLR_TEXT; Result.ClrTextAct := CLR_ACCENT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.Font.Style := []; end; // --------------------------------------------------------------------------- // BuildUI // --------------------------------------------------------------------------- procedure TSettingsForm.BuildUI; begin FNavPanel := TPanel.Create(Self); FNavPanel.Parent := Self; FNavPanel.Align := alLeft; FNavPanel.Width := 188; FNavPanel.BevelOuter := bvNone; FNavPanel.Color := TColor($00151515); with TLabel.Create(Self) do begin Parent := FNavPanel; Caption := 'Settings'; SetBounds(14, 14, 160, 24); Font.Name := UI_FONT; Font.Size := 14; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FNavPanel; Caption := 'EWSDR'; SetBounds(14, 39, 160, 18); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; FNavAudio := MakeNavButton('Audio', 74); FNavDisplay := MakeNavButton('Display', 110); FNavSpectrum := MakeNavButton('Spectrum', 146); FNavWaterfall := MakeNavButton('Waterfall', 182); FNavPA := MakeNavButton('Power Amplifier', 218); FNavAdvanced := MakeNavButton('Advanced', 254); FNavCAT := MakeNavButton('CAT', 290); 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; FPagePA := MakeScrollPage; FPageAdvanced := MakeScrollPage; FPageCAT := MakeScrollPage; BuildAudioTab; BuildDisplayTab; BuildRX1Tab; BuildPATab; BuildCATTab; 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 = FNavPA then SelectPage(FPagePA, FNavPA) else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced) else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT); 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; FPagePA.Visible := APage = FPagePA; FPageAdvanced.Visible := APage = FPageAdvanced; FPageCAT.Visible := APage = FPageCAT; FNavAudio.Active := ANav = FNavAudio; FNavDisplay.Active := ANav = FNavDisplay; FNavSpectrum.Active := ANav = FNavSpectrum; FNavWaterfall.Active := ANav = FNavWaterfall; FNavPA.Active := ANav = FNavPA; FNavAdvanced.Active := ANav = FNavAdvanced; FNavCAT.Active := ANav = FNavCAT; end; // --------------------------------------------------------------------------- // BuildAudioTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildAudioTab; const MARGIN = 22; PAD = 18; R1 = 42; STEP = 42; LW = 150; CX = PAD + LW + 12; CMB_W = 460; var Grp: TPanel; begin with TLabel.Create(Self) do begin Parent := FPageAudio; Caption := 'Audio'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageAudio; Caption := 'Input and output devices used by the receiver and transmitter.'; SetBounds(MARGIN, 48, 560, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageAudio, 'Audio Devices', MARGIN, 86, 650, 142); MakeLbl(Grp, 'TX input device', PAD, R1 + 5, LW); FCmbTXDev := MakeCombo(Grp, CX, R1, CMB_W, OnTXDevChange); FCmbTXDev.Items.Add('None'); MakeLbl(Grp, 'RX output device', PAD, R1 + STEP + 5, LW); FCmbRXDev := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnRXDevChange); FCmbRXDev.Items.Add('Default'); end; // --------------------------------------------------------------------------- // BuildDisplayTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildDisplayTab; const MARGIN = 22; PAD = 18; R1 = 42; CHKW = 220; LW = 150; CX = PAD + LW + 12; var i: Integer; Grp: TPanel; begin with TLabel.Create(Self) do begin Parent := FPageDisplay; Caption := 'Display'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageDisplay; Caption := 'General visual options. Spectrum and waterfall processing are split into their own pages.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageDisplay, 'Visible Panes', MARGIN, 86, 650, 86); FChkShowSpectrum := TCheckBox.Create(Self); FChkShowSpectrum.Parent := Grp; FChkShowSpectrum.Caption := 'Spectrum'; FChkShowSpectrum.SetBounds(PAD, R1, CHKW, 22); FChkShowSpectrum.Checked := True; FChkShowSpectrum.Font.Color := CLR_TEXT; FChkShowSpectrum.Font.Name := UI_FONT; FChkShowSpectrum.Font.Size := 9; FChkShowSpectrum.OnChange := OnVisibilityChkChange; FChkShowWaterfall := TCheckBox.Create(Self); FChkShowWaterfall.Parent := Grp; FChkShowWaterfall.Caption := 'Waterfall'; FChkShowWaterfall.SetBounds(PAD + CHKW + 20, R1, CHKW, 22); FChkShowWaterfall.Checked := True; FChkShowWaterfall.Font.Color := CLR_TEXT; FChkShowWaterfall.Font.Name := UI_FONT; FChkShowWaterfall.Font.Size := 9; FChkShowWaterfall.OnChange := OnVisibilityChkChange; Grp := MakeGroupPanel(FPageDisplay, 'Performance', MARGIN, 186, 650, 92); MakeLbl(Grp, 'Refresh rate', PAD, R1 + 5, LW); FCmbFPS := MakeCombo(Grp, CX, R1, 140, OnFPSCmbChange); for i := 0 to High(FPS_NAMES) do FCmbFPS.Items.Add(FPS_NAMES[i]); FCmbFPS.ItemIndex := 8; end; // --------------------------------------------------------------------------- // BuildRX1Tab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildRX1Tab; const MARGIN = 22; PAD = 18; LW = 150; CX = PAD + LW + 12; CMB_W = 230; CMB_W_LONG = 300; SPW = 96; EDT_W = 96; STEP = 42; R1 = 42; var Grp: TPanel; i: Integer; function MkFloat(AX, AY, AW: Integer; AValue, AMin, AMax, AInc: Double; AOnChange: TNotifyEvent): TFloatSpinEdit; begin Result := TFloatSpinEdit.Create(Self); Result.Parent := Grp; Result.SetBounds(AX, AY, AW, BTN_H); Result.Value := AValue; Result.MinValue := AMin; Result.MaxValue := AMax; Result.Increment := AInc; Result.DecimalPlaces := 1; Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.OnChange := AOnChange; end; function MkEdit(AX, AY, AW: Integer; const AText: string; AOnExit: TNotifyEvent): TEdit; begin Result := TEdit.Create(Self); Result.Parent := Grp; Result.SetBounds(AX, AY, AW, BTN_H); Result.Color := CLR_INPUT; Result.Font.Color := CLR_INPUT_TEXT; Result.Font.Name := UI_FONT; Result.Font.Size := 9; Result.Text := AText; Result.OnExit := AOnExit; end; begin with TLabel.Create(Self) do begin Parent := FPageSpectrum; Caption := 'Spectrum'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageSpectrum; Caption := 'Analyzer, detector, averaging and grid parameters for RX1.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageSpectrum, 'Analyzer', MARGIN, 86, 650, 128); MakeLbl(Grp, 'FFT Size:', PAD, R1 + 5, LW); FCmbFFTSize := MakeCombo(Grp, CX, R1, CMB_W, OnFFTSizeChange); for i := 0 to High(FFT_NAMES) do FCmbFFTSize.Items.Add(FFT_NAMES[i]); MakeLbl(Grp, 'Window:', PAD, R1 + STEP + 5, LW); FCmbWindow := MakeCombo(Grp, CX, R1 + STEP, CMB_W_LONG, OnWindowChange); for i := 0 to High(WINDOW_NAMES) do FCmbWindow.Items.Add(WINDOW_NAMES[i]); Grp := MakeGroupPanel(FPageSpectrum, 'Detection && Averaging', MARGIN, 230, 650, 170); MakeLbl(Grp, 'Detector:', PAD, R1 + 5, LW); FCmbSpecDet := MakeCombo(Grp, CX, R1, CMB_W, OnSpecDetChange); for i := 0 to High(DETECTOR_NAMES) do FCmbSpecDet.Items.Add(DETECTOR_NAMES[i]); MakeLbl(Grp, 'Avg Mode:', PAD, R1 + STEP + 5, LW); FCmbSpecAvg := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnSpecAvgModeChange); for i := 0 to High(AVGMODE_NAMES) do FCmbSpecAvg.Items.Add(AVGMODE_NAMES[i]); MakeLbl(Grp, 'Avg Time (ms):', PAD, R1 + 2 * STEP + 5, LW); FSpecAvgTime := MkFloat(CX, R1 + 2 * STEP, SPW, 30.0, 0.0, 5000.0, 10.0, OnSpecAvgTimeChange); Grp := MakeGroupPanel(FPageSpectrum, 'Grid', MARGIN, 416, 650, 170); MakeLbl(Grp, 'Reference level:', PAD, R1 + 5, LW); FEdRefLevel := MkEdit(CX, R1, EDT_W, '-20', OnRefLevelChange); MakeLbl(Grp, 'Range (dB):', PAD, R1 + STEP + 5, LW); FEdRange := MkEdit(CX, R1 + STEP, EDT_W, '110', OnRangeChange); MakeLbl(Grp, 'Grid step:', PAD, R1 + 2 * STEP + 5, LW); FCmbGridStep := MakeCombo(Grp, CX, R1 + 2 * STEP, 130, OnGridStepChange); for i := 0 to High(GRID_STEP_NAMES) do FCmbGridStep.Items.Add(GRID_STEP_NAMES[i]); FCmbGridStep.ItemIndex := 1; with TLabel.Create(Self) do begin Parent := FPageWaterfall; Caption := 'Waterfall'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageWaterfall; Caption := 'Waterfall detector, smoothing and color scale thresholds.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPageWaterfall, 'Detection && Averaging', MARGIN, 86, 650, 170); MakeLbl(Grp, 'Detector:', PAD, R1 + 5, LW); FCmbWfDet := MakeCombo(Grp, CX, R1, CMB_W, OnWfDetChange); for i := 0 to High(DETECTOR_NAMES) do FCmbWfDet.Items.Add(DETECTOR_NAMES[i]); MakeLbl(Grp, 'Avg Mode:', PAD, R1 + STEP + 5, LW); FCmbWfAvg := MakeCombo(Grp, CX, R1 + STEP, CMB_W, OnWfAvgModeChange); for i := 0 to High(WF_AVGMODE_NAMES) do FCmbWfAvg.Items.Add(WF_AVGMODE_NAMES[i]); MakeLbl(Grp, 'Avg Time (ms):', PAD, R1 + 2 * STEP + 5, LW); FWfAvgTime := MkFloat(CX, R1 + 2 * STEP, SPW, 120.0, 0.0, 5000.0, 10.0, OnWfAvgTimeChange); Grp := MakeGroupPanel(FPageWaterfall, 'Color Scale', MARGIN, 272, 650, 170); MakeLbl(Grp, 'High (dBm):', PAD, R1 + 5, LW); FWfHigh := MkFloat(CX, R1, SPW, -80.0, -160.0, 20.0, 1.0, OnWfThresholdChange); MakeLbl(Grp, 'Low (dBm):', PAD, R1 + STEP + 5, LW); FWfLow := MkFloat(CX, R1 + STEP, SPW, -130.0, -180.0, 0.0, 1.0, OnWfThresholdChange); MakeLbl(Grp, 'AGC Offset (dB):', PAD, R1 + 2 * STEP + 5, LW); FWfAGCOffset := MkFloat(CX, R1 + 2 * STEP, SPW, 0.0, -40.0, 40.0, 0.5, OnWfThresholdChange); end; // --------------------------------------------------------------------------- // BuildPATab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildPATab; const MARGIN = 22; LBL_W = 46; ED_W = 72; COL_W = LBL_W + ED_W + 8; COLS = 4; ROW_H = 32; GRP_PAD = 18; R1 = 42; GRP_H = 222; function MakeFloatSpin(AParent: TWinControl; ALeft, ATop: Integer; AChange: TNotifyEvent): TFloatSpinEdit; var E: TFloatSpinEdit; begin E := TFloatSpinEdit.Create(Self); E.Parent := AParent; E.SetBounds(ALeft, ATop, ED_W, BTN_H + 2); E.Color := CLR_INPUT; E.Font.Color := CLR_INPUT_TEXT; E.Font.Name := UI_FONT; E.Font.Size := 9; E.MinValue := 38.8; E.MaxValue := 100.0; E.Increment := 0.1; E.DecimalPlaces := 1; E.Value := 100.0; E.OnChange := AChange; Result := E; end; var Grp: TPanel; Lbl: TLabel; Spin: TSpinEdit; i, r, c, X, Y: Integer; begin with TLabel.Create(Self) do begin Parent := FPagePA; Caption := 'Power Amplifier'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPagePA; Caption := 'Output limit and per-band calibration values.'; SetBounds(MARGIN, 48, 620, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; Grp := MakeGroupPanel(FPagePA, 'Power && Calibration', MARGIN, 86, 650, GRP_H); // Max Power Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'Max power (W)'; Lbl.SetBounds(GRP_PAD, R1 + 6, 132, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; Spin := TSpinEdit.Create(Self); Spin.Parent := Grp; Spin.SetBounds(GRP_PAD + 150, R1, ED_W, BTN_H + 2); Spin.Color := CLR_INPUT; Spin.Font.Color := CLR_INPUT_TEXT; Spin.Font.Name := UI_FONT; Spin.Font.Size := 9; Spin.MinValue := 5; Spin.MaxValue := 200; Spin.Increment := 1; Spin.Value := 100; Spin.OnChange := OnMaxPowerChange; FEdMaxPower := Spin; // Calibration label Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := 'Calibration per band, 38.8 ... 100.0'; Lbl.SetBounds(GRP_PAD, R1 + ROW_H + 12, 320, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; // Band grid 4 columns × 3 rows for i := 0 to 10 do begin r := i div COLS; c := i mod COLS; X := GRP_PAD + c * (COL_W + 4); Y := R1 + ROW_H + 34 + r * ROW_H; Lbl := TLabel.Create(Self); Lbl.Parent := Grp; Lbl.Caption := PA_BAND_NAMES[i] + ':'; Lbl.SetBounds(X, Y + 5, LBL_W, 18); Lbl.Font.Color := CLR_TEXTDIM; Lbl.Font.Name := UI_FONT; Lbl.Font.Size := 9; FEdBandCal[i] := MakeFloatSpin(Grp, X + LBL_W, Y, OnBandCalChange); FEdBandCal[i].Tag := i; end; with TLabel.Create(Self) do begin Parent := FPageAdvanced; Caption := 'Advanced'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageAdvanced; Caption := 'Transmit, CAT and DSP-only settings will appear here when they are wired to runtime settings.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; end; procedure TSettingsForm.ApplyTheme; begin Color := CLR_BG; if Assigned(FNavPanel) then FNavPanel.Color := TColor($00151515); if Assigned(FContentPanel) then FContentPanel.Color := CLR_BG; if Assigned(FPageAudio) then FPageAudio.Color := CLR_BG; if Assigned(FPageDisplay) then FPageDisplay.Color := CLR_BG; if Assigned(FPageSpectrum) then FPageSpectrum.Color := CLR_BG; if Assigned(FPageWaterfall) then FPageWaterfall.Color := CLR_BG; if Assigned(FPagePA) then FPagePA.Color := CLR_BG; if Assigned(FPageAdvanced) then FPageAdvanced.Color := CLR_BG; if Assigned(FPageCAT) then FPageCAT.Color := CLR_BG; end; // --------------------------------------------------------------------------- // LoadValues // --------------------------------------------------------------------------- procedure TSettingsForm.LoadValues( FFTSize, WinType, SpecDet, SpecAvgMode: Integer; SpecAvgTimeMS: Double; WfDet, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double; RefLevel, Range, GridStep: Double; const AudioOutDevName: string; const AudioInDevName: string); var i, Idx: Integer; begin FLoading := True; try Idx := 5; for i := 0 to High(FFT_SIZES) do if FFT_SIZES[i] = FFTSize then begin Idx := i; Break; end; FCmbFFTSize.ItemIndex := Idx; if (WinType >= 0) and (WinType < FCmbWindow.Items.Count) then FCmbWindow.ItemIndex := WinType else FCmbWindow.ItemIndex := 2; if (SpecDet >= 0) and (SpecDet < FCmbSpecDet.Items.Count) then FCmbSpecDet.ItemIndex := SpecDet else FCmbSpecDet.ItemIndex := 0; Idx := 3; for i := 0 to High(AVGMODE_VALUES) do if AVGMODE_VALUES[i] = SpecAvgMode then begin Idx := i; Break; end; FCmbSpecAvg.ItemIndex := Idx; FSpecAvgTime.Value := EnsureRange(SpecAvgTimeMS, 0.0, 5000.0); if (WfDet >= 0) and (WfDet < FCmbWfDet.Items.Count) then FCmbWfDet.ItemIndex := WfDet else FCmbWfDet.ItemIndex := 0; Idx := 3; for i := 0 to High(WF_AVGMODE_VALUES) do if WF_AVGMODE_VALUES[i] = WfAvgMode then begin Idx := i; Break; end; FCmbWfAvg.ItemIndex := Idx; FWfAvgTime.Value := EnsureRange(WfAvgTimeMS, 0.0, 5000.0); FWfHigh.Value := EnsureRange(WfHigh, -160.0, 20.0); FWfLow.Value := EnsureRange(WfLow, -180.0, 0.0); FWfAGCOffset.Value := EnsureRange(WfAGCOffset, -40.0, 40.0); FEdRefLevel.Text := FormatFloat('0.#', RefLevel); FEdRange.Text := FormatFloat('0.#', Range); Idx := 1; for i := 0 to High(GRID_STEPS) do if Abs(GRID_STEPS[i] - GridStep) < 0.5 then begin Idx := i; Break; end; FCmbGridStep.ItemIndex := Idx; if AudioOutDevName = '' then FCmbRXDev.ItemIndex := 0 else begin Idx := FCmbRXDev.Items.IndexOf(AudioOutDevName); if Idx >= 0 then FCmbRXDev.ItemIndex := Idx else FCmbRXDev.ItemIndex := 0; end; if AudioInDevName = '' then FCmbTXDev.ItemIndex := 0 else begin Idx := FCmbTXDev.Items.IndexOf(AudioInDevName); if Idx >= 0 then FCmbTXDev.ItemIndex := Idx else FCmbTXDev.ItemIndex := 0; end; finally FLoading := False; end; end; // --------------------------------------------------------------------------- // RefreshAudioDevices // --------------------------------------------------------------------------- procedure TSettingsForm.RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput); var Names: TStringList; Cnt, i: Integer; OldRXSel: string; OldTXSel: string; begin Names := TStringList.Create; try if AudioOut <> nil then begin OldRXSel := FCmbRXDev.Text; FCmbRXDev.Items.Clear; FCmbRXDev.Items.Add('Default'); FRXDevCount := 0; Cnt := 0; Names.Clear; if AudioOut.EnumOutputDevices(Names, FRXDevIndices, Cnt) then begin FRXDevCount := Cnt; for i := 0 to Cnt - 1 do FCmbRXDev.Items.Add(Names[i]); end; if (FRXDevCount = 0) and (AudioOut.LastError <> '') then FCmbRXDev.Items.Add('PortAudio: ' + AudioOut.LastError); i := FCmbRXDev.Items.IndexOf(OldRXSel); if i >= 0 then FCmbRXDev.ItemIndex := i else FCmbRXDev.ItemIndex := 0; end; if AudioIn <> nil then begin OldTXSel := FCmbTXDev.Text; FCmbTXDev.Items.Clear; FCmbTXDev.Items.Add('None'); FTXDevCount := 0; Cnt := 0; Names.Clear; if AudioIn.EnumInputDevices(Names, FTXDevIndices, Cnt) then begin FTXDevCount := Cnt; for i := 0 to Cnt - 1 do FCmbTXDev.Items.Add(Names[i]); end; if (FTXDevCount = 0) and (AudioIn.LastError <> '') then FCmbTXDev.Items.Add('PortAudio: ' + AudioIn.LastError); i := FCmbTXDev.Items.IndexOf(OldTXSel); if i >= 0 then FCmbTXDev.ItemIndex := i else FCmbTXDev.ItemIndex := 0; end; finally Names.Free; end; end; // --------------------------------------------------------------------------- // Value accessors // --------------------------------------------------------------------------- function TSettingsForm.GetFFTSize: Integer; var Idx: Integer; begin Idx := FCmbFFTSize.ItemIndex; if (Idx >= 0) and (Idx <= High(FFT_SIZES)) then Result := FFT_SIZES[Idx] else Result := 4096; end; function TSettingsForm.GetSpecAvgTimeMS: Double; begin Result := EnsureRange(FSpecAvgTime.Value, 0.0, 5000.0); end; function TSettingsForm.GetWfAvgTimeMS: Double; begin Result := EnsureRange(FWfAvgTime.Value, 0.0, 5000.0); end; function TSettingsForm.GetWfHigh: Double; begin Result := EnsureRange(FWfHigh.Value, -160.0, 20.0); end; function TSettingsForm.GetWfLow: Double; begin Result := EnsureRange(FWfLow.Value, -180.0, 0.0); end; function TSettingsForm.GetWfAGCOffset: Double; begin Result := EnsureRange(FWfAGCOffset.Value, -40.0, 40.0); end; function TSettingsForm.GetRefLevel: Double; var V: Double; Err: Integer; begin Val(FEdRefLevel.Text, V, Err); if Err <> 0 then V := -20.0; Result := EnsureRange(V, -200.0, 0.0); end; function TSettingsForm.GetRange: Double; var V: Double; Err: Integer; begin Val(FEdRange.Text, V, Err); if Err <> 0 then V := 110.0; Result := EnsureRange(V, 10.0, 200.0); end; function TSettingsForm.GetGridStep: Double; var Idx: Integer; begin Idx := FCmbGridStep.ItemIndex; if (Idx >= 0) and (Idx <= High(GRID_STEPS)) then Result := GRID_STEPS[Idx] else Result := 10.0; end; // --------------------------------------------------------------------------- // Fire helpers // --------------------------------------------------------------------------- procedure TSettingsForm.FireDisplayChange; var AvgIdx: Integer; AvgVal: Integer; begin if FLoading then Exit; if not Assigned(FOnDisplayChange) then Exit; AvgIdx := FCmbSpecAvg.ItemIndex; if (AvgIdx >= 0) and (AvgIdx <= High(AVGMODE_VALUES)) then AvgVal := AVGMODE_VALUES[AvgIdx] else AvgVal := 3; FOnDisplayChange( GetFFTSize, FCmbWindow.ItemIndex, FCmbSpecDet.ItemIndex, AvgVal, GetSpecAvgTimeMS); end; procedure TSettingsForm.FireWaterfallChange; var AvgIdx: Integer; AvgVal: Integer; begin if FLoading then Exit; if not Assigned(FOnWaterfallChange) then Exit; AvgIdx := FCmbWfAvg.ItemIndex; if (AvgIdx >= 0) and (AvgIdx <= High(WF_AVGMODE_VALUES)) then AvgVal := WF_AVGMODE_VALUES[AvgIdx] else AvgVal := 1; FOnWaterfallChange( FCmbWfDet.ItemIndex, AvgVal, GetWfAvgTimeMS, GetWfHigh, GetWfLow, GetWfAGCOffset); end; procedure TSettingsForm.FireGridChange; begin if FLoading then Exit; if not Assigned(FOnGridChange) then Exit; FOnGridChange(GetRefLevel, GetRange, GetGridStep); end; // --------------------------------------------------------------------------- // Event handlers // --------------------------------------------------------------------------- procedure TSettingsForm.OnFFTSizeChange(Sender: TObject); begin FireDisplayChange; end; procedure TSettingsForm.OnWindowChange(Sender: TObject); begin FireDisplayChange; end; procedure TSettingsForm.OnSpecDetChange(Sender: TObject); begin FireDisplayChange; end; procedure TSettingsForm.OnSpecAvgModeChange(Sender: TObject); begin FireDisplayChange; end; procedure TSettingsForm.OnSpecAvgTimeChange(Sender: TObject); begin FireDisplayChange; end; procedure TSettingsForm.OnWfDetChange(Sender: TObject); begin FireWaterfallChange; end; procedure TSettingsForm.OnWfAvgModeChange(Sender: TObject); begin FireWaterfallChange; end; procedure TSettingsForm.OnWfAvgTimeChange(Sender: TObject); begin FireWaterfallChange; end; procedure TSettingsForm.OnWfThresholdChange(Sender: TObject); begin FireWaterfallChange; end; procedure TSettingsForm.OnRefLevelChange(Sender: TObject); begin FireGridChange; end; procedure TSettingsForm.OnRangeChange(Sender: TObject); begin FireGridChange; end; procedure TSettingsForm.OnGridStepChange(Sender: TObject); begin FireGridChange; end; procedure TSettingsForm.OnRXDevChange(Sender: TObject); var Idx: Integer; DevIdx: Integer; DevName: string; begin if FLoading then Exit; if not Assigned(FOnAudioDevChange) then Exit; Idx := FCmbRXDev.ItemIndex; if Idx <= 0 then FOnAudioDevChange(-1, '') else begin DevName := FCmbRXDev.Items[Idx]; DevIdx := Idx - 1; if (DevIdx >= 0) and (DevIdx < FRXDevCount) then FOnAudioDevChange(FRXDevIndices[DevIdx], DevName) else FOnAudioDevChange(-1, ''); end; end; procedure TSettingsForm.OnTXDevChange(Sender: TObject); var Idx: Integer; DevIdx: Integer; DevName: string; begin if FLoading then Exit; if not Assigned(FOnAudioInDevChange) then Exit; Idx := FCmbTXDev.ItemIndex; if Idx <= 0 then FOnAudioInDevChange(-1, '') else begin DevName := FCmbTXDev.Items[Idx]; DevIdx := Idx - 1; if (DevIdx >= 0) and (DevIdx < FTXDevCount) then FOnAudioInDevChange(FTXDevIndices[DevIdx], DevName) else FOnAudioInDevChange(-1, ''); end; end; procedure TSettingsForm.LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean); begin FLoading := True; try FChkShowSpectrum.Checked := ShowSpectrum; FChkShowWaterfall.Checked := ShowWaterfall; finally FLoading := False; end; end; procedure TSettingsForm.OnVisibilityChkChange(Sender: TObject); begin if FLoading then Exit; if not Assigned(FOnVisibilityChange) then Exit; FOnVisibilityChange(FChkShowSpectrum.Checked, FChkShowWaterfall.Checked); end; procedure TSettingsForm.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.LoadPASettings(MaxPower: Double; const BandCal: array of Double); var i, n: Integer; begin FLoading := True; try FEdMaxPower.Value := Round(EnsureRange(MaxPower, 5.0, 200.0)); n := Min(10, High(BandCal)); for i := 0 to n do FEdBandCal[i].Value := EnsureRange(BandCal[i], 38.8, 100.0); finally FLoading := False; end; end; procedure TSettingsForm.OnMaxPowerChange(Sender: TObject); var BandCal: array[0..10] of Double; i: Integer; begin if FLoading then Exit; if not Assigned(FOnPAChange) then Exit; for i := 0 to 10 do BandCal[i] := FEdBandCal[i].Value; FOnPAChange(FEdMaxPower.Value, BandCal); end; procedure TSettingsForm.OnBandCalChange(Sender: TObject); var BandCal: array[0..10] of Double; i: Integer; begin if FLoading then Exit; if not Assigned(FOnPAChange) then Exit; for i := 0 to 10 do BandCal[i] := FEdBandCal[i].Value; FOnPAChange(FEdMaxPower.Value, BandCal); end; procedure TSettingsForm.BtnCloseClick(Sender: TObject); begin Close; end; // --------------------------------------------------------------------------- // BuildCATTab // --------------------------------------------------------------------------- procedure TSettingsForm.BuildCATTab; const MARGIN = 22; PAD = 18; LW = 120; CX = PAD + LW + 8; GRP_W = 320; GRP_H = 240; GRP_GAP = 16; CMB_W = 140; EDT_W = 160; R1 = 42; STEP = 32; var i, col, row, gx, gy, j: Integer; Grp: TPanel; Chk: TCheckBox; Ed: TEdit; Cmb: TComboBox; Spin: TSpinEdit; begin with TLabel.Create(Self) do begin Parent := FPageCAT; Caption := 'CAT'; SetBounds(MARGIN, 18, 400, 28); Font.Name := UI_FONT; Font.Size := 16; Font.Style := [fsBold]; Font.Color := CLR_TEXT; end; with TLabel.Create(Self) do begin Parent := FPageCAT; Caption := 'Computer Aided Transceiver — serial port and TCP server settings.'; SetBounds(MARGIN, 48, 680, 20); Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM; end; for i := 0 to 3 do begin row := i div 2; col := i mod 2; gx := MARGIN + col * (GRP_W + GRP_GAP); gy := 86 + row * (GRP_H + GRP_GAP); Grp := MakeGroupPanel(FPageCAT, 'Serial Port ' + IntToStr(i + 1), gx, gy, GRP_W, GRP_H); Chk := TCheckBox.Create(Self); Chk.Parent := Grp; Chk.Caption := 'Enable'; Chk.SetBounds(PAD, R1, GRP_W - PAD * 2, 22); Chk.Font.Color := CLR_TEXT; Chk.Font.Name := UI_FONT; Chk.Font.Size := 9; Chk.Tag := i; Chk.OnChange := OnCATSerialChange; FCATSerialEn[i] := Chk; MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW); Ed := TEdit.Create(Self); Ed.Parent := Grp; Ed.SetBounds(CX, R1 + STEP, EDT_W, BTN_H); Ed.Color := CLR_INPUT; Ed.Font.Color := CLR_INPUT_TEXT; Ed.Font.Name := UI_FONT; Ed.Font.Size := 9; Ed.Tag := i; Ed.OnChange := OnCATSerialChange; {$IFDEF MSWINDOWS} Ed.Text := 'COM' + IntToStr(i + 1); {$ELSE} Ed.Text := '/dev/ttyS' + IntToStr(i); {$ENDIF} FCATSerialPort[i] := Ed; MakeLbl(Grp, 'Baud rate:', PAD, R1 + 2 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 2 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; for j := 0 to High(CAT_BAUD_NAMES) do Cmb.Items.Add(CAT_BAUD_NAMES[j]); Cmb.ItemIndex := 3; // 9600 FCATSerialBaud[i] := Cmb; MakeLbl(Grp, 'Data bits:', PAD, R1 + 3 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 3 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('7'); Cmb.Items.Add('8'); Cmb.ItemIndex := 1; // 8 FCATSerialDataBits[i] := Cmb; MakeLbl(Grp, 'Stop bits:', PAD, R1 + 4 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 4 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('1'); Cmb.Items.Add('2'); Cmb.ItemIndex := 0; // 1 FCATSerialStopBits[i] := Cmb; MakeLbl(Grp, 'Parity:', PAD, R1 + 5 * STEP + 6, LW); Cmb := MakeCombo(Grp, CX, R1 + 5 * STEP, CMB_W, OnCATSerialChange); Cmb.Tag := i; Cmb.Items.Add('None'); Cmb.Items.Add('Odd'); Cmb.Items.Add('Even'); Cmb.ItemIndex := 0; // None FCATSerialParity[i] := Cmb; end; // TCP server group below the two rows of serial port panels gy := 86 + 2 * (GRP_H + GRP_GAP); Grp := MakeGroupPanel(FPageCAT, 'TCP CAT Server', MARGIN, gy, GRP_W * 2 + GRP_GAP, 116); FCATTcpEn := TCheckBox.Create(Self); FCATTcpEn.Parent := Grp; FCATTcpEn.Caption := 'Enable TCP server (default port 4992)'; FCATTcpEn.SetBounds(PAD, R1, 300, 22); FCATTcpEn.Font.Color := CLR_TEXT; FCATTcpEn.Font.Name := UI_FONT; FCATTcpEn.Font.Size := 9; FCATTcpEn.OnChange := OnCATTcpChange; MakeLbl(Grp, 'Port:', PAD, R1 + STEP + 6, LW); Spin := TSpinEdit.Create(Self); Spin.Parent := Grp; Spin.SetBounds(CX, R1 + STEP, 100, BTN_H + 2); Spin.Color := CLR_INPUT; Spin.Font.Color := CLR_INPUT_TEXT; Spin.Font.Name := UI_FONT; Spin.Font.Size := 9; Spin.MinValue := 1; Spin.MaxValue := 65535; Spin.Value := 4992; 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; end.