feat(ui): настройки показывают только то, что есть у активного железа

В TBackendCaps восемь флагов заполнялись обоими бэкендами и не читались нигде,
а в ApplyBackendCapsToUI на этом месте стояла расписка «(TODO) гейтинг полей,
которых нет у бэкенда». Из-за этого на AD936x оператору предлагали крутить то,
чего у платы нет, — и хуже всего страница PA: калибровка драйва по диапазонам
там просто ничего не делает (мощность задаётся железной аттенюацией, drive-байта
нет вовсе), но выглядела рабочей.

Теперь гейт идёт по возможностям, а не по виду устройства:
- HasPA — потолок мощности и калибровки драйва (HF + трансвертеры) против
  «TX att @100% drive»; заголовок и подзаголовок страницы следуют содержимому;
- HasHWMic — строка микрофонного разъёма (DUC Specific байт 50). Mic gain
  остаётся: он идёт в WDSP и работает с любым источником, включая звук с ПК,
  поэтому поднимается в освободившуюся строку;
- HasWideband — галки широкополосного АЦП; сам режим принудительно гасится,
  иначе сохранённый в конфиге wideband оставлял бы пустую панель без способа
  её выключить;
- HasDitherRandom — группа ADC целиком, Web Server встаёт на её место.
Вкладка QO-100 убрана на openHPSDR: TRadioController.InQO100 требует IsPluto,
то есть транспондер там недостижим. Симметрично OC Control, которого нет у
AD936x.

Измеритель: TSMeterView.HasPATelemetry. Без моста детектора FLastFwdW/FLastSWR
стоят на значениях инициализации, и «0W / SWR 1.0» на передаче — не показание,
а выдумка. Шкала рисуется пустой: деления и рамка на месте, заливки нет, ватты
на делениях не подписаны (они считаются от Max power, которого у AD936x нет).

★Отдельно — дефект раскладки, который всё это вскрыло. Перекладка страницы
настроек ломается в двух случаях, и оба случались при смене трансивера на живой
программе (Stop/Discover/Connect/Start), а не при старте:

1. Страница ПРОКРУЧЕНА. TScrollBox двигает детей, и SetBounds задаёт координаты
   относительно прокрученного клиента: всё уезжало вверх, а первая группа
   оставалась на месте — под переключателями Profile/EQ/Hardware зияла дыра.
   Лечение — сброс прокрутки первой строкой (так уже делал RelayoutCalTab).
2. Страница СПРЯТАНА. Тогда сброс не доходит до виджета, а сохранённое смещение
   возвращается при показе — и дыра та же. А попадает перекладка почти всегда
   именно на спрятанную страницу: гейты отрабатывают при открытии окна, когда
   активна одна вкладка, а до Transmit/Calibration оператор доходит потом.
   Лечение — перекладка спрятанной страницы взводит флаг и выходит, а SelectPage
   исполняет её в момент показа (RunDueRelayout). Гард стоит внутри каждой
   Relayout*, а не у вызывающих: у RelayoutCalTab их два, и второй
   (LoadXvtrSettings) приходит как раз на спрятанную страницу.

Проверено офскрин-рендером окна настроек в обе стороны (openHPSDR ↔ AD936x) для
Transmit/Power Amplifier/Calibration/Advanced, в том числе на прокрученной и
спрятанной странице. При полном железе координаты совпадают со старыми.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M37eCTZZ9aqppKWkZRxXkP
This commit is contained in:
2026-08-25 10:16:31 +03:00
co-authored by Claude Opus 5
parent eedaffad78
commit f99443620a
4 changed files with 435 additions and 27 deletions
+30 -2
View File
@@ -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);