diff --git a/MainForm.pas b/MainForm.pas index 9034e5f..423c017 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -4649,10 +4649,12 @@ begin end; procedure TMainForm.ApplyBackendCapsToUI; -// Пресеты sample-rate оверлея из Caps (Pluto >560k) + (TODO) гейтинг полей, -// которых нет у бэкенда (PA/SWR/Alex/Wideband). +// Пресеты sample-rate оверлея из Caps (Pluto >560k) + гейтинг того, чего у +// бэкенда физически нет. Настройки гейтятся при открытии окна (SF.Set*Backend +// в BtnSettingsClick), здесь — то, что видно в главном окне. var Caps: TBackendCaps; + i: Integer; begin Caps := FController.BackendCaps; if Assigned(FSampleRateOverlay) then @@ -4665,6 +4667,21 @@ begin PushBandsToWeb; // band-план бэкенда → web (HF ↔ VHF/UHF) // Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже. + // Широкополосный АЦП есть только у openHPSDR. Галка на такой машине из + // настроек убрана, поэтому включённой её оставлять нельзя: панель осталась бы + // пустой, а выключить её стало бы нечем. + if (not Caps.HasWideband) and FController.FShowWideband then + ApplyVisibility(FController.FShowSpectrum, FController.FShowWaterfall, + False, FController.FWidebandFill, FController.FSpectrumFill); + + // Измеритель PWR/SWR: без моста детектора (AD936x) FLastFwdW/FLastSWR стоят + // на инициализации, и «0W / SWR 1.0» на передаче были бы выдумкой — шкала + // рисуется пустой. + if FSpecView <> nil then FSpecView.HasPATelemetry := Caps.HasPA; + for i := 0 to High(FPans) do + if (FPans[i] <> nil) and (FPans[i].View <> nil) then + FPans[i].View.HasPATelemetry := Caps.HasPA; + LayoutLeftPanel(Caps.HasHWGain); if Caps.HasHWGain then UpdateRxGainUI; UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен) @@ -7633,6 +7650,7 @@ begin P.View.SpecGridStep := FController.FSpecGridStep; P.View.WfFrameInterval := EffectiveWfInterval; P.View.PAMaxPower := FController.FPAMaxPower; + P.View.HasPATelemetry := FController.BackendCaps.HasPA; P.View.CenterFreq := Freq; P.View.SpanHz := FController.PanDDCRateKHz(PanId) * 1000.0; // VFO/TX-маркеры главного на доп. пане не рисуем — уводим за пределы окна. @@ -9767,6 +9785,16 @@ begin SF.LoadOCSettings(FController.FOCSettings); // Вкладка OC Control — только openHPSDR, у Pluto/AD936x нет такого выхода. SF.SetOCBackend(FController.BackendCaps.Kind = bkPluto); + // Вкладка QO-100 — ровно наоборот: транспондер живёт только на AD936x + // (TRadioController.InQO100 требует IsPluto). + SF.SetQOBackend(FController.BackendCaps.Kind = bkPluto); + // Остальные гейты — по объявленным возможностям бэкенда, а не по его виду: + // мост детектора мощности и drive-байт (PA), микрофонный разъём трансивера, + // широкополосный АЦП, dither/random. Всё это есть у openHPSDR и нет у AD936x. + SF.SetPABackend(FController.BackendCaps.HasPA); + SF.SetTXMicBackend(FController.BackendCaps.HasHWMic); + SF.SetWidebandBackend(FController.BackendCaps.HasWideband); + SF.SetADCBackend(FController.BackendCaps.HasDitherRandom); SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal); SF.LoadCalibration(FController.FCal); SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb); diff --git a/SMeterView.pas b/SMeterView.pas index 01321e9..58e2384 100644 --- a/SMeterView.pas +++ b/SMeterView.pas @@ -30,6 +30,11 @@ type FLastFwdW: Double; FLastSWR: Double; FPAMaxPower: Double; + // Есть ли у железа мост детектора мощности (Caps.HasPA). У AD936x его нет: + // FLastFwdW/FLastSWR так и стоят на значениях инициализации, и «0W / SWR + // 1.0» на передаче — не показание, а выдумка. Шкала в этом случае рисуется + // пустой: деления есть, заливки и цифр нет. + FHasPATelemetry: Boolean; FTransmitting: Boolean; FPbSMeterRight: TPaintBox; // Масштаб канвы: 2 на Retina, 1 на Windows/Linux. Битмап растёт во столько @@ -52,6 +57,7 @@ type property LastFwdW: Double read FLastFwdW write FLastFwdW; property LastSWR: Double read FLastSWR write FLastSWR; property PAMaxPower: Double read FPAMaxPower write FPAMaxPower; + property HasPATelemetry: Boolean read FHasPATelemetry write FHasPATelemetry; property Transmitting: Boolean read FTransmitting write FTransmitting; property PbSMeterRight: TPaintBox write FPbSMeterRight; @@ -76,6 +82,7 @@ begin FLastFwdW := 0; FLastSWR := 1; FPAMaxPower := 100.0; + FHasPATelemetry := True; FTransmitting := False; FScale := 1; end; @@ -358,9 +365,17 @@ begin W := R.Right - R.Left; H := R.Bottom - R.Top; if (W < 80 * Sc) or (H < 16 * Sc) then Exit; - SWRHigh := FLastSWR > SWR_HIGH_THRESH; - LblPwr := Format('%.0fW', [FLastFwdW]); - LblSWR := Format('SWR:%.1f', [FLastSWR]); + SWRHigh := FHasPATelemetry and (FLastSWR > SWR_HIGH_THRESH); + if FHasPATelemetry then + begin + LblPwr := Format('%.0fW', [FLastFwdW]); + LblSWR := Format('SWR:%.1f', [FLastSWR]); + end + else + begin + LblPwr := ''; // измерять нечем — показания не выдумываем + LblSWR := ''; + end; ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Size := 10 * Sc; @@ -394,18 +409,24 @@ begin FillBar(BX, SY1, S_WarnX, SY2, CLR_BAR_GREEN); FillBar(S_WarnX, SY1, BX + BW, SY2, CLR_BAR_OVER); - if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0; - PBarEnd := PwrToX(PwrPct); - FillBar(BX, PY1 + Sc, PBarEnd, PY2 - Sc, SM_CLR_METER_ON); - - SBarEnd := SWRToX(FLastSWR); - if not SWRHigh then - FillBar(BX, SY1 + Sc, SBarEnd, SY2 - Sc, SM_CLR_AMBER) - else + if FHasPATelemetry then begin - FillBar(BX, SY1 + Sc, Min(S_WarnX, SBarEnd), SY2 - Sc, SM_CLR_AMBER); - if SBarEnd > S_WarnX then - FillBar(S_WarnX, SY1 + Sc, SBarEnd, SY2 - Sc, CLR_SWR_HIGH); + if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0; + PBarEnd := PwrToX(PwrPct); + FillBar(BX, PY1 + Sc, PBarEnd, PY2 - Sc, SM_CLR_METER_ON); + end; + + if FHasPATelemetry then + begin + SBarEnd := SWRToX(FLastSWR); + if not SWRHigh then + FillBar(BX, SY1 + Sc, SBarEnd, SY2 - Sc, SM_CLR_AMBER) + else + begin + FillBar(BX, SY1 + Sc, Min(S_WarnX, SBarEnd), SY2 - Sc, SM_CLR_AMBER); + if SBarEnd > S_WarnX then + FillBar(S_WarnX, SY1 + Sc, SBarEnd, SY2 - Sc, CLR_SWR_HIGH); + end; end; VLine(S_WarnX, SY1, SY2, FTheme.SMeterDivider); @@ -437,6 +458,9 @@ begin begin X := PwrToX(PWR_MARKS[i]); VLine(X, PY1 - TICK_LONG * Sc, PY1 - Sc, CLR_TICK_WHITE); + // Ватты на делениях считаются от Max power, а он есть только там, где есть + // детектор. Без него оставляем голые деления: подписать их нечем. + if not FHasPATelemetry then Continue; Lbl := Format('%.0f', [PWR_MARKS[i] * FPAMaxPower]); TW := ACanvas.TextWidth(Lbl); ACanvas.Font.Color := CLR_SCALE_DBM; diff --git a/SettingsForm.pas b/SettingsForm.pas index a933b98..aeb0ee8 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -210,6 +210,17 @@ type FEdBandCal: array[0..10] of TFlatFloatSpinEdit; FEdVHFCal: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; FVHFCalLabels: array[0..CFG_XVTR_COUNT-1] of TLabel; + // Раскладку страницы PA ставит RelayoutPATab под активное железо: у AD936x + // нет ни моста детектора, ни drive-байта — мощность там задаётся + // аттенюацией, поэтому из всей страницы остаётся один потолок. + FPAGrpPower: TPanel; + FPAGrpVHF: TPanel; + FLblPAHint: TLabel; + FLblPAMaxPower: TLabel; + FLblPlutoTxAtt: TLabel; + FLblPABandHdr: TLabel; + FPABandLabels: array[0..10] of TLabel; + FPAHasPA: Boolean; // ---- Calibration tab controls ---- // Таблицы «на диапазон» строятся на 12 слотов (максимум плана); подписи, @@ -242,6 +253,7 @@ type FLblDispBandHdr: TLabel; FLblDispXvtrHdr: TLabel; FBtnDispFromSM: TFlatButton; // «= S-meter» — копия таблицы + FLblCalHint: TLabel; // подзаголовок страницы, зависит от железа FCalGrpFreq: TPanel; // Reference frequency FEdFreqCalPPM: TFlatFloatSpinEdit; // калибровка опорника, ppm FCalIsPluto: Boolean; // активное железо — AD936x @@ -274,7 +286,16 @@ type FChkTXMicPTT: TFlatCheckBox; FCmbTXMicTR: TFlatComboBox; // "Tip" / "Ring" (только Orion) FLblTipRing: TLabel; // метка (только Orion) + FLblMicJack: TLabel; // метка "Mic jack" + FLblMicGain: TLabel; // метка "Mic gain (dB)" FEdTXMicGain: TFlatFloatSpinEdit; + // Группы подвкладки Profile — их двигает RelayoutTXProfileSub, когда у + // железа нет микрофонного разъёма (AD936x: звук только с ПК). + FTXGrpMic: TPanel; + FTXGrpFilt: TPanel; + FTXGrpDyn: TPanel; + FTXGrpPwr: TPanel; + FTXHasHWMic: Boolean; FEdTXAttOnTX: TFlatSpinEdit; // Tune (TUN button) FEdTUNLevel: TFlatSpinEdit; @@ -470,6 +491,21 @@ type // ---- Advanced tab controls ---- FChkDither: TFlatCheckBox; FChkRandom: TFlatCheckBox; + // Группа ADC прячется целиком у AD936x (SetADCBackend), Web Server встаёт + // на её место — иначе страница начиналась бы с пустоты. + FAdvGrpADC: TPanel; + FAdvGrpWeb: TPanel; + FLblAdvHint: TLabel; + FAdvHasADC: Boolean; + // ★Перекладка страницы возможна только когда страница ВИДИМА: сброс + // прокрутки не доходит до спрятанного виджета, а сохранённое смещение + // возвращается при показе — и блоки разъезжаются. Поэтому перекладка + // спрятанной страницы лишь взводит свой флаг, а исполняется из SelectPage + // в момент показа (RunDueRelayout). + FPARelayoutDue: Boolean; + FTXRelayoutDue: Boolean; + FCalRelayoutDue: Boolean; + FAdvRelayoutDue: Boolean; FChkWebEnabled: TFlatCheckBox; FEdWebPort: TFlatSpinEdit; FEdWebBind: TFlatEdit; @@ -531,7 +567,12 @@ type procedure BuildRX1Tab; procedure BuildPATab; procedure BuildCalibrationTab; + function GroupTitle(Grp: TPanel): TLabel; + procedure RunDueRelayout(APage: TWinControl); procedure RelayoutCalTab; + procedure RelayoutPATab; + procedure RelayoutTXProfileSub; + procedure RelayoutAdvancedTab; // Раскладка блока офсетов «на диапазон + на трансвертер» внутри группы // (общая для S-метра и спектра). Возвращает нужную высоту группы. function LayoutCalOffsets(const BandLbl: array of TLabel; @@ -694,6 +735,9 @@ type procedure LoadCalibration(const C: TCalibration); // Железо вкладки Calibration: band-план и состав групп (Pluto/openHPSDR). procedure SetCalibrationBackend(IsPluto: Boolean); + // Мост детектора мощности и drive-байт есть только у openHPSDR: у AD936x + // мощность = железная аттенюация, и вся калибровка по диапазонам мертва. + procedure SetPABackend(HasPA: Boolean); // Вкладка Antenna: Alex-таблица (openHPSDR) либо таблица разъёмов AD936x. procedure SetAntennaBackend(IsAD936x, HasAntSwitch: Boolean); // Таблица разъёмов + имена/видимость слотов трансвертеров. @@ -716,6 +760,11 @@ type procedure LoadCWSettings(const C: TCWSettings); // Есть ли у активного железа кейер в прошивке (openHPSDR — да, AD936x — нет). procedure SetCWBackend(HasFWKeyer: Boolean); + // Микрофонный разъём трансивера (DUC Specific байт 50) — только openHPSDR. + procedure SetTXMicBackend(HasHWMic: Boolean); + // Широкополосный АЦП и dither/random — тоже свойства openHPSDR. + procedure SetWidebandBackend(HasWideband: Boolean); + procedure SetADCBackend(HasDitherRandom: Boolean); // Список TX-профилей + активный + drive активного (ползунок DRV живёт в // главном окне, здесь показываем его значение как часть профиля). procedure LoadTXProfiles(const Names: array of string; @@ -730,6 +779,8 @@ type procedure LoadOCSettings(const O: TOCSettings); // Вкладка OC Control — только openHPSDR (у Pluto/AD936x нет такого выхода). procedure SetOCBackend(IsPluto: Boolean); + // Вкладка QO-100 — наоборот, только AD936x (см. TRadioController.InQO100). + procedure SetQOBackend(IsPluto: Boolean); procedure LoadWfAGCNF(AGC, NF: Boolean); procedure LoadWfRender(Palette: Integer; Gamma: Double; ResLimit, SpeedDiv: Integer); @@ -1029,6 +1080,26 @@ begin Divider.Color := CLR_BORDER; end; +procedure TSettingsForm.RunDueRelayout(APage: TWinControl); +// Зовётся из SelectPage сразу после того, как страница стала видимой: только +// теперь у её TScrollBox осмысленная прокрутка, и перекладку можно исполнить. +begin + if (APage = FPagePA) and FPARelayoutDue then RelayoutPATab + else if (APage = FPageTransmit) and FTXRelayoutDue then RelayoutTXProfileSub + else if (APage = FPageCalib) and FCalRelayoutDue then RelayoutCalTab + else if (APage = FPageAdvanced) and FAdvRelayoutDue then RelayoutAdvancedTab; +end; + +function TSettingsForm.GroupTitle(Grp: TPanel): TLabel; +// Подпись группы MakeGroupPanel создаёт первой — до разделителя и до всего +// содержимого, — поэтому она всегда Controls[0]. Нужна там, где заголовок +// зависит от железа (страница PA). +begin + Result := nil; + if (Grp <> nil) and (Grp.ControlCount > 0) and (Grp.Controls[0] is TLabel) then + Result := TLabel(Grp.Controls[0]); +end; + function TSettingsForm.MakeScrollPage: TScrollBox; begin Result := TScrollBox.Create(Self); @@ -1556,6 +1627,9 @@ begin // Скрытые alClient-страницы LCL не всегда перекладывает до показа. // После переключения берём уже актуальный ClientWidth выбранной страницы. RelayoutResponsiveCards; + // Отложенная перекладка под железо — теперь страница видима, и сброс + // прокрутки внутри неё наконец доходит до виджета. + RunDueRelayout(APage); UpdateSettingsScrollBar; end; @@ -2019,9 +2093,10 @@ begin // шагом. Connector скрыт на не-Orion и потому делит строку с Mic gain — // спрятанная пара не оставляет дыры. Grp := MakeGroupPanel(FTXSubPage[0], 'Microphone', MARGIN, 0, GRP_W, 134); + FTXGrpMic := Grp; // Row 0: Mic jack | Boost (Mic In) либо Line In gain (Line In) | Bias | PTT - MakeLbl(Grp, 'Mic jack', PAD, R1 + 5, LW); + FLblMicJack := MakeLbl(Grp, 'Mic jack', PAD, R1 + 5, LW); FCmbTXMicJack := MkCmb(Grp, CX, R1, 110); FCmbTXMicJack.Items.Add('Mic In'); FCmbTXMicJack.Items.Add('Line In'); @@ -2037,7 +2112,10 @@ begin FChkTXMicPTT := MkChk(Grp, PAD + 596, R1, 130, 'Mic-jack PTT'); // Row 1: Mic gain | Connector Tip/Ring (только Orion boards 4 и 5) - MakeLbl(Grp, 'Mic gain (dB)', PAD, R1 + STEP + 5, LW); + // ★Mic gain — единственное здесь, что не про разъём: он идёт в WDSP + // (SetMicGain) и работает с любым источником, включая звук с ПК. При + // скрытом разъёме RelayoutTXProfileSub поднимает его в первую строку. + FLblMicGain := MakeLbl(Grp, 'Mic gain (dB)', PAD, R1 + STEP + 5, LW); FEdTXMicGain := MkFloat(Grp, CX, R1 + STEP, 10.0, -20.0, 60.0, 0.5); FLblTipRing := MakeLbl(Grp, 'Connector', PAD + 302, R1 + STEP + 5, 76); @@ -2051,6 +2129,7 @@ begin // ---- Кромки TX-фильтра (FIR/фаза/окно — на вкладке Hardware) ---- Y := 134 + 12; Grp := MakeGroupPanel(FTXSubPage[0], 'TX Filter (bandpass)', MARGIN, Y, GRP_W, 108); + FTXGrpFilt := Grp; MakeLbl(Grp, 'Low cut (Hz)', PAD, R1 + 5, LW); FEdTXFiltLow := MkSpin(Grp, CX, R1, 0, 8000, 200); @@ -2063,6 +2142,7 @@ begin // ---- Динамическая обработка ---- Inc(Y, 108 + 12); Grp := MakeGroupPanel(FTXSubPage[0], 'Dynamic processing', MARGIN, Y, GRP_W, 220); + FTXGrpDyn := Grp; FChkTXCompOn := MkChk(Grp, PAD, R1, 220, 'Compressor'); MakeLbl(Grp, 'Gain (dB)', PAD + 230, R1 + 5, 80); @@ -2089,6 +2169,7 @@ begin // ---- Мощность и несущая AM (тоже часть профиля) ---- Inc(Y, 220 + 12); Grp := MakeGroupPanel(FTXSubPage[0], 'Power / AM carrier', MARGIN, Y, GRP_W, 148); + FTXGrpPwr := Grp; MakeLbl(Grp, 'Tune level (%)', PAD, R1 + 5, LW); FEdTUNLevel := MkSpin(Grp, CX, R1, 0, 100, 10); @@ -2101,6 +2182,7 @@ begin R1 + STEP + 28, 26); FTXSubPage[0].Height := DpiScale(Y + 148 + 12); + FTXHasHWMic := True; // до подключения показываем полный (openHPSDR) вид // ========================================================================= // Подвкладка 1 — EQ (тоже профильный, но занимает целый экран) @@ -2566,12 +2648,142 @@ procedure TSettingsForm.UpdateMicJackVisibility; var LineIn: Boolean; begin if FCmbTXMicJack = nil then Exit; + // Без разъёма прятать нечего и показывать нечего: вся строка снята + // RelayoutTXProfileSub, и вернуть её отсюда было бы возвратом дыры. + if not FTXHasHWMic 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.SetTXMicBackend(HasHWMic: Boolean); +// HasHWMic — Caps.HasHWMic: микрофонный разъём самого трансивера. Всё, что +// стоит в строке разъёма, уезжает в DUC Specific байт 50 (openHPSDR); у AD936x +// такого разъёма нет, звук идёт только с ПК. +begin + if FTXHasHWMic = HasHWMic then Exit; + FTXHasHWMic := HasHWMic; + RelayoutTXProfileSub; +end; + +procedure TSettingsForm.RelayoutTXProfileSub; +// Подвкладка Profile: без микрофонного разъёма группа Microphone теряет целую +// строку, Mic gain поднимается в неё, а три группы ниже подтягиваются — иначе +// на месте снятой строки осталась бы дыра. +const + TX_MARGIN = 22; + TX_STEP = 42; + TX_R1 = 42; + TX_MIC_H = 134; + TX_FILT_H = 108; + TX_DYN_H = 220; + TX_PWR_H = 148; +var + MicH, GainY, Y: Integer; + + procedure Vis(C: TControl; V: Boolean); + begin + if C <> nil then C.Visible := V; + end; + +begin + if (FTXGrpMic = nil) or (FTXGrpFilt = nil) or (FTXGrpDyn = nil) + or (FTXGrpPwr = nil) or (FTXSubPage[0] = nil) then Exit; + if not FPageTransmit.Visible then begin FTXRelayoutDue := True; Exit; end; + FTXRelayoutDue := False; + + // ★Как и на вкладке Calibration: TScrollBox двигает детей при прокрутке, и + // SetBounds ниже задаёт координаты ОТНОСИТЕЛЬНО прокрученного клиента. + // Перекладка случается при смене трансивера на живой программе, то есть + // страницу к этому моменту уже успели пролистать, — и без сброса группы + // разъезжались: всё уезжало вверх, а Microphone оставался на месте. + FPageTransmit.VertScrollBar.Position := 0; + FPageTransmit.HorzScrollBar.Position := 0; + + Vis(FLblMicJack, FTXHasHWMic); + Vis(FCmbTXMicJack, FTXHasHWMic); + Vis(FChkTXMicBias, FTXHasHWMic); + Vis(FChkTXMicPTT, FTXHasHWMic); + if not FTXHasHWMic then + begin + // Пара «Boost ↔ Line In gain» обычно переключается UpdateMicJackVisibility; + // здесь снимаем обе, а тот выше уже знает, что возвращать их не должен. + Vis(FChkTXMicBoost, False); + Vis(FLblLineInGain, False); + Vis(FEdLineInGain, False); + Vis(FLblTipRing, False); + Vis(FCmbTXMicTR, False); + end + else + UpdateMicJackVisibility; // вернуть ту из пары, что положена по разъёму + + if FTXHasHWMic then + begin + GainY := TX_R1 + TX_STEP; + MicH := TX_MIC_H; + end + else + begin + GainY := TX_R1; + MicH := TX_MIC_H - TX_STEP; + end; + if FLblMicGain <> nil then FLblMicGain.Top := DpiScale(GainY + 5); + if FEdTXMicGain <> nil then FEdTXMicGain.Top := DpiScale(GainY); + + // Ширину групп ведёт RelayoutResponsiveCards — здесь только высота и Top. + FTXGrpMic.SetBounds(DpiScale(TX_MARGIN), 0, FTXGrpMic.Width, DpiScale(MicH)); + Y := MicH + 12; + FTXGrpFilt.Top := DpiScale(Y); + Inc(Y, TX_FILT_H + 12); + FTXGrpDyn.Top := DpiScale(Y); + Inc(Y, TX_DYN_H + 12); + FTXGrpPwr.Top := DpiScale(Y); + FTXSubPage[0].Height := DpiScale(Y + TX_PWR_H + 12); + UpdatePageBottomSpace(FPageTransmit); + if FActivePage = FPageTransmit then UpdateSettingsScrollBar; +end; + +procedure TSettingsForm.SetWidebandBackend(HasWideband: Boolean); +// Кадры широкополосного АЦП приходят только от openHPSDR. У AD936x галка +// открывала бы пустую панель, поэтому её здесь нет вовсе. +begin + if FChkShowWideband <> nil then FChkShowWideband.Visible := HasWideband; + if FChkWidebandFill <> nil then FChkWidebandFill.Visible := HasWideband; +end; + +procedure TSettingsForm.SetADCBackend(HasDitherRandom: Boolean); +// Dither/random — свойства АЦП openHPSDR; у AD936x такого понятия нет. +begin + if FAdvHasADC = HasDitherRandom then Exit; // не сбрасывать прокрутку зря + FAdvHasADC := HasDitherRandom; + RelayoutAdvancedTab; +end; + +procedure TSettingsForm.RelayoutAdvancedTab; +// Без dither/random группа ADC пуста — прячем её целиком, а Web Server +// поднимается на её место, иначе страница начиналась бы с пустоты. +const + ADV_ADC_H = 128; +begin + if (FAdvGrpADC = nil) or (FAdvGrpWeb = nil) or (FLblAdvHint = nil) then Exit; + if not FPageAdvanced.Visible then begin FAdvRelayoutDue := True; Exit; end; + FAdvRelayoutDue := False; + FPageAdvanced.VertScrollBar.Position := 0; + FPageAdvanced.HorzScrollBar.Position := 0; + if FAdvHasADC then + FLblAdvHint.Caption := 'ADC processing and built-in web server settings.' + else + FLblAdvHint.Caption := 'Built-in web server settings.'; + FAdvGrpADC.Visible := FAdvHasADC; + if FAdvHasADC then + FAdvGrpWeb.Top := DpiScale(86 + ADV_ADC_H + 16) + else + FAdvGrpWeb.Top := DpiScale(86); + UpdatePageBottomSpace(FPageAdvanced); + if FActivePage = FPageAdvanced then UpdateSettingsScrollBar; +end; + procedure TSettingsForm.OnMicJackChange(Sender: TObject); begin UpdateMicJackVisibility; @@ -2716,8 +2928,11 @@ begin 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]; + // Tip/Ring (Orion boards 4 и 5). ★Без микрофонного разъёма строку уже снял + // RelayoutTXProfileSub, и вернуть её отсюда значило бы вернуть дыру: этот + // Load зовётся при каждом открытии окна, а SetTXMicBackend — только на + // смене железа. + IsOrion := FTXHasHWMic and (BoardType in [4, 5]); if FLblTipRing <> nil then FLblTipRing.Visible := IsOrion; if FCmbTXMicTR <> nil then begin @@ -2862,13 +3077,20 @@ var Spin: TFlatSpinEdit; i, r, c, X, Y: Integer; begin - MakePageHeader(FPagePA, 'Power Amplifier', - 'Output limit and per-band calibration values.'); + // Подпись под заголовком зависит от железа (RelayoutPATab): у AD936x на + // странице остаётся один потолок аттенюации, и обещать «per-band calibration» + // там нечестно. Поэтому создаём её отдельно, а не через ASubtitle. + MakePageHeader(FPagePA, 'Power Amplifier', ''); + FLblPAHint := MakeTextBlock(FPagePA, '', SETTINGS_MARGIN, 48, + SETTINGS_CARD_W, 20); + FLblPAHint.Tag := TAG_PAGE_HEADER; Grp := MakeGroupPanel(FPagePA, 'Power && Calibration', MARGIN, 86, SETTINGS_CARD_W, GRP_H); + FPAGrpPower := Grp; // Max Power Lbl := MakeLbl(Grp, 'Max power (W)', GRP_PAD, R1 + 6, 132); + FLblPAMaxPower := Lbl; Spin := TFlatSpinEdit.Create(Self); Spin.Parent := Grp; @@ -2884,8 +3106,9 @@ begin FEdMaxPower := Spin; // Pluto TX max attenuation @ drive 100% (потолок под внешний усилитель), дБ<0 - Lbl := MakeLbl(Grp, 'Pluto TX att @100% (dB)', + Lbl := MakeLbl(Grp, 'TX att @100% drive (dB)', GRP_PAD + 320, R1 + 6, 180); + FLblPlutoTxAtt := Lbl; FEdPlutoTxMaxAtt := TFlatFloatSpinEdit.Create(Self); FEdPlutoTxMaxAtt.Parent := Grp; @@ -2903,6 +3126,7 @@ begin // Calibration label Lbl := MakeLbl(Grp, 'Calibration per band, 38.8 ... 100.0', GRP_PAD, R1 + ROW_H + 12, 320); + FLblPABandHdr := Lbl; // Band grid 4 columns × 3 rows for i := 0 to 10 do @@ -2913,6 +3137,7 @@ begin Y := R1 + ROW_H + 34 + r * ROW_H; Lbl := MakeLbl(Grp, PA_BAND_NAMES[i] + ':', X, Y + 5, LBL_W); + FPABandLabels[i] := Lbl; FEdBandCal[i] := MakeFloatSpin(Grp, X + LBL_W, Y, OnBandCalChange); FEdBandCal[i].Tag := i; @@ -2921,6 +3146,7 @@ begin // ── VHF Calibration (Transverters) ────────────────────────────────────── Grp := MakeGroupPanel(FPagePA, 'VHF Calibration (Transverters)', MARGIN, 86 + GRP_H + 14, SETTINGS_CARD_W, R1 + 4 * ROW_H + 44); + FPAGrpVHF := Grp; Lbl := MakeLbl(Grp, 'Calibration per transverter slot, 38.8 ... 100.0', GRP_PAD, R1, 420); @@ -2946,6 +3172,95 @@ begin FEdVHFCal[i].Tag := i; end; + FPAHasPA := True; // до подключения показываем полный (openHPSDR) вид + RelayoutPATab; +end; + +procedure TSettingsForm.SetPABackend(HasPA: Boolean); +// HasPA — Caps.HasPA активного бэкенда: мост детектора мощности, drive-байт и +// телеметрия питания. У AD936x ничего этого нет. +begin + if FPAHasPA = HasPA then Exit; + FPAHasPA := HasPA; + RelayoutPATab; +end; + +procedure TSettingsForm.RelayoutPATab; +// openHPSDR: потолок мощности + калибровка драйва по диапазонам и слотам +// трансвертера (всё это кормит CalcDriveByte). AD936x: мощность задаётся +// железной аттенюацией, drive-байта нет вовсе — на странице остаётся один +// потолок аттенюации, остальное прячется вместе со своей группой. +const + PA_MARGIN = 22; + PA_R1 = 42; + PA_ROW_H = 32; + PA_GRP_H = 222; + PA_GRP_PAD = 18; + PA_VHF_H = PA_R1 + 4 * PA_ROW_H + 44; +var + i, H: Integer; + Lbl: TLabel; +begin + if (FPAGrpPower = nil) or (FPAGrpVHF = nil) or (FLblPAHint = nil) then Exit; + if not FPagePA.Visible then begin FPARelayoutDue := True; Exit; end; + FPARelayoutDue := False; + // Как и на вкладке Calibration: SetBounds ниже задаёт координаты относительно + // прокрученного клиента, поэтому сперва возвращаем страницу в ноль. + FPagePA.VertScrollBar.Position := 0; + FPagePA.HorzScrollBar.Position := 0; + + if FPAHasPA then + FLblPAHint.Caption := 'Output limit and per-band calibration values.' + else + FLblPAHint.Caption := 'Transmit power is set by hardware attenuation; ' + + 'there is no power detector to calibrate.'; + + // Заголовок группы — по её содержимому: калибровки у AD936x не остаётся. + Lbl := GroupTitle(FPAGrpPower); + if Lbl <> nil then + if FPAHasPA then Lbl.Caption := 'Power && Calibration' + else Lbl.Caption := 'Transmit power'; + + if FLblPAMaxPower <> nil then FLblPAMaxPower.Visible := FPAHasPA; + if FEdMaxPower <> nil then FEdMaxPower.Visible := FPAHasPA; + if FLblPABandHdr <> nil then FLblPABandHdr.Visible := FPAHasPA; + for i := 0 to High(FPABandLabels) do + begin + if FPABandLabels[i] <> nil then FPABandLabels[i].Visible := FPAHasPA; + if FEdBandCal[i] <> nil then FEdBandCal[i].Visible := FPAHasPA; + end; + FPAGrpVHF.Visible := FPAHasPA; + + // Аттенюация — свойство AD936x. У openHPSDR мощность идёт drive-байтом, и + // поле было бы мёртвым; у AD936x оно единственное и переезжает в первую + // колонку, чтобы не висеть посреди пустой группы. + if (FLblPlutoTxAtt <> nil) and (FEdPlutoTxMaxAtt <> nil) then + begin + FLblPlutoTxAtt.Visible := not FPAHasPA; + FEdPlutoTxMaxAtt.Visible := not FPAHasPA; + if FPAHasPA then + begin + FLblPlutoTxAtt.Left := DpiScale(PA_GRP_PAD + 320); + FEdPlutoTxMaxAtt.Left := DpiScale(PA_GRP_PAD + 510); + end + else + begin + FLblPlutoTxAtt.Left := DpiScale(PA_GRP_PAD); + FEdPlutoTxMaxAtt.Left := DpiScale(PA_GRP_PAD + 190); + end; + end; + + if FPAHasPA then H := PA_GRP_H + else H := PA_R1 + BTN_H + 2 + 18; // ровно одна строка + // Ширину не трогаем: карточки тянет по окну RelayoutResponsiveCards, и + // фиксированная ширина здесь схлопнула бы их до базовой. + FPAGrpPower.SetBounds(DpiScale(PA_MARGIN), DpiScale(86), + FPAGrpPower.Width, DpiScale(H)); + if FPAHasPA then + FPAGrpVHF.SetBounds(DpiScale(PA_MARGIN), DpiScale(86 + H + 14), + FPAGrpVHF.Width, DpiScale(PA_VHF_H)); + UpdatePageBottomSpace(FPagePA); + if FActivePage = FPagePA then UpdateSettingsScrollBar; end; procedure TSettingsForm.BuildCalibrationTab; @@ -3004,8 +3319,12 @@ begin FCalXvtrName[i] := ''; end; - MakePageHeader(FPageCalib, 'Calibration', - 'Power/SWR detector, supply voltage/current and S-meter calibration.'); + // Подпись зависит от железа (RelayoutCalTab): у AD936x детектора мощности и + // телеметрии питания нет, и обещать их калибровку в подзаголовке нечестно. + MakePageHeader(FPageCalib, 'Calibration', ''); + FLblCalHint := MakeTextBlock(FPageCalib, '', SETTINGS_MARGIN, 48, + SETTINGS_CARD_W, 20); + FLblCalHint.Tag := TAG_PAGE_HEADER; // ── Power / SWR detector ──────────────────────────────────────────────── GTop := 86; @@ -3177,6 +3496,8 @@ begin if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil) or (FCalGrpDisp = nil) or (FLblDispXvtrHdr = nil) or (FCalGrpFreq = nil) then Exit; + if not FPageCalib.Visible then begin FCalRelayoutDue := True; Exit; end; + FCalRelayoutDue := False; // TScrollBox двигает детей при прокрутке, и SetBounds ниже задаёт координаты // ОТНОСИТЕЛЬНО прокрученного клиента. Если страницу успели прокрутить (у @@ -3186,6 +3507,14 @@ begin FPageCalib.VertScrollBar.Position := 0; FPageCalib.HorzScrollBar.Position := 0; + if FLblCalHint <> nil then + if FCalIsPluto then + FLblCalHint.Caption := 'S-meter, spectrum and reference frequency ' + + 'calibration.' + else + FLblCalHint.Caption := 'Power/SWR detector, supply voltage/current and ' + + 'S-meter calibration.'; + // ---- подписи/видимость строк Det. Cal. (своя сетка, уже с подсказкой) ---- for i := 0 to CFG_BAND_COUNT - 1 do begin @@ -3410,11 +3739,15 @@ var Lbl: TLabel; Y, i: Integer; begin - MakePageHeader(FPageAdvanced, 'Advanced', - 'ADC processing and built-in web server settings.'); + MakePageHeader(FPageAdvanced, 'Advanced', ''); + FLblAdvHint := MakeTextBlock(FPageAdvanced, '', SETTINGS_MARGIN, 48, + SETTINGS_CARD_W, 20); + FLblAdvHint.Tag := TAG_PAGE_HEADER; // ── ADC ─────────────────────────────────────────────────────────────────── Grp := MakeGroupPanel(FPageAdvanced, 'ADC', MARGIN, 86, SETTINGS_CARD_W, 128); + FAdvGrpADC := Grp; + FAdvHasADC := True; // до подключения показываем полный (openHPSDR) вид Chk := TFlatCheckBox.Create(Self); Chk.Parent := Grp; @@ -3438,6 +3771,7 @@ begin // ── Web Server ──────────────────────────────────────────────────────────── Grp := MakeGroupPanel(FPageAdvanced, 'Web Server', MARGIN, 230, SETTINGS_CARD_W, 318); + FAdvGrpWeb := Grp; Y := R1; Chk := TFlatCheckBox.Create(Self); @@ -3515,6 +3849,8 @@ begin // TCI Server живёт на вкладке CAT (см. BuildCATTab): это такой же канал // внешнего управления трансивером, что и CAT, и оператор ищет его там. + + RelayoutAdvancedTab; // стартовая раскладка (полный, openHPSDR-вид) end; procedure TSettingsForm.BuildDXClusterTab; @@ -6110,6 +6446,20 @@ begin end; end; +procedure TSettingsForm.SetQOBackend(IsPluto: Boolean); +// Транспондер QO-100 у нас существует только на AD936x: InQO100 в контроллере +// требует IsPluto (полный дуплекс с раздельными RX/TX LO). На openHPSDR вся +// вкладка мертва, поэтому её нет в списке. +var + Changed: Boolean; +begin + Changed := FNavQO.Visible <> IsPluto; + FNavQO.Visible := IsPluto; + if Changed then RelayoutNav; // иначе на её месте останется дыра + if (not IsPluto) and FPageQO.Visible then + SelectPage(FPageAudio, FNavAudio); +end; + procedure TSettingsForm.SetOCBackend(IsPluto: Boolean); // У Pluto/AD936x нет Open Collector выхода — вкладка целиком скрывается, // а не показывает пустое содержимое (в отличие от Antenna, где Pluto имеет diff --git a/SpectrumView.pas b/SpectrumView.pas index 73420f2..f2a68e8 100644 --- a/SpectrumView.pas +++ b/SpectrumView.pas @@ -230,6 +230,8 @@ type procedure SetLastSWR(V: Double); function GetPAMaxPower: Double; procedure SetPAMaxPower(V: Double); + function GetHasPATelemetry: Boolean; + procedure SetHasPATelemetry(V: Boolean); function GetTransmitting: Boolean; procedure SetTransmitting(V: Boolean); procedure SetPbSMeterRight(V: TPaintBox); @@ -301,6 +303,8 @@ type property LastFwdW: Double read GetLastFwdW write SetLastFwdW; property LastSWR: Double read GetLastSWR write SetLastSWR; property PAMaxPower: Double read GetPAMaxPower write SetPAMaxPower; + // Есть ли мост детектора мощности: без него шкала PWR/SWR рисуется пустой. + property HasPATelemetry: Boolean read GetHasPATelemetry write SetHasPATelemetry; property Transmitting: Boolean read GetTransmitting write SetTransmitting; // ── Флаги обновления ────────────────────────────────────────────────────── @@ -612,6 +616,8 @@ function TSpectrumView.GetLastSWR: Double; begin Result := FSMeter.Las procedure TSpectrumView.SetLastSWR(V: Double); begin FSMeter.LastSWR := V; end; function TSpectrumView.GetPAMaxPower: Double; begin Result := FSMeter.PAMaxPower; end; procedure TSpectrumView.SetPAMaxPower(V: Double); begin FSMeter.PAMaxPower := V; end; +function TSpectrumView.GetHasPATelemetry: Boolean; begin Result := FSMeter.HasPATelemetry; end; +procedure TSpectrumView.SetHasPATelemetry(V: Boolean); begin FSMeter.HasPATelemetry := V; end; function TSpectrumView.GetTransmitting: Boolean; begin Result := FSMeter.Transmitting; end; procedure TSpectrumView.SetTransmitting(V: Boolean); begin FSMeter.Transmitting := V; end; procedure TSpectrumView.SetPbSMeterRight(V: TPaintBox); begin FSMeter.PbSMeterRight := V; end;