diff --git a/MainForm.pas b/MainForm.pas index 6de6366..9c4396f 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -202,6 +202,8 @@ type // Меню rate пана («NNN kHz» в шапке панов N; создаётся лениво). FPanRateMenu: TPopupMenu; FPanRateMenuPanId: Integer; + FPanBandMenu: TPopupMenu; // селектор диапазона пана N (3.6, КВ) + FPanBandMenuPanId: Integer; // Pop-out (3.5): пан N в отдельном OS-окне. nil = пан в стеке. FPanFloatForm: array[1..MAX_PANS-1] of TForm; FSpecView: TSpectrumView; @@ -604,6 +606,10 @@ type procedure UpdateAddPanButton; procedure OnPanRateClick(Sender: TObject); // «NNN kHz» шапки пана N procedure PanRateMenuItemClick(Sender: TObject); + procedure OnPanBandClick(Sender: TObject); // «20m» шапки пана N (3.6) + procedure PanBandMenuItemClick(Sender: TObject); + procedure ApplyPanBand(P: TPanafallPanel; BandIdx: Integer); // Flex-style смена банда пана + function PanBandSelectorVisible: Boolean; // КВ (не XVTR, не Pluto) procedure OnPanADCClick(Sender: TObject); // бейдж «RX1/RX2» (3.4) // Per-pan дисплей (3.6): «◑» → поповер палитры/gamma/уровней. procedure OnPanDisplayClick(Sender: TObject); @@ -5638,6 +5644,95 @@ begin MarkPanDirty(P); end; +function TMainForm.PanBandSelectorVisible: Boolean; +// Селектор диапазона пана — только на «настоящих КВ»: не в XVTR и не Pluto +// (у Pluto/трансвертера band-стек другой, сценарий не актуален). +begin + Result := (FController.FCurrentXvtr < 0) and not FController.IsPluto; +end; + +procedure TMainForm.OnPanBandClick(Sender: TObject); +// Клик по бейджу диапазона в шапке пана N: меню КВ-диапазонов, текущий (по +// частоте DDC пана) отмечен галкой. +var + P: TPanafallPanel; + MI: TMenuItem; + i, CurBand: Integer; +begin + if not (Sender is TPanafallPanel) then Exit; + P := TPanafallPanel(Sender); + if (P.PanId < 1) or not FController.PanDDCActive(P.PanId) then Exit; + if not PanBandSelectorVisible then Exit; + CurBand := FreqToBandIdx(FController.PanDDCFreq(P.PanId), False); + if FPanBandMenu = nil then + FPanBandMenu := TPopupMenu.Create(Self); + FPanBandMenu.Items.Clear; + for i := 0 to CFG_BAND_COUNT - 1 do + begin + if BandName(i, False) = '' then Continue; // 6m-дубль/пустые слоты + MI := TMenuItem.Create(FPanBandMenu); + MI.Caption := BandName(i, False); + MI.Tag := i; + MI.Checked := i = CurBand; + MI.OnClick := PanBandMenuItemClick; + FPanBandMenu.Items.Add(MI); + end; + if FPanBandMenu.Items.Count = 0 then Exit; + FPanBandMenuPanId := P.PanId; + FPanBandMenu.PopUp; +end; + +procedure TMainForm.PanBandMenuItemClick(Sender: TObject); +var PanId: Integer; +begin + PanId := FPanBandMenuPanId; + FPanBandMenuPanId := 0; + if (PanId < 1) or (PanId >= MAX_PANS) then Exit; + if FPans[PanId] = nil then Exit; + ApplyPanBand(FPans[PanId], TMenuItem(Sender).Tag); +end; + +procedure TMainForm.ApplyPanBand(P: TPanafallPanel; BandIdx: Integer); +// Flex-style смена диапазона пана N: ретюн DDC на VfoA диапазона (из общего +// band-кэша, как пан 0), активный приёмник садится на VfoA с mode/filter/AGC +// (+zoom/pan) диапазона, прочие слайсы пана убираются (они вне новой полосы). +var + B: TBandSettings; + Freq: Double; + Lo, Hi, Id: Integer; +begin + if (P = nil) or (P.PanId < 1) then Exit; + if not PanBandSelectorVisible then Exit; + if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit; + B := FController.FBandCache[BandIdx]; + Freq := B.VfoA; + // Кэш диапазона мог нести чужую частоту (дефолты/битый конфиг) — тогда центр + // диапазона по плану. + if FreqToBandIdx(Freq, False) <> BandIdx then + Freq := BandDefaultFreq(BandIdx, False); + + // Слайсы вне новой полосы — снести до ретюна DDC. + P.DestroyAllSliceFlags; + FActiveSliceId := 0; + // Ретюн DDC пана + зум/пан диапазона. + FController.SetPanDDCFreq(P.PanId, Freq); + FController.SetPanZoomPan(P.PanId, + EnsureRange(B.ZoomFactor, 0.0, 1.0), EnsureRange(B.PanSlider, 0.0, 1.0)); + // Один приёмник на VfoA с настройками диапазона. + TRadioController.FilterEdgesFor(B.Mode, B.FilterBW, Lo, Hi); + Id := FController.AddSlice(Freq, B.Mode, Lo, Hi, + TRadioController.AGCModeFromUI(B.AGCMode), 0.7, -1, '', P.PanId); + if Id >= 0 then + begin + P.CreateSliceFlag(Id); + FActiveSliceId := Id; + end; + // Синк вьюхи/зум-бара/флагов/шапки под новый центр. + P.PushAllSliceFlagStates; + RefreshPanZoom(P); + UpdatePanHeaders; +end; + procedure TMainForm.OnPanADCClick(Sender: TObject); // Бейдж «RX1/RX2»: тумблер ADC-источника пана (двух-АЦПшные платы, 3.4). var @@ -5934,6 +6029,14 @@ begin P.HeaderRateLabel.Font.Color := T.BtnTextActive else P.HeaderRateLabel.Font.Color := T.Text; + // Band-лейбл (3.6): кликабельный — акцент селектора. + if P.HeaderBandLabel <> nil then + begin + if P.BandClickable then + P.HeaderBandLabel.Font.Color := T.BtnTextActive + else + P.HeaderBandLabel.Font.Color := T.Text; + end; // ADC-бейдж кликабелен на 2-АЦПшных платах — тоже акцент. if P.ADCClickable then P.HeaderADCLabel.Font.Color := T.BtnTextActive @@ -5945,7 +6048,7 @@ procedure TMainForm.UpdatePanHeaders; // Текст/видимость шапок: у одного пана шапки нет, у стека — у всех; // у плавающего (pop-out) пана шапка всегда (там его rate/ADC/×). var - i, N, NADC: Integer; + i, N, NADC, bi: Integer; P: TPanafallPanel; begin N := PanCount; @@ -5972,6 +6075,21 @@ begin [i, FController.PanDDCFreq(i) / 1e6])); P.SetHeaderRate(Format('%d kHz', [FController.PanDDCRateKHz(i)])); end; + // Бейдж диапазона (3.6): только паны N на «настоящих КВ»; клик = меню. + if (i > 0) and PanBandSelectorVisible then + begin + bi := FreqToBandIdx(FController.PanDDCFreq(i), False); + if (bi >= 0) and (BandName(bi, False) <> '') then + P.SetHeaderBand(BandName(bi, False)) + else + P.SetHeaderBand('—'); + P.BandClickable := True; + end + else + begin + P.SetHeaderBand(''); + P.BandClickable := False; + end; // Бейдж АЦП: только на двух-АЦПшных платах; клик = тумблер. if NADC > 1 then begin @@ -6027,6 +6145,7 @@ begin P.OnADCClick := OnPanADCClick; // «RX1/RX2» тумблер АЦП (3.4) P.OnPanPopOut := OnPanPopOutClick; // «⧉» в отдельное окно (3.5) P.OnDisplayClick := OnPanDisplayClick; // «◑» дисплей-поповер (3.6) + P.OnBandClick := OnPanBandClick; // «20m» селектор диапазона (3.6, КВ) P.SpectrumMouseDown := PanNSpectrumMouseDown; P.SpectrumMouseMove := PanNSpectrumMouseMove; P.SpectrumMouseUp := PanNSpectrumMouseUp; diff --git a/PanafallPanel.pas b/PanafallPanel.pas index 0fd6fce..6060b81 100644 --- a/PanafallPanel.pas +++ b/PanafallPanel.pas @@ -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;