mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27:33 +00:00
QO-100 reuses the transverter path (VFO/band-stack/beacon-lock untouched) with a split LO for the geostationary transponder: - TXvtrEntry += FullDuplexTx + TxLOOffset (persisted). Reserve the last XVTR slot (QO100_SLOT) for QO-100; migration restores the template when the slot is unset or has empty offsets (fixes old configs showing zeros). - XvtrTranslateTX: TX LO = visible - TxLOOffset (no LNB LOError) for QO-100; identical to RX translate for normal transverters. Switched all TX-side translate calls (controller + MainForm). - SetDuplex wired (live KeepRXDuringTX); auto display-duplex on QO-100 entry. - New "QO-100" settings page (LNB LO / transponder offset / downlink edges / RX gain / TX ceiling + live RX-IF/TX readout); QO slot hidden from the transverter table. Reuses OnXvtrChange — no MainForm wiring needed. - doc/PLUTO_INTEGRATION_PLAN.md: phase 5 done. Builds clean (headless + qt6 GUI). Not yet verified on hardware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3578 lines
127 KiB
ObjectPascal
3578 lines
127 KiB
ObjectPascal
unit SettingsForm;
|
||
|
||
{
|
||
SettingsForm.pas — диалог настроек EWSDR SDR приложения.
|
||
|
||
Вкладки: General | Audio | Display | DSP | Transmit | PA Settings | CAT
|
||
Display имеет подвкладки: General | RX1 | RX2 | TX
|
||
|
||
Все изменения применяются МГНОВЕННО через events:
|
||
OnDisplayChange — изменены FFT/Window/Detector/Avg параметры
|
||
OnWaterfallChange — изменены параметры водопада
|
||
OnGridChange — изменена сетка спектра
|
||
OnAudioChange — изменено аудио устройство/буфер
|
||
OnSampleRateChange — изменён audio output sample rate (только PortAudio, DDC не трогается)
|
||
|
||
Для применения настроек: создайте форму, зарегистрируйте callbacks,
|
||
потом вызовите Show или ShowModal.
|
||
}
|
||
|
||
{$IFDEF FPC}
|
||
{$MODE Delphi}
|
||
{$ENDIF}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, Math,
|
||
Forms, Controls, Graphics, Dialogs,
|
||
StdCtrls, ExtCtrls,
|
||
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;
|
||
// Pluto TX: аттенюация выходного тракта на drive=100% (потолок), дБ<0
|
||
TOnPlutoTxChange = procedure(MaxAttDb: 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;
|
||
TOnSendAudioToRadioChange = procedure(Enabled: Boolean) of object;
|
||
TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall, ShowWideband,
|
||
WidebandFill, SpectrumFill: 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;
|
||
FChkSendAudioToRadio: TFlatCheckBox;
|
||
|
||
// ---- 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;
|
||
FChkSpectrumFill: TFlatCheckBox;
|
||
FCmbFPS: TFlatComboBox;
|
||
FChkLightTheme: TFlatCheckBox;
|
||
FCmbFreqMhzDigits: TFlatComboBox;
|
||
|
||
// ---- PA Settings tab controls ----
|
||
FEdMaxPower: TFlatSpinEdit;
|
||
FEdPlutoTxMaxAtt: TFlatFloatSpinEdit; // Pluto TX att на drive=100%
|
||
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;
|
||
|
||
// ---- QO-100 tab (редактирует зарезервированный слот QO100_SLOT) ----
|
||
FPageQO: TScrollBox;
|
||
FNavQO: TFlatButton;
|
||
FQoEn: TFlatCheckBox;
|
||
FQoLnbLO: TFlatFloatSpinEdit; // МГц — LNB LO (= LOOffset, RX)
|
||
FQoTxLO: TFlatFloatSpinEdit; // МГц — транспондер-offset (= TxLOOffset)
|
||
FQoBegin: TFlatFloatSpinEdit; // МГц — downlink begin
|
||
FQoEnd: TFlatFloatSpinEdit; // МГц — downlink end
|
||
FQoRXGain: TFlatFloatSpinEdit; // dB
|
||
FQoPower: TFlatSpinEdit; // 0..100 TX drive ceiling
|
||
FQoInfo: TLabel; // расчёт RX IF / TX freq
|
||
|
||
// ---- 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;
|
||
FOnPlutoTxChange: TOnPlutoTxChange;
|
||
FOnVHFCalChange: TOnVHFCalChange;
|
||
FOnADCChange: TOnADCChange;
|
||
FOnWebSettingsChange: TOnWebSettingsChange;
|
||
FOnDisplayChange: TOnDisplayParamChange;
|
||
FOnWaterfallChange: TOnWaterfallParamChange;
|
||
FOnGridChange: TOnGridParamChange;
|
||
FOnAudioDevChange: TOnAudioDevChange;
|
||
FOnAudioInDevChange: TOnAudioInDevChange;
|
||
FOnAudioBufferChange: TOnAudioBufferChange;
|
||
FOnSendAudioToRadioChange: TOnSendAudioToRadioChange;
|
||
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 BuildQOTab;
|
||
procedure CollectQOFromUI; // QO-виджеты → FXvtrConfig[QO100_SLOT]
|
||
procedure LoadQOFromConfig; // FXvtrConfig[QO100_SLOT] → QO-виджеты
|
||
procedure UpdateQOInfo; // пересчёт RX IF / TX freq в инфо-метке
|
||
procedure OnQOAnyChange(Sender: TObject);
|
||
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 OnSendAudioToRadioChkChange(Sender: TObject);
|
||
procedure OnVisibilityChkChange(Sender: TObject);
|
||
procedure OnFPSCmbChange(Sender: TObject);
|
||
procedure OnLightThemeChkChange(Sender: TObject);
|
||
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
|
||
procedure OnMaxPowerChange(Sender: TObject);
|
||
procedure OnPlutoTxMaxAttChange(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 LoadSendAudioToRadio(Enabled: Boolean);
|
||
procedure LoadVisibility(ShowSpectrum, ShowWaterfall, ShowWideband,
|
||
WidebandFill, SpectrumFill: Boolean);
|
||
procedure LoadFPS(FPS: Integer);
|
||
procedure LoadLightTheme(ALight: Boolean);
|
||
procedure LoadFreqMhzDigits(Digits: Integer);
|
||
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
|
||
procedure LoadPlutoTxMaxAtt(MaxAttDb: 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 OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
|
||
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 OnSendAudioToRadioChange: TOnSendAudioToRadioChange read FOnSendAudioToRadioChange write FOnSendAudioToRadioChange;
|
||
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);
|
||
FNavQO := MakeNavButton('QO-100', 434);
|
||
|
||
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;
|
||
FPageQO := MakeScrollPage;
|
||
FAlexBoardType := 0;
|
||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||
TSettingsManager.DefaultXvtr(FXvtrConfig);
|
||
|
||
BuildAudioTab;
|
||
BuildDisplayTab;
|
||
BuildRX1Tab;
|
||
BuildTransmitTab;
|
||
BuildPATab;
|
||
BuildAdvancedTab;
|
||
BuildCATTab;
|
||
BuildAlexTab;
|
||
BuildXvtrTab;
|
||
BuildQOTab;
|
||
|
||
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)
|
||
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
|
||
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;
|
||
FPageQO.Visible := APage = FPageQO;
|
||
|
||
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;
|
||
FNavQO.Active := ANav = FNavQO;
|
||
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;
|
||
|
||
Grp := MakeGroupPanel(FPageAudio, 'Transceiver Audio', MARGIN, 290, 650, 92);
|
||
|
||
FChkSendAudioToRadio := TFlatCheckBox.Create(Self);
|
||
FChkSendAudioToRadio.Parent := Grp;
|
||
FChkSendAudioToRadio.Caption := 'Send audio stream to radio (built-in speaker)';
|
||
FChkSendAudioToRadio.SetBounds(PAD, R1, 420, 22);
|
||
FChkSendAudioToRadio.Checked := False;
|
||
FChkSendAudioToRadio.Font.Color := CLR_TEXT;
|
||
FChkSendAudioToRadio.Font.Name := UI_FONT;
|
||
FChkSendAudioToRadio.Font.Size := 9;
|
||
FChkSendAudioToRadio.OnChange := OnSendAudioToRadioChkChange;
|
||
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;
|
||
|
||
FChkSpectrumFill := TFlatCheckBox.Create(Self);
|
||
FChkSpectrumFill.Parent := Grp;
|
||
FChkSpectrumFill.Caption := 'Spectrum gradient fill';
|
||
FChkSpectrumFill.SetBounds(PAD, R1 + 28, CHKW, 22);
|
||
FChkSpectrumFill.Checked := True;
|
||
FChkSpectrumFill.Font.Color := CLR_TEXT;
|
||
FChkSpectrumFill.Font.Name := UI_FONT;
|
||
FChkSpectrumFill.Font.Size := 9;
|
||
FChkSpectrumFill.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;
|
||
|
||
// Pluto TX max attenuation @ drive 100% (потолок под внешний усилитель), дБ<0
|
||
Lbl := TLabel.Create(Self);
|
||
Lbl.Parent := Grp;
|
||
Lbl.Caption := 'Pluto TX att @100% (dB)';
|
||
Lbl.SetBounds(GRP_PAD + 320, R1 + 6, 180, 18);
|
||
Lbl.Font.Color := CLR_TEXTDIM;
|
||
Lbl.Font.Name := UI_FONT;
|
||
Lbl.Font.Size := 9;
|
||
|
||
FEdPlutoTxMaxAtt := TFlatFloatSpinEdit.Create(Self);
|
||
FEdPlutoTxMaxAtt.Parent := Grp;
|
||
FEdPlutoTxMaxAtt.SetBounds(GRP_PAD + 510, R1, ED_W, BTN_H + 2);
|
||
FEdPlutoTxMaxAtt.Color := CLR_INPUT;
|
||
FEdPlutoTxMaxAtt.Font.Color := CLR_INPUT_TEXT;
|
||
FEdPlutoTxMaxAtt.Font.Name := UI_FONT;
|
||
FEdPlutoTxMaxAtt.Font.Size := 9;
|
||
FEdPlutoTxMaxAtt.DecimalPlaces := 2;
|
||
FEdPlutoTxMaxAtt.MinValue := -50;
|
||
FEdPlutoTxMaxAtt.MaxValue := 0;
|
||
FEdPlutoTxMaxAtt.Increment := 0.25;
|
||
FEdPlutoTxMaxAtt.Value := -10;
|
||
FEdPlutoTxMaxAtt.OnChange := OnPlutoTxMaxAttChange;
|
||
|
||
// 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;
|
||
|
||
procedure TSettingsForm.LoadSendAudioToRadio(Enabled: Boolean);
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
FChkSendAudioToRadio.Checked := Enabled;
|
||
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.OnSendAudioToRadioChkChange(Sender: TObject);
|
||
begin
|
||
if FLoading then Exit;
|
||
if Assigned(FOnSendAudioToRadioChange) then
|
||
FOnSendAudioToRadioChange(FChkSendAudioToRadio.Checked);
|
||
end;
|
||
|
||
procedure TSettingsForm.LoadVisibility(ShowSpectrum, ShowWaterfall,
|
||
ShowWideband, WidebandFill, SpectrumFill: Boolean);
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
FChkShowSpectrum.Checked := ShowSpectrum;
|
||
FChkShowWaterfall.Checked := ShowWaterfall;
|
||
FChkShowWideband.Checked := ShowWideband;
|
||
FChkWidebandFill.Checked := WidebandFill;
|
||
FChkSpectrumFill.Checked := SpectrumFill;
|
||
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, FChkSpectrumFill.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.LoadPlutoTxMaxAtt(MaxAttDb: Double);
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
FEdPlutoTxMaxAtt.Value := EnsureRange(MaxAttDb, -50.0, 0.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.OnPlutoTxMaxAttChange(Sender: TObject);
|
||
begin
|
||
if FLoading then Exit;
|
||
if Assigned(FOnPlutoTxChange) then FOnPlutoTxChange(FEdPlutoTxMaxAtt.Value);
|
||
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" --- (последний слот зарезервирован под QO-100 — не строим)
|
||
GrpY := 100;
|
||
Grp := MakeGroupPanel(FPageXvtr, 'Slots',
|
||
MARGIN, GrpY, GRP_W, GRP_TITLE_H + HDR_H + QO100_SLOT * 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);
|
||
|
||
// Ряды слотов (QO100_SLOT исключён — у него отдельная страница QO-100)
|
||
for i := 0 to QO100_SLOT - 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);
|
||
// диапазон ±10 МГц: QO-100 LNB без опоры легко уходит на сотни кГц, и
|
||
// beacon-lock складывает свою коррекцию сюда (живая калибровка LNB).
|
||
FXvtrLOErr[i] := MakeFlt(Grp, ERR_X, Y, ERR_W, -9999999.0, 9999999.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 + QO100_SLOT * 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; // слоты 0..QO100_SLOT-1 (виджеты QO-слота не созданы → skip)
|
||
CollectQOFromUI; // QO100_SLOT — из QO-страницы
|
||
// Перерисовать 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;
|
||
LoadQOFromConfig; // QO100_SLOT → отдельная страница QO-100
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
// ── QO-100 page ─────────────────────────────────────────────────────────────
|
||
|
||
procedure TSettingsForm.BuildQOTab;
|
||
const
|
||
MARGIN = 22;
|
||
LBL_X = 14; LBL_W = 230;
|
||
ED_X = 250; ED_W = 130;
|
||
var
|
||
Grp: TPanel;
|
||
L: TLabel;
|
||
Y: Integer;
|
||
|
||
function QoFloat(AY: Integer; AMin, AMax, AStep: Double; ADec: Integer): TFlatFloatSpinEdit;
|
||
begin
|
||
Result := TFlatFloatSpinEdit.Create(Self);
|
||
Result.Parent := Grp;
|
||
Result.SetBounds(ED_X, AY, ED_W, 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 := OnQOAnyChange;
|
||
end;
|
||
|
||
procedure QoLabel(const Cap: string; AY: Integer);
|
||
begin
|
||
L := TLabel.Create(Self);
|
||
L.Parent := Grp; L.Caption := Cap;
|
||
L.SetBounds(LBL_X, AY + 4, LBL_W, 18);
|
||
L.Font.Color := CLR_TEXTDIM; L.Font.Name := UI_FONT; L.Font.Size := 9;
|
||
end;
|
||
|
||
begin
|
||
FLoading := True;
|
||
try
|
||
L := TLabel.Create(Self);
|
||
L.Parent := FPageQO; L.Caption := 'QO-100 (Es''hail-2 NB transponder)';
|
||
L.SetBounds(MARGIN, 18, 600, 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 := FPageQO;
|
||
L.Caption := 'Geostationary transponder: RX downlink (~10489.5 MHz) via LNB, ' +
|
||
'TX uplink (~2400 MHz) direct. VFO is displayed in downlink terms; ' +
|
||
'RX and TX use separate LOs. Drift is corrected by the beacon lock.';
|
||
L.SetBounds(MARGIN, 50, 700, 34);
|
||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||
L.WordWrap := True;
|
||
|
||
Grp := MakeGroupPanel(FPageQO, 'Transponder', MARGIN, 100, 560, GRP_TITLE_H + 7 * 30 + 16);
|
||
|
||
Y := GRP_TITLE_H + 8;
|
||
FQoEn := TFlatCheckBox.Create(Self);
|
||
FQoEn.Parent := Grp; FQoEn.Caption := 'Enable QO-100 band';
|
||
FQoEn.SetBounds(LBL_X, Y, 300, 20);
|
||
FQoEn.Font.Color := CLR_TEXT; FQoEn.Font.Name := UI_FONT; FQoEn.Font.Size := 9;
|
||
FQoEn.OnChange := OnQOAnyChange;
|
||
|
||
Inc(Y, 32); QoLabel('LNB LO (MHz)', Y);
|
||
FQoLnbLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||
FQoLnbLO.Value := 9750.0;
|
||
|
||
Inc(Y, 30); QoLabel('Transponder offset = downlink - uplink (MHz)', Y);
|
||
FQoTxLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||
FQoTxLO.Value := 8089.5;
|
||
|
||
Inc(Y, 30); QoLabel('Downlink begin (MHz)', Y);
|
||
FQoBegin := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||
FQoBegin.Value := 10489.5;
|
||
|
||
Inc(Y, 30); QoLabel('Downlink end (MHz)', Y);
|
||
FQoEnd := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||
FQoEnd.Value := 10490.0;
|
||
|
||
Inc(Y, 30); QoLabel('RX gain offset (dB)', Y);
|
||
FQoRXGain := QoFloat(Y, -60.0, 60.0, 0.1, 1);
|
||
|
||
Inc(Y, 30); QoLabel('TX drive ceiling (%)', Y);
|
||
FQoPower := TFlatSpinEdit.Create(Self);
|
||
FQoPower.Parent := Grp; FQoPower.SetBounds(ED_X, Y, ED_W, BTN_H);
|
||
FQoPower.MinValue := 0; FQoPower.MaxValue := 100; FQoPower.Value := 100;
|
||
FQoPower.Color := CLR_INPUT; FQoPower.Font.Color := CLR_INPUT_TEXT;
|
||
FQoPower.Font.Name := UI_FONT; FQoPower.Font.Size := 9;
|
||
FQoPower.OnChange := OnQOAnyChange;
|
||
|
||
FQoInfo := TLabel.Create(Self);
|
||
FQoInfo.Parent := FPageQO;
|
||
FQoInfo.SetBounds(MARGIN, 100 + GRP_TITLE_H + 7 * 30 + 28, 700, 52);
|
||
FQoInfo.Font.Color := CLR_TEXTDIM; FQoInfo.Font.Name := 'Courier New';
|
||
FQoInfo.Font.Size := 9; FQoInfo.WordWrap := True;
|
||
finally
|
||
FLoading := False;
|
||
end;
|
||
end;
|
||
|
||
procedure TSettingsForm.UpdateQOInfo;
|
||
var dlMid, rxIf, txF: Double;
|
||
begin
|
||
if FQoInfo = nil then Exit;
|
||
dlMid := (FQoBegin.Value + FQoEnd.Value) / 2.0; // МГц
|
||
rxIf := dlMid - FQoLnbLO.Value; // RX IF в SDR
|
||
txF := dlMid - FQoTxLO.Value; // TX uplink
|
||
FQoInfo.Caption := Format(
|
||
'At downlink %.3f MHz: RX IF = %.3f MHz TX = %.3f MHz',
|
||
[dlMid, rxIf, txF]);
|
||
end;
|
||
|
||
procedure TSettingsForm.CollectQOFromUI;
|
||
begin
|
||
if FQoEn = nil then Exit;
|
||
FXvtrConfig.Entries[QO100_SLOT].Enabled := FQoEn.Checked;
|
||
FXvtrConfig.Entries[QO100_SLOT].FullDuplexTx := True; // слот всегда QO-100
|
||
FXvtrConfig.Entries[QO100_SLOT].ButtonText := 'QO-100';
|
||
FXvtrConfig.Entries[QO100_SLOT].LOOffset := FQoLnbLO.Value * 1.0e6;
|
||
FXvtrConfig.Entries[QO100_SLOT].TxLOOffset := FQoTxLO.Value * 1.0e6;
|
||
FXvtrConfig.Entries[QO100_SLOT].FreqBegin := FQoBegin.Value * 1.0e6;
|
||
FXvtrConfig.Entries[QO100_SLOT].FreqEnd := FQoEnd.Value * 1.0e6;
|
||
FXvtrConfig.Entries[QO100_SLOT].RXGain := FQoRXGain.Value;
|
||
FXvtrConfig.Entries[QO100_SLOT].TXPower := FQoPower.Value;
|
||
end;
|
||
|
||
procedure TSettingsForm.LoadQOFromConfig;
|
||
var E: TXvtrEntry;
|
||
begin
|
||
if FQoEn = nil then Exit;
|
||
E := FXvtrConfig.Entries[QO100_SLOT];
|
||
FQoEn.Checked := E.Enabled;
|
||
FQoLnbLO.Value := E.LOOffset / 1.0e6;
|
||
FQoTxLO.Value := E.TxLOOffset / 1.0e6;
|
||
FQoBegin.Value := E.FreqBegin / 1.0e6;
|
||
FQoEnd.Value := E.FreqEnd / 1.0e6;
|
||
FQoRXGain.Value := E.RXGain;
|
||
FQoPower.Value := EnsureRange(E.TXPower, 0, 100);
|
||
UpdateQOInfo;
|
||
end;
|
||
|
||
procedure TSettingsForm.OnQOAnyChange(Sender: TObject);
|
||
begin
|
||
if FLoading then Exit;
|
||
UpdateQOInfo;
|
||
FireXvtrChange;
|
||
end;
|
||
|
||
end.
|