feat(slices): выбор КВ-диапазона на панах N (Flex-style band-селектор)

Кликабельный бейдж диапазона («20m») в шапке пана N → popup-меню КВ-бандов.
На выбор — по-флексовому: ретюн DDC на VfoA диапазона (из общего band-кэша,
как пан 0), активный приёмник садится на VfoA с mode/filter/AGC + zoom/pan
этого диапазона, прочие слайсы пана убираются (вне новой полосы). Только на
настоящих КВ (не XVTR, не Pluto).

- TPanafallPanel: FHeaderBandLabel + OnBandClick/BandClickable/SetHeaderBand;
  каскад left-лейблов шапки вынесен в LayoutHeaderLabels (band/rate/adc без
  дыр при скрытии).
- MainForm: OnPanBandClick/PanBandMenuItemClick/ApplyPanBand +
  PanBandSelectorVisible (гейт КВ); band-лейбл в UpdatePanHeaders (по
  FreqToBandIdx частоты DDC), тема в ApplyPanHeaderTheme.

Ограничение: per-slice AGC-Top в движке нет — подтягиваются
mode/filter/AGC-mode/zoom/pan, но не индивидуальный AGC-Top диапазона.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 14:27:17 +03:00
co-authored by Claude Opus 4.8
parent 3566f10191
commit 3ee996154a
2 changed files with 192 additions and 20 deletions
+72 -19
View File
@@ -98,6 +98,9 @@ type
FHeaderRateLabel: TLabel; // «NNN kHz»; у панов N>0 кликабелен (селектор rate)
FRateClickable: Boolean;
FOnRateClick: TNotifyEvent;
FHeaderBandLabel: TLabel; // «20m»; кликабелен на КВ (селектор диапазона пана, 3.6)
FBandClickable: Boolean;
FOnBandClick: TNotifyEvent;
FHeaderADCLabel: TLabel; // «RX1»/«RX2»; кликабелен при 2 АЦП (тумблер, 3.4)
FADCClickable: Boolean;
FOnADCClick: TNotifyEvent;
@@ -122,6 +125,9 @@ type
procedure BtnAddPanClick(Sender: TObject);
procedure HeaderRateClick(Sender: TObject);
procedure SetRateClickable(AValue: Boolean);
procedure HeaderBandClick(Sender: TObject);
procedure SetBandClickable(AValue: Boolean);
procedure LayoutHeaderLabels; // каскад left-лейблов (band/rate/adc)
procedure HeaderADCClick(Sender: TObject);
procedure SetADCClickable(AValue: Boolean);
procedure BtnPopPanClick(Sender: TObject);
@@ -210,6 +216,12 @@ type
procedure SetHeaderRate(const S: string);
property RateClickable: Boolean read FRateClickable write SetRateClickable;
property OnRateClick: TNotifyEvent read FOnRateClick write FOnRateClick;
// Бейдж диапазона «20m» (3.6, только КВ-паны N): пустой текст = скрыт,
// клик = меню КВ-диапазонов (хозяин).
procedure SetHeaderBand(const S: string);
property BandClickable: Boolean read FBandClickable write SetBandClickable;
property OnBandClick: TNotifyEvent read FOnBandClick write FOnBandClick;
property HeaderBandLabel: TLabel read FHeaderBandLabel;
// Бейдж ADC-источника «RX1»/«RX2» (3.4): показывается при NumADCs>1,
// клик = тумблер АЦП (хозяин).
procedure SetHeaderADC(const S: string);
@@ -439,6 +451,12 @@ begin
FHeaderRateLabel.Parent := FHeaderPanel;
FHeaderRateLabel.AutoSize := True;
FHeaderRateLabel.OnClick := HeaderRateClick;
// Band-бейдж «20m» (3.6): между текстом пана и rate; пустой = скрыт.
FHeaderBandLabel := TLabel.Create(Self);
FHeaderBandLabel.Parent := FHeaderPanel;
FHeaderBandLabel.AutoSize := True;
FHeaderBandLabel.Visible := False;
FHeaderBandLabel.OnClick := HeaderBandClick;
// ADC-бейдж «RX1»/«RX2» (3.4): пустой текст = скрыт (одноАЦПшные платы).
FHeaderADCLabel := TLabel.Create(Self);
FHeaderADCLabel.Parent := FHeaderPanel;
@@ -483,24 +501,63 @@ begin
if Assigned(FOnAddPan) then FOnAddPan(Self);
end;
procedure TPanafallPanel.LayoutHeaderLabels;
// Единый каскад left-лейблов шапки: [текст пана] [band?] [rate] [adc?].
// Скрытые (Visible=False) лейблы пропускаются, чтобы не оставлять дыр.
var X: Integer;
begin
if FHeaderLabel = nil then Exit;
X := FHeaderLabel.Left + FHeaderLabel.Width + 6;
if (FHeaderBandLabel <> nil) and FHeaderBandLabel.Visible then
begin
FHeaderBandLabel.Left := X;
Inc(X, FHeaderBandLabel.Width + 10);
end;
if FHeaderRateLabel <> nil then
begin
FHeaderRateLabel.Left := X;
Inc(X, FHeaderRateLabel.Width + 10);
end;
if FHeaderADCLabel <> nil then
FHeaderADCLabel.Left := X;
end;
procedure TPanafallPanel.SetHeaderText(const S: string);
begin
if FHeaderLabel = nil then Exit;
if FHeaderLabel.Caption = S then Exit;
FHeaderLabel.Caption := S;
// Rate-лейбл прижат к основному тексту — сдвинуть за новой шириной.
if FHeaderRateLabel <> nil then
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
LayoutHeaderLabels; // ширина текста изменилась — сдвинуть хвост
end;
procedure TPanafallPanel.SetHeaderRate(const S: string);
begin
if FHeaderRateLabel = nil then Exit;
if FHeaderRateLabel.Caption <> S then FHeaderRateLabel.Caption := S;
if FHeaderLabel <> nil then
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
if FHeaderADCLabel <> nil then
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
LayoutHeaderLabels;
end;
procedure TPanafallPanel.SetHeaderBand(const S: string);
begin
if FHeaderBandLabel = nil then Exit;
if FHeaderBandLabel.Caption <> S then FHeaderBandLabel.Caption := S;
FHeaderBandLabel.Visible := S <> '';
LayoutHeaderLabels;
end;
procedure TPanafallPanel.SetBandClickable(AValue: Boolean);
begin
FBandClickable := AValue;
if FHeaderBandLabel <> nil then
begin
if AValue then FHeaderBandLabel.Cursor := crHandPoint
else FHeaderBandLabel.Cursor := crArrow;
end;
end;
procedure TPanafallPanel.HeaderBandClick(Sender: TObject);
begin
if FBandClickable and Assigned(FOnBandClick) then FOnBandClick(Self);
end;
procedure TPanafallPanel.SetRateClickable(AValue: Boolean);
@@ -525,8 +582,7 @@ 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;
LayoutHeaderLabels;
end;
procedure TPanafallPanel.SetADCClickable(AValue: Boolean);
@@ -569,16 +625,13 @@ var BtnS, X: Integer;
begin
if FHeaderLabel <> nil then
FHeaderLabel.Top := (H - FHeaderLabel.Height) div 2;
if (FHeaderRateLabel <> nil) and (FHeaderLabel <> nil) then
begin
FHeaderRateLabel.Top := (H - FHeaderRateLabel.Height) div 2;
FHeaderRateLabel.Left := FHeaderLabel.Left + FHeaderLabel.Width + 6;
end;
if (FHeaderADCLabel <> nil) and (FHeaderRateLabel <> nil) then
begin
FHeaderADCLabel.Top := (H - FHeaderADCLabel.Height) div 2;
FHeaderADCLabel.Left := FHeaderRateLabel.Left + FHeaderRateLabel.Width + 10;
end;
if FHeaderBandLabel <> nil then
FHeaderBandLabel.Top := (H - FHeaderBandLabel.Height) div 2;
if FHeaderRateLabel <> nil then
FHeaderRateLabel.Top := (H - FHeaderRateLabel.Height) div 2;
if FHeaderADCLabel <> nil then
FHeaderADCLabel.Top := (H - FHeaderADCLabel.Height) div 2;
LayoutHeaderLabels; // единый каскад left-позиций
// Кнопки шапки прижаты вправо, справа налево: [×] [⧉] [◑]. Позиция каждой
// зависит от видимости соседей (у пана 0 нет × и ⧉ — ◑ уезжает к краю).
BtnS := H - 4;