mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27:33 +00:00
feat(slices): этап 3.4 ADC-селектор панов + этап 3.5 pop-out в отдельное окно
3.4 ADC-селектор: - Caps.NumADCs (HPSDR: 2 для ORION/ORION2/SATURN, как Pkt.NumADCs; Pluto: 1) - controller.PanADC/SetPanADC: пан 0 = FMainADCSrc + ConfigureDDCs (ADC прокинут и в штатные вызовы — смена rate не сбрасывает выбор), паны N = FPanADC[] + SetPanDDC (RebuildDDCSpecific) - Бейдж «A1/A2» в шапке каждого пана (виден при NumADCs>1), клик = тумблер; пан 0 на ADC2 закрывает сценарий rx2-support 3.5 Pop-out: - «⧉» в шапке панов N → PopOutPan: TForm.CreateNew + ReparentTo (перенос всех контролов панели), [×] окна = DockBackPan (возврат в стек), «×» шапки = закрыть пан; LayoutPanStack скипает плавающих; колесо окна = FormMouseWheel - GL: reparent пересоздаёт контексты → ResetGLCache обеих канв без glDelete (по прецеденту MSAA); TWaterfallViewOpenGL.ResetGLCache новый, история водопада перезаливается из CPU-буфера Проверено юзером на экране (беглый тест: селектор и pop-out работают). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -693,6 +693,12 @@ begin
|
|||||||
// см. doc/SLICES_PLAN.md 3.1). Каждый DDC тюнится независимо.
|
// см. doc/SLICES_PLAN.md 3.1). Каждый DDC тюнится независимо.
|
||||||
Result.MaxPans := 4;
|
Result.MaxPans := 4;
|
||||||
Result.IndependentPanFreq := True;
|
Result.IndependentPanFreq := True;
|
||||||
|
// Два АЦП у ANAN-100D/200D (ORION), 7000/8000 (ORION2) и Saturn/G2 —
|
||||||
|
// те же платы, что в RebuildDDCSpecific (Pkt.NumADCs).
|
||||||
|
if FDevice.BoardType in [4, 5, 10] then
|
||||||
|
Result.NumADCs := 2
|
||||||
|
else
|
||||||
|
Result.NumADCs := 1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
constructor THPSDRNetwork.Create;
|
constructor THPSDRNetwork.Create;
|
||||||
|
|||||||
+163
-4
@@ -187,6 +187,8 @@ type
|
|||||||
// Меню rate пана («NNN kHz» в шапке панов N; создаётся лениво).
|
// Меню rate пана («NNN kHz» в шапке панов N; создаётся лениво).
|
||||||
FPanRateMenu: TPopupMenu;
|
FPanRateMenu: TPopupMenu;
|
||||||
FPanRateMenuPanId: Integer;
|
FPanRateMenuPanId: Integer;
|
||||||
|
// Pop-out (3.5): пан N в отдельном OS-окне. nil = пан в стеке.
|
||||||
|
FPanFloatForm: array[1..MAX_PANS-1] of TForm;
|
||||||
FSpecView: TSpectrumView;
|
FSpecView: TSpectrumView;
|
||||||
FWidebandView: TWidebandView;
|
FWidebandView: TWidebandView;
|
||||||
FUseOpenGLSpectrum: Boolean;
|
FUseOpenGLSpectrum: Boolean;
|
||||||
@@ -575,6 +577,15 @@ type
|
|||||||
procedure UpdateAddPanButton;
|
procedure UpdateAddPanButton;
|
||||||
procedure OnPanRateClick(Sender: TObject); // «NNN kHz» шапки пана N
|
procedure OnPanRateClick(Sender: TObject); // «NNN kHz» шапки пана N
|
||||||
procedure PanRateMenuItemClick(Sender: TObject);
|
procedure PanRateMenuItemClick(Sender: TObject);
|
||||||
|
procedure OnPanADCClick(Sender: TObject); // бейдж «A1/A2» (3.4)
|
||||||
|
// Pop-out (3.5): «⧉» выносит пан в отдельное окно, [×] окна возвращает в стек.
|
||||||
|
procedure OnPanPopOutClick(Sender: TObject);
|
||||||
|
procedure PopOutPan(PanId: Integer);
|
||||||
|
procedure DockBackPan(PanId: Integer);
|
||||||
|
procedure PanFloatClose(Sender: TObject; var CloseAction: TCloseAction);
|
||||||
|
procedure PanFloatResize(Sender: TObject);
|
||||||
|
function PanFloating(PanId: Integer): Boolean;
|
||||||
|
procedure ResetPanGLCaches(P: TPanafallPanel); // после смены родителя
|
||||||
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
|
||||||
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
|
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
|
||||||
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
|
||||||
@@ -1061,6 +1072,7 @@ begin
|
|||||||
// Стек панов (этап 3.3): пан 0 — в общем массиве; «⊞» добавляет пан.
|
// Стек панов (этап 3.3): пан 0 — в общем массиве; «⊞» добавляет пан.
|
||||||
FPan.PanId := 0;
|
FPan.PanId := 0;
|
||||||
FPan.OnAddPan := OnAddPanClick;
|
FPan.OnAddPan := OnAddPanClick;
|
||||||
|
FPan.OnADCClick := OnPanADCClick; // бейдж A1/A2 (3.4, платы с 2 АЦП)
|
||||||
FPans[0] := FPan;
|
FPans[0] := FPan;
|
||||||
FPanShare[0] := 1.0;
|
FPanShare[0] := 1.0;
|
||||||
FPanCbLock := SyncObjs.TCriticalSection.Create;
|
FPanCbLock := SyncObjs.TCriticalSection.Create;
|
||||||
@@ -2566,9 +2578,11 @@ var
|
|||||||
N, i, k, TotalH, UsableH, Y, Hi: Integer;
|
N, i, k, TotalH, UsableH, Y, Hi: Integer;
|
||||||
ShareSum: Double;
|
ShareSum: Double;
|
||||||
begin
|
begin
|
||||||
|
// Плавающие (pop-out) паны в стеке не участвуют — у них своё окно.
|
||||||
N := 0;
|
N := 0;
|
||||||
for i := 0 to MAX_PANS - 1 do
|
for i := 0 to MAX_PANS - 1 do
|
||||||
if FPans[i] <> nil then begin ActiveIds[N] := i; Inc(N); end;
|
if (FPans[i] <> nil) and not PanFloating(i) then
|
||||||
|
begin ActiveIds[N] := i; Inc(N); end;
|
||||||
if N = 0 then Exit;
|
if N = 0 then Exit;
|
||||||
TotalH := PanelRight.ClientHeight;
|
TotalH := PanelRight.ClientHeight;
|
||||||
UsableH := TotalH - (N - 1) * PAN_SPLIT_H;
|
UsableH := TotalH - (N - 1) * PAN_SPLIT_H;
|
||||||
@@ -2679,6 +2693,7 @@ begin
|
|||||||
if FPans[i] <> nil then
|
if FPans[i] <> nil then
|
||||||
begin
|
begin
|
||||||
if FPans[i].BtnAddPan <> nil then StyleButton(FPans[i].BtnAddPan, False);
|
if FPans[i].BtnAddPan <> nil then StyleButton(FPans[i].BtnAddPan, False);
|
||||||
|
if FPans[i].BtnPopPan <> nil then StyleButton(FPans[i].BtnPopPan, False);
|
||||||
if FPans[i].BtnClosePan <> nil then StyleButton(FPans[i].BtnClosePan, False);
|
if FPans[i].BtnClosePan <> nil then StyleButton(FPans[i].BtnClosePan, False);
|
||||||
if FPans[i].HeaderPanel <> nil then
|
if FPans[i].HeaderPanel <> nil then
|
||||||
begin
|
begin
|
||||||
@@ -2689,11 +2704,17 @@ begin
|
|||||||
FPans[i].HeaderRateLabel.Font.Color := T.BtnTextActive
|
FPans[i].HeaderRateLabel.Font.Color := T.BtnTextActive
|
||||||
else
|
else
|
||||||
FPans[i].HeaderRateLabel.Font.Color := T.Text;
|
FPans[i].HeaderRateLabel.Font.Color := T.Text;
|
||||||
|
// ADC-бейдж кликабелен на 2-АЦПшных платах — тоже акцент.
|
||||||
|
if FPans[i].ADCClickable then
|
||||||
|
FPans[i].HeaderADCLabel.Font.Color := T.BtnTextActive
|
||||||
|
else
|
||||||
|
FPans[i].HeaderADCLabel.Font.Color := T.Text;
|
||||||
end;
|
end;
|
||||||
if i > 0 then
|
if i > 0 then
|
||||||
begin
|
begin
|
||||||
FPans[i].SetFlagsTheme(T);
|
FPans[i].SetFlagsTheme(T);
|
||||||
if FPanSplitters[i] <> nil then FPanSplitters[i].Color := T.Border;
|
if FPanSplitters[i] <> nil then FPanSplitters[i].Color := T.Border;
|
||||||
|
if FPanFloatForm[i] <> nil then FPanFloatForm[i].Color := T.BG;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if FPanZoomBar <> nil then
|
if FPanZoomBar <> nil then
|
||||||
@@ -5355,6 +5376,120 @@ begin
|
|||||||
MarkPanDirty(P);
|
MarkPanDirty(P);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.OnPanADCClick(Sender: TObject);
|
||||||
|
// Бейдж «A1/A2»: тумблер ADC-источника пана (двух-АЦПшные платы, 3.4).
|
||||||
|
var
|
||||||
|
P: TPanafallPanel;
|
||||||
|
N: Integer;
|
||||||
|
begin
|
||||||
|
if not (Sender is TPanafallPanel) then Exit;
|
||||||
|
P := TPanafallPanel(Sender);
|
||||||
|
N := FController.BackendCaps.NumADCs;
|
||||||
|
if N < 2 then Exit;
|
||||||
|
if not FController.SetPanADC(P.PanId, (FController.PanADC(P.PanId) + 1) mod N) then
|
||||||
|
Exit;
|
||||||
|
UpdatePanHeaders;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// ---- Pop-out (3.5): пан N в отдельном OS-окне ----
|
||||||
|
|
||||||
|
function TMainForm.PanFloating(PanId: Integer): Boolean;
|
||||||
|
begin
|
||||||
|
Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanFloatForm[PanId] <> nil);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.ResetPanGLCaches(P: TPanafallPanel);
|
||||||
|
// Смена родителя пересоздаёт хэндлы GL-канв → старые контексты (и все
|
||||||
|
// текстуры) мертвы; кэши вьюхи обнуляем БЕЗ glDelete (как при смене MSAA).
|
||||||
|
begin
|
||||||
|
if FUseOpenGLSpectrum and (P.View is TSpectrumViewOpenGL) then
|
||||||
|
TSpectrumViewOpenGL(P.View).ResetGLCache;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.OnPanPopOutClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
if Sender is TPanafallPanel then
|
||||||
|
PopOutPan(TPanafallPanel(Sender).PanId);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.PopOutPan(PanId: Integer);
|
||||||
|
var
|
||||||
|
P: TPanafallPanel;
|
||||||
|
F: TForm;
|
||||||
|
T: TAppTheme;
|
||||||
|
begin
|
||||||
|
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||||
|
P := FPans[PanId];
|
||||||
|
if (P = nil) or PanFloating(PanId) then Exit;
|
||||||
|
if FLightTheme then T := LightTheme else T := DarkTheme;
|
||||||
|
F := TForm.CreateNew(Self);
|
||||||
|
F.Caption := Format('EWSDR · PAN %d', [PanId]);
|
||||||
|
F.Color := T.BG;
|
||||||
|
F.Position := poDefault;
|
||||||
|
F.Width := PanelRight.ClientWidth;
|
||||||
|
F.Height := Max(300, P.StackHeight);
|
||||||
|
F.Tag := PanId;
|
||||||
|
F.OnClose := PanFloatClose;
|
||||||
|
F.OnResize := PanFloatResize;
|
||||||
|
F.OnMouseWheel := FormMouseWheel; // колесо над флагами/фоном как в стеке
|
||||||
|
FPanFloatForm[PanId] := F;
|
||||||
|
P.ReparentTo(F);
|
||||||
|
ResetPanGLCaches(P);
|
||||||
|
P.StackTop := 0;
|
||||||
|
P.StackHeight := 0; // 0/0 = весь родитель (окно)
|
||||||
|
if FPanSplitters[PanId] <> nil then FPanSplitters[PanId].Visible := False;
|
||||||
|
F.Show;
|
||||||
|
UpdatePanHeaders;
|
||||||
|
ResizeSpectrumPanels; // пере-стек оставшихся в главном окне
|
||||||
|
P.Layout(0, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||||
|
P.LayoutFlags;
|
||||||
|
MarkPanDirty(P);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.DockBackPan(PanId: Integer);
|
||||||
|
var
|
||||||
|
P: TPanafallPanel;
|
||||||
|
F: TForm;
|
||||||
|
begin
|
||||||
|
P := FPans[PanId];
|
||||||
|
F := FPanFloatForm[PanId];
|
||||||
|
if F = nil then Exit;
|
||||||
|
FPanFloatForm[PanId] := nil;
|
||||||
|
if P <> nil then
|
||||||
|
begin
|
||||||
|
P.ReparentTo(PanelRight);
|
||||||
|
ResetPanGLCaches(P);
|
||||||
|
end;
|
||||||
|
if FPanSplitters[PanId] <> nil then FPanSplitters[PanId].Visible := True;
|
||||||
|
F.Release; // безопасно из событий самого окна
|
||||||
|
UpdatePanHeaders;
|
||||||
|
ResizeSpectrumPanels;
|
||||||
|
if P <> nil then
|
||||||
|
begin
|
||||||
|
P.LayoutFlags;
|
||||||
|
MarkPanDirty(P);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.PanFloatClose(Sender: TObject; var CloseAction: TCloseAction);
|
||||||
|
// [×] окна = вернуть пан в стек (не закрыть пан). Release — в DockBackPan.
|
||||||
|
begin
|
||||||
|
CloseAction := caNone;
|
||||||
|
DockBackPan(TForm(Sender).Tag);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.PanFloatResize(Sender: TObject);
|
||||||
|
var P: TPanafallPanel; PanId: Integer;
|
||||||
|
begin
|
||||||
|
PanId := TForm(Sender).Tag;
|
||||||
|
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||||
|
P := FPans[PanId];
|
||||||
|
if (P = nil) or (FPanFloatForm[PanId] <> Sender) then Exit;
|
||||||
|
P.Layout(0, FController.FShowSpectrum, FController.FShowWaterfall);
|
||||||
|
P.LayoutFlags;
|
||||||
|
MarkPanDirty(P);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer);
|
procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer);
|
||||||
// Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог
|
// Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог
|
||||||
// DoSpectrumClick главного: снап к 100 Гц). Цель — активный слайс, если он
|
// DoSpectrumClick главного: снап к 100 Гц). Цель — активный слайс, если он
|
||||||
@@ -5392,18 +5527,22 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.UpdatePanHeaders;
|
procedure TMainForm.UpdatePanHeaders;
|
||||||
// Текст/видимость шапок: у одного пана шапки нет, у стека — у всех.
|
// Текст/видимость шапок: у одного пана шапки нет, у стека — у всех;
|
||||||
|
// у плавающего (pop-out) пана шапка всегда (там его rate/ADC/×).
|
||||||
var
|
var
|
||||||
i, N: Integer;
|
i, N, NADC: Integer;
|
||||||
P: TPanafallPanel;
|
P: TPanafallPanel;
|
||||||
begin
|
begin
|
||||||
N := PanCount;
|
N := PanCount;
|
||||||
|
NADC := FController.BackendCaps.NumADCs;
|
||||||
for i := 0 to MAX_PANS - 1 do
|
for i := 0 to MAX_PANS - 1 do
|
||||||
begin
|
begin
|
||||||
P := FPans[i];
|
P := FPans[i];
|
||||||
if P = nil then Continue;
|
if P = nil then Continue;
|
||||||
P.HeaderVisible := N > 1;
|
P.HeaderVisible := (N > 1) or ((i > 0) and PanFloating(i));
|
||||||
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
|
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
|
||||||
|
if P.BtnPopPan <> nil then
|
||||||
|
P.BtnPopPan.Visible := (N > 1) and (i > 0) and not PanFloating(i);
|
||||||
if i = 0 then
|
if i = 0 then
|
||||||
begin
|
begin
|
||||||
P.SetHeaderText(Format('PAN 0 · %.6f MHz ·',
|
P.SetHeaderText(Format('PAN 0 · %.6f MHz ·',
|
||||||
@@ -5416,6 +5555,17 @@ begin
|
|||||||
[i, FController.PanDDCFreq(i) / 1e6]));
|
[i, FController.PanDDCFreq(i) / 1e6]));
|
||||||
P.SetHeaderRate(Format('%d kHz', [FController.PanDDCRateKHz(i)]));
|
P.SetHeaderRate(Format('%d kHz', [FController.PanDDCRateKHz(i)]));
|
||||||
end;
|
end;
|
||||||
|
// Бейдж АЦП: только на двух-АЦПшных платах; клик = тумблер.
|
||||||
|
if NADC > 1 then
|
||||||
|
begin
|
||||||
|
P.SetHeaderADC(Format('A%d', [FController.PanADC(i) + 1]));
|
||||||
|
P.ADCClickable := True;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
P.SetHeaderADC('');
|
||||||
|
P.ADCClickable := False;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -5466,6 +5616,8 @@ begin
|
|||||||
P.OnSplitterMoved := RightPanelResize; // ratio обновлён → полный re-layout
|
P.OnSplitterMoved := RightPanelResize; // ratio обновлён → полный re-layout
|
||||||
P.OnRateClick := OnPanRateClick; // «NNN kHz» в шапке → меню rate
|
P.OnRateClick := OnPanRateClick; // «NNN kHz» в шапке → меню rate
|
||||||
P.RateClickable := True;
|
P.RateClickable := True;
|
||||||
|
P.OnADCClick := OnPanADCClick; // «A1/A2» тумблер АЦП (3.4)
|
||||||
|
P.OnPanPopOut := OnPanPopOutClick; // «⧉» в отдельное окно (3.5)
|
||||||
P.SpectrumMouseDown := PanNSpectrumMouseDown;
|
P.SpectrumMouseDown := PanNSpectrumMouseDown;
|
||||||
P.SpectrumMouseMove := PanNSpectrumMouseMove;
|
P.SpectrumMouseMove := PanNSpectrumMouseMove;
|
||||||
P.SpectrumMouseUp := PanNSpectrumMouseUp;
|
P.SpectrumMouseUp := PanNSpectrumMouseUp;
|
||||||
@@ -5555,6 +5707,13 @@ begin
|
|||||||
FActiveSliceId := 0;
|
FActiveSliceId := 0;
|
||||||
FreeAndNil(FPanSplitters[PanId]);
|
FreeAndNil(FPanSplitters[PanId]);
|
||||||
P.Free;
|
P.Free;
|
||||||
|
// Пан был в отдельном окне — окно больше не нужно. Release (не Free):
|
||||||
|
// сюда можно попасть из событий контролов этого же окна («×» шапки → тик).
|
||||||
|
if FPanFloatForm[PanId] <> nil then
|
||||||
|
begin
|
||||||
|
FPanFloatForm[PanId].Release;
|
||||||
|
FPanFloatForm[PanId] := nil;
|
||||||
|
end;
|
||||||
for i := 0 to MAX_PANS - 1 do
|
for i := 0 to MAX_PANS - 1 do
|
||||||
if FPans[i] <> nil then FPanShare[i] := 1.0 / PanCount;
|
if FPans[i] <> nil then FPanShare[i] := 1.0 / PanCount;
|
||||||
UpdatePanHeaders;
|
UpdatePanHeaders;
|
||||||
|
|||||||
+87
-3
@@ -98,6 +98,11 @@ type
|
|||||||
FHeaderRateLabel: TLabel; // «NNN kHz»; у панов N>0 кликабелен (селектор rate)
|
FHeaderRateLabel: TLabel; // «NNN kHz»; у панов N>0 кликабелен (селектор rate)
|
||||||
FRateClickable: Boolean;
|
FRateClickable: Boolean;
|
||||||
FOnRateClick: TNotifyEvent;
|
FOnRateClick: TNotifyEvent;
|
||||||
|
FHeaderADCLabel: TLabel; // «A1»/«A2»; кликабелен при 2 АЦП (тумблер, 3.4)
|
||||||
|
FADCClickable: Boolean;
|
||||||
|
FOnADCClick: TNotifyEvent;
|
||||||
|
FBtnPopPan: TFlatButton; // «⧉» pop-out в отдельное окно (паны N>0, 3.5)
|
||||||
|
FOnPanPopOut: TNotifyEvent;
|
||||||
FBtnClosePan: TFlatButton; // «×» в шапке (только паны N>0)
|
FBtnClosePan: TFlatButton; // «×» в шапке (только паны N>0)
|
||||||
FBtnAddPan: TFlatButton; // «⊞» в ряду пан/зума (только пан 0)
|
FBtnAddPan: TFlatButton; // «⊞» в ряду пан/зума (только пан 0)
|
||||||
FHeaderVisible: Boolean;
|
FHeaderVisible: Boolean;
|
||||||
@@ -110,6 +115,9 @@ type
|
|||||||
procedure BtnAddPanClick(Sender: TObject);
|
procedure BtnAddPanClick(Sender: TObject);
|
||||||
procedure HeaderRateClick(Sender: TObject);
|
procedure HeaderRateClick(Sender: TObject);
|
||||||
procedure SetRateClickable(AValue: Boolean);
|
procedure SetRateClickable(AValue: Boolean);
|
||||||
|
procedure HeaderADCClick(Sender: TObject);
|
||||||
|
procedure SetADCClickable(AValue: Boolean);
|
||||||
|
procedure BtnPopPanClick(Sender: TObject);
|
||||||
procedure PositionHeaderChildren(RW, H: Integer);
|
procedure PositionHeaderChildren(RW, H: Integer);
|
||||||
// Видимое частотное окно ЭТОГО пана: пан 0 — окно главного (зум/пан),
|
// Видимое частотное окно ЭТОГО пана: пан 0 — окно главного (зум/пан),
|
||||||
// паны N — центр их DDC ± rate/2 (пан-зум — этап 3.3+).
|
// паны N — центр их DDC ± rate/2 (пан-зум — этап 3.3+).
|
||||||
@@ -148,6 +156,10 @@ type
|
|||||||
|
|
||||||
// Создаёт контролы в AParent и подвешивает обработчики (см. поля выше).
|
// Создаёт контролы в AParent и подвешивает обработчики (см. поля выше).
|
||||||
procedure Build(AParent: TWinControl; AMSAA: Integer);
|
procedure Build(AParent: TWinControl; AMSAA: Integer);
|
||||||
|
// Переносит ВСЕ контролы панели в другого родителя (pop-out/dock, 3.5).
|
||||||
|
// GL-контексты канв при этом пересоздаются — хозяин обязан сбросить
|
||||||
|
// GL-кэши вьюхи (ResetGLCache) после вызова.
|
||||||
|
procedure ReparentTo(NewParent: TWinControl);
|
||||||
// Полная раскладка стека. ATopOffset — высота wideband-блока хозяина
|
// Полная раскладка стека. ATopOffset — высота wideband-блока хозяина
|
||||||
// над спектром. Show-флаги приходят от контроллера (FShowSpectrum/
|
// над спектром. Show-флаги приходят от контроллера (FShowSpectrum/
|
||||||
// FShowWaterfall).
|
// FShowWaterfall).
|
||||||
@@ -182,9 +194,18 @@ type
|
|||||||
procedure SetHeaderRate(const S: string);
|
procedure SetHeaderRate(const S: string);
|
||||||
property RateClickable: Boolean read FRateClickable write SetRateClickable;
|
property RateClickable: Boolean read FRateClickable write SetRateClickable;
|
||||||
property OnRateClick: TNotifyEvent read FOnRateClick write FOnRateClick;
|
property OnRateClick: TNotifyEvent read FOnRateClick write FOnRateClick;
|
||||||
|
// Бейдж ADC-источника «A1»/«A2» (3.4): показывается при NumADCs>1,
|
||||||
|
// клик = тумблер АЦП (хозяин).
|
||||||
|
procedure SetHeaderADC(const S: string);
|
||||||
|
property ADCClickable: Boolean read FADCClickable write SetADCClickable;
|
||||||
|
property OnADCClick: TNotifyEvent read FOnADCClick write FOnADCClick;
|
||||||
|
// «⧉» (3.5): вынести пан в отдельное окно (хозяин).
|
||||||
|
property OnPanPopOut: TNotifyEvent read FOnPanPopOut write FOnPanPopOut;
|
||||||
property HeaderPanel: TPanel read FHeaderPanel;
|
property HeaderPanel: TPanel read FHeaderPanel;
|
||||||
property HeaderLabel: TLabel read FHeaderLabel;
|
property HeaderLabel: TLabel read FHeaderLabel;
|
||||||
property HeaderRateLabel: TLabel read FHeaderRateLabel;
|
property HeaderRateLabel: TLabel read FHeaderRateLabel;
|
||||||
|
property HeaderADCLabel: TLabel read FHeaderADCLabel;
|
||||||
|
property BtnPopPan: TFlatButton read FBtnPopPan;
|
||||||
property BtnClosePan: TFlatButton read FBtnClosePan;
|
property BtnClosePan: TFlatButton read FBtnClosePan;
|
||||||
property BtnAddPan: TFlatButton read FBtnAddPan;
|
property BtnAddPan: TFlatButton read FBtnAddPan;
|
||||||
// Ряд пан/зума: у панов N>0 скрыт (их зум — этап 3.3+).
|
// Ряд пан/зума: у панов N>0 скрыт (их зум — этап 3.3+).
|
||||||
@@ -380,10 +401,35 @@ begin
|
|||||||
FHeaderRateLabel.Parent := FHeaderPanel;
|
FHeaderRateLabel.Parent := FHeaderPanel;
|
||||||
FHeaderRateLabel.AutoSize := True;
|
FHeaderRateLabel.AutoSize := True;
|
||||||
FHeaderRateLabel.OnClick := HeaderRateClick;
|
FHeaderRateLabel.OnClick := HeaderRateClick;
|
||||||
|
// ADC-бейдж «A1»/«A2» (3.4): пустой текст = скрыт (одноАЦПшные платы).
|
||||||
|
FHeaderADCLabel := TLabel.Create(Self);
|
||||||
|
FHeaderADCLabel.Parent := FHeaderPanel;
|
||||||
|
FHeaderADCLabel.AutoSize := True;
|
||||||
|
FHeaderADCLabel.OnClick := HeaderADCClick;
|
||||||
|
FBtnPopPan := MakeFlatBtn(FHeaderPanel, '⧉', 0, 0, 10, 10, BtnPopPanClick);
|
||||||
|
FBtnPopPan.Hint := 'Pop out';
|
||||||
|
FBtnPopPan.ShowHint := True;
|
||||||
|
FBtnPopPan.Visible := False; // хозяин включает у панов N>0
|
||||||
FBtnClosePan := MakeFlatBtn(FHeaderPanel, '×', 0, 0, 10, 10, BtnClosePanClick);
|
FBtnClosePan := MakeFlatBtn(FHeaderPanel, '×', 0, 0, 10, 10, BtnClosePanClick);
|
||||||
FBtnClosePan.Visible := False; // хозяин включает у панов N>0
|
FBtnClosePan.Visible := False; // хозяин включает у панов N>0
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TPanafallPanel.ReparentTo(NewParent: TWinControl);
|
||||||
|
begin
|
||||||
|
if (NewParent = nil) or (NewParent = FParent) then Exit;
|
||||||
|
FParent := NewParent;
|
||||||
|
FPbSpectrum.Parent := NewParent;
|
||||||
|
FPbRuler.Parent := NewParent;
|
||||||
|
FSplitter.Parent := NewParent;
|
||||||
|
FPbWaterfall.Parent := NewParent;
|
||||||
|
FPbPanZoom.Parent := NewParent;
|
||||||
|
FBtnZoomOut.Parent := NewParent;
|
||||||
|
FBtnZoomDef.Parent := NewParent;
|
||||||
|
FBtnZoomIn.Parent := NewParent;
|
||||||
|
FBtnAddPan.Parent := NewParent;
|
||||||
|
FHeaderPanel.Parent := NewParent; // дети шапки едут вместе с ней
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TPanafallPanel.BtnClosePanClick(Sender: TObject);
|
procedure TPanafallPanel.BtnClosePanClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
if Assigned(FOnPanClose) then FOnPanClose(Self);
|
if Assigned(FOnPanClose) then FOnPanClose(Self);
|
||||||
@@ -410,6 +456,8 @@ begin
|
|||||||
if FHeaderRateLabel.Caption <> S then FHeaderRateLabel.Caption := S;
|
if FHeaderRateLabel.Caption <> S then FHeaderRateLabel.Caption := S;
|
||||||
if FHeaderLabel <> nil then
|
if FHeaderLabel <> nil then
|
||||||
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
|
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
|
||||||
|
if FHeaderADCLabel <> nil then
|
||||||
|
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPanafallPanel.SetRateClickable(AValue: Boolean);
|
procedure TPanafallPanel.SetRateClickable(AValue: Boolean);
|
||||||
@@ -429,6 +477,37 @@ begin
|
|||||||
if FRateClickable and Assigned(FOnRateClick) then FOnRateClick(Self);
|
if FRateClickable and Assigned(FOnRateClick) then FOnRateClick(Self);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TPanafallPanel.SetHeaderADC(const S: string);
|
||||||
|
begin
|
||||||
|
if FHeaderADCLabel = nil then Exit;
|
||||||
|
if FHeaderADCLabel.Caption <> S then FHeaderADCLabel.Caption := S;
|
||||||
|
FHeaderADCLabel.Visible := S <> '';
|
||||||
|
if FHeaderRateLabel <> nil then
|
||||||
|
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPanafallPanel.SetADCClickable(AValue: Boolean);
|
||||||
|
begin
|
||||||
|
FADCClickable := AValue;
|
||||||
|
if FHeaderADCLabel <> nil then
|
||||||
|
begin
|
||||||
|
if AValue then
|
||||||
|
FHeaderADCLabel.Cursor := crHandPoint
|
||||||
|
else
|
||||||
|
FHeaderADCLabel.Cursor := crArrow;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPanafallPanel.HeaderADCClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
if FADCClickable and Assigned(FOnADCClick) then FOnADCClick(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPanafallPanel.BtnPopPanClick(Sender: TObject);
|
||||||
|
begin
|
||||||
|
if Assigned(FOnPanPopOut) then FOnPanPopOut(Self);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TPanafallPanel.ViewWindow(out VC, VS: Double);
|
procedure TPanafallPanel.ViewWindow(out VC, VS: Double);
|
||||||
begin
|
begin
|
||||||
VC := 0; VS := 0;
|
VC := 0; VS := 0;
|
||||||
@@ -452,11 +531,16 @@ begin
|
|||||||
FHeaderRateLabel.Top := (H - FHeaderRateLabel.Height) div 2;
|
FHeaderRateLabel.Top := (H - FHeaderRateLabel.Height) div 2;
|
||||||
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
|
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
|
||||||
end;
|
end;
|
||||||
if FBtnClosePan <> nil then
|
if (FHeaderADCLabel <> nil) and (FHeaderRateLabel <> nil) then
|
||||||
begin
|
begin
|
||||||
BtnS := H - 4;
|
FHeaderADCLabel.Top := (H - FHeaderADCLabel.Height) div 2;
|
||||||
FBtnClosePan.SetBounds(RW - BtnS - 2, 2, BtnS, BtnS);
|
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
|
||||||
end;
|
end;
|
||||||
|
BtnS := H - 4;
|
||||||
|
if FBtnClosePan <> nil then
|
||||||
|
FBtnClosePan.SetBounds(RW - BtnS - 2, 2, BtnS, BtnS);
|
||||||
|
if FBtnPopPan <> nil then
|
||||||
|
FBtnPopPan.SetBounds(RW - 2 * BtnS - 6, 2, BtnS, BtnS);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPanafallPanel.PositionPanZoomBar(BottomY, RW, H: Integer;
|
procedure TPanafallPanel.PositionPanZoomBar(BottomY, RW, H: Integer;
|
||||||
|
|||||||
@@ -436,6 +436,7 @@ begin
|
|||||||
// RX LO, IndependentPanFreq=False — этап 3.6.)
|
// RX LO, IndependentPanFreq=False — этап 3.6.)
|
||||||
Result.MaxPans := 1;
|
Result.MaxPans := 1;
|
||||||
Result.IndependentPanFreq := False;
|
Result.IndependentPanFreq := False;
|
||||||
|
Result.NumADCs := 1;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ type
|
|||||||
MaxPans: Integer;
|
MaxPans: Integer;
|
||||||
// False = частоты панов связаны с паном 0 (Pluto+ 2r2t: общий RX LO).
|
// False = частоты панов связаны с паном 0 (Pluto+ 2r2t: общий RX LO).
|
||||||
IndependentPanFreq: Boolean;
|
IndependentPanFreq: Boolean;
|
||||||
|
// Число АЦП (SCU): >1 → у панов есть селектор ADC-источника (A1/A2, 3.4).
|
||||||
|
// Достоверно после Connect (у HPSDR зависит от BoardType).
|
||||||
|
NumADCs: Integer;
|
||||||
MinSampleRate: Integer;
|
MinSampleRate: Integer;
|
||||||
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||||
MinFreqHz: Double;
|
MinFreqHz: Double;
|
||||||
|
|||||||
+38
-2
@@ -353,6 +353,7 @@ type
|
|||||||
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
||||||
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
||||||
FPanADC: array[1..MAX_PANS-1] of Byte; // ADC-источник пана (3.4)
|
FPanADC: array[1..MAX_PANS-1] of Byte; // ADC-источник пана (3.4)
|
||||||
|
FMainADCSrc: Byte; // ADC главного DDC (пан 0)
|
||||||
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
||||||
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
||||||
FLastDDCIndex: Integer; // последний DDC index
|
FLastDDCIndex: Integer; // последний DDC index
|
||||||
@@ -576,6 +577,10 @@ type
|
|||||||
function PanDDCActive(PanId: Integer): Boolean;
|
function PanDDCActive(PanId: Integer): Boolean;
|
||||||
function PanDDCFreq(PanId: Integer): Double;
|
function PanDDCFreq(PanId: Integer): Double;
|
||||||
function PanDDCRateKHz(PanId: Integer): Word;
|
function PanDDCRateKHz(PanId: Integer): Word;
|
||||||
|
// ADC-источник пана (3.4). PanId=0 = главный DDC (реконфиг ConfigureDDCs),
|
||||||
|
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
||||||
|
function PanADC(PanId: Integer): Byte;
|
||||||
|
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||||
|
|
||||||
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
||||||
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
||||||
@@ -1317,6 +1322,35 @@ begin
|
|||||||
else Result := 0;
|
else Result := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.PanADC(PanId: Integer): Byte;
|
||||||
|
begin
|
||||||
|
if PanId = 0 then Result := FMainADCSrc
|
||||||
|
else if PanDDCActive(PanId) then Result := FPanADC[PanId]
|
||||||
|
else Result := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TRadioController.SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
if not (Assigned(FNetwork) and FRunning) then Exit;
|
||||||
|
if ADC >= FNetwork.Caps.NumADCs then Exit;
|
||||||
|
if PanId = 0 then
|
||||||
|
begin
|
||||||
|
// Главный DDC: тот же ConfigureDDCs, что при смене rate (кэш+RebuildDDCSpecific).
|
||||||
|
if FMainADCSrc = ADC then Exit(True);
|
||||||
|
FMainADCSrc := ADC;
|
||||||
|
FNetwork.ConfigureDDCs(1, FSampleRate div 1000, ADC,
|
||||||
|
FDitherEnabled, FRandomEnabled);
|
||||||
|
Exit(True);
|
||||||
|
end;
|
||||||
|
if not PanDDCActive(PanId) then Exit;
|
||||||
|
if FPanADC[PanId] = ADC then Exit(True);
|
||||||
|
FPanADC[PanId] := ADC;
|
||||||
|
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, FPanFreqHz[PanId],
|
||||||
|
FPanRateKHz[PanId], ADC);
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.AddSlice(TargetHz: Double;
|
function TRadioController.AddSlice(TargetHz: Double;
|
||||||
Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double;
|
Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double;
|
||||||
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
||||||
@@ -3142,7 +3176,8 @@ begin
|
|||||||
|
|
||||||
// 2. Новый rate трансиверу (после остановки DSP — пакеты сразу с верным rate).
|
// 2. Новый rate трансиверу (после остановки DSP — пакеты сразу с верным rate).
|
||||||
if Assigned(FNetwork) and FNetwork.Connected then
|
if Assigned(FNetwork) and FNetwork.Connected then
|
||||||
FNetwork.ConfigureDDCs(1, Hz div 1000, 0, FDitherEnabled, FRandomEnabled);
|
FNetwork.ConfigureDDCs(1, Hz div 1000, FMainADCSrc,
|
||||||
|
FDitherEnabled, FRandomEnabled);
|
||||||
|
|
||||||
// 3. Восстанавливаем DSP-состояние (ChangeSampleRate пересоздал канал).
|
// 3. Восстанавливаем DSP-состояние (ChangeSampleRate пересоздал канал).
|
||||||
if Assigned(FDSPEngine) then
|
if Assigned(FDSPEngine) then
|
||||||
@@ -3657,7 +3692,8 @@ begin
|
|||||||
|
|
||||||
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
|
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
|
||||||
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings.
|
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings.
|
||||||
FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled);
|
FNetwork.ConfigureDDCs(1, FSampleRate div 1000, FMainADCSrc,
|
||||||
|
FDitherEnabled, FRandomEnabled);
|
||||||
// Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1).
|
// Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1).
|
||||||
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
||||||
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
||||||
|
|||||||
@@ -457,6 +457,11 @@ begin
|
|||||||
FBandOverlayDirty := True;
|
FBandOverlayDirty := True;
|
||||||
FGLSpectrumW := 0;
|
FGLSpectrumW := 0;
|
||||||
FGLSpectrumH := 0;
|
FGLSpectrumH := 0;
|
||||||
|
// Водопад — свой контекст/текстуры (история + маркер). При reparent (pop-out)
|
||||||
|
// умирают ОБА контекста; при смене MSAA только спектровый — там сброс
|
||||||
|
// водопада лишь пере-зальёт его текстуру (дёшево, история и так в CPU-буфере).
|
||||||
|
if FWaterfall is TWaterfallViewOpenGL then
|
||||||
|
TWaterfallViewOpenGL(FWaterfall).ResetGLCache;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TSpectrumViewOpenGL.FormatFreqGL(Hz: Double): string;
|
function TSpectrumViewOpenGL.FormatFreqGL(Hz: Double): string;
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ type
|
|||||||
procedure SetTheme(const T: TAppTheme); override;
|
procedure SetTheme(const T: TAppTheme); override;
|
||||||
procedure ResetWfBuf; override;
|
procedure ResetWfBuf; override;
|
||||||
procedure ResetWfAvgBuf; override;
|
procedure ResetWfAvgBuf; override;
|
||||||
|
// Контекст умер (reparent/пересоздание хэндла): обнулить id БЕЗ glDelete.
|
||||||
|
procedure ResetGLCache;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -94,6 +96,15 @@ begin
|
|||||||
FGLControl := C;
|
FGLControl := C;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.ResetGLCache;
|
||||||
|
begin
|
||||||
|
FTexture := 0;
|
||||||
|
FMarkerTex := 0;
|
||||||
|
FTexW := 0;
|
||||||
|
FTexH := 0;
|
||||||
|
FMarkerText := '';
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TWaterfallViewOpenGL.DeleteTexture(var Tex: GLuint);
|
procedure TWaterfallViewOpenGL.DeleteTexture(var Tex: GLuint);
|
||||||
begin
|
begin
|
||||||
if Tex <> 0 then glDeleteTextures(1, @Tex);
|
if Tex <> 0 then glDeleteTextures(1, @Tex);
|
||||||
|
|||||||
@@ -463,6 +463,21 @@ ChangeSampleRate).
|
|||||||
- Diversity (sync-DDC, оба АЦП когерентно в один канал) — НЕ здесь; протокол
|
- Diversity (sync-DDC, оба АЦП когерентно в один канал) — НЕ здесь; протокол
|
||||||
умеет, дизайну панов не мешает, отдельная будущая фича.
|
умеет, дизайну панов не мешает, отдельная будущая фича.
|
||||||
|
|
||||||
|
**СТАТУС 3.4 (2026-07-11): ГОТОВО, коммит 5e50cba, юзер подтвердил беглым
|
||||||
|
тестом. ⚠Ожидаются вопросы юзера по пункту «смена rate не сбрасывает АЦП».**
|
||||||
|
- `Caps.NumADCs` (HPSDR: 2 для BoardType 4/5/10 — ORION/ORION2/SATURN, те же
|
||||||
|
платы, что Pkt.NumADCs в RebuildDDCSpecific; Pluto: 1; достоверно после
|
||||||
|
Connect).
|
||||||
|
- Контроллер: `PanADC(PanId)` / `SetPanADC(PanId, ADC)`. Пан 0 = главный DDC
|
||||||
|
(`FMainADCSrc` + ConfigureDDCs с тем же rate/dither/random; FMainADCSrc
|
||||||
|
теперь прокинут и в оба штатных вызова ConfigureDDCs — смена rate не
|
||||||
|
сбрасывает АЦП). Паны N = `FPanADC[]` + SetPanDDC (RebuildDDCSpecific).
|
||||||
|
- UI: бейдж «A1»/«A2» в шапке КАЖДОГО пана (вкл. пан 0 — закрывает сценарий
|
||||||
|
rx2-support), виден только при NumADCs>1, клик = тумблер, акцентный цвет.
|
||||||
|
- ⚠Ограничение: шапка пана 0 видна только при ≥2 панах ⇒ с единственным
|
||||||
|
паном перекинуть пан 0 на ADC2 из UI нельзя (добавь пан или жди персиста).
|
||||||
|
Антенны Alex (ANT1/2/3) — по-прежнему глобально в Antenna-табе.
|
||||||
|
|
||||||
### 3.5 — Pop-out: панадаптер в отдельном OS-окне
|
### 3.5 — Pop-out: панадаптер в отдельном OS-окне
|
||||||
|
|
||||||
Улучшение поверх Flex (SmartSDR так не умеет). Кнопка `⧉` в шапке.
|
Улучшение поверх Flex (SmartSDR так не умеет). Кнопка `⧉` в шапке.
|
||||||
@@ -478,6 +493,25 @@ ChangeSampleRate).
|
|||||||
- **Риск №2 — общие GL-ресурсы**: закрывается аудитом из 3.0.
|
- **Риск №2 — общие GL-ресурсы**: закрывается аудитом из 3.0.
|
||||||
- Персист: флаг «пан N в отдельном окне» + геометрия окна.
|
- Персист: флаг «пан N в отдельном окне» + геометрия окна.
|
||||||
|
|
||||||
|
**СТАТУС 3.5 (2026-07-11): ГОТОВО, коммит 5e50cba, юзер подтвердил беглым
|
||||||
|
тестом — честный GL-reparent на Qt6/Wayland РАБОТАЕТ (fallback-пересоздание
|
||||||
|
не понадобилось).**
|
||||||
|
- Кнопка «⧉» в шапке панов N>0 → `PopOutPan`: `TForm.CreateNew` (Caption
|
||||||
|
«EWSDR · PAN N», тема, OnMouseWheel = FormMouseWheel — колесо над
|
||||||
|
флагами/фоном работает как в стеке), `TPanafallPanel.ReparentTo(F)`
|
||||||
|
(Parent всех контролов), StackTop/Height=0/0 (= всё окно), сплиттер пана
|
||||||
|
скрыт, LayoutPanStack пере-стекует оставшихся (плавающие скипаются).
|
||||||
|
- GL: reparent пересоздаёт хэндлы канв → контексты умирают. По прецеденту
|
||||||
|
MSAA: `ResetGLCache` (обнуление id без glDelete) — расширен, теперь
|
||||||
|
сбрасывает и водопад (`TWaterfallViewOpenGL.ResetGLCache`, новый);
|
||||||
|
история водопада живёт в CPU-буфере — тексура перезальётся.
|
||||||
|
- [×] окна = dock back (`CloseAction := caNone` + `DockBackPan`: reparent в
|
||||||
|
PanelRight, сброс GL, Release окна). «×» шапки в окне = закрыть пан
|
||||||
|
целиком (ClosePanadapter освобождает и окно). STOP закрывает всё.
|
||||||
|
- Резерв на случай мёртвого GL после reparent: пересоздание вьюхи/контролов
|
||||||
|
(в коде НЕ делалось — сначала экранный тест).
|
||||||
|
- Персист геометрии — вместе с общим персистом панов.
|
||||||
|
|
||||||
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
||||||
|
|
||||||
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
||||||
|
|||||||
Reference in New Issue
Block a user