From 6b386a4a763b1da9f9cb4c8e8c40d62366b79584 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 22 May 2026 12:40:04 +0300 Subject: [PATCH] Add NR/NB/SNB/ANF DSP row to VfoOverlay Third button row mirrors MainForm NR (cycle 0-4), NB (cycle 0-2), SNB and ANF toggles. OnDSPChange event syncs state back to MainForm buttons and DSP engine. Overlay height tightened to match top/bottom padding. Co-Authored-By: Claude Sonnet 4.6 --- MainForm.pas | 17 ++++++- VfoOverlay.pas | 130 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 131 insertions(+), 16 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 3fa702f..aba0a55 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -666,6 +666,7 @@ type procedure BtnSettingsClick(Sender: TObject); procedure PositionVfoOverlay; procedure OnModeFilterSelect(Mode: Integer; FilterBW: Integer); + procedure OnVfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean); procedure PbSpectrumDblClick(Sender: TObject); procedure TrkDriveChange(Sender: TObject); procedure TrkVolumeChange(Sender: TObject); @@ -1329,7 +1330,8 @@ begin FDeviceDialog := TDeviceDialog.Create(Self); FDeviceDialog.OnDiscover := BtnDiscoverFromDialog; FVfoOverlay := TVfoOverlay.Create(Self); - FVfoOverlay.OnSelect := OnModeFilterSelect; + FVfoOverlay.OnSelect := OnModeFilterSelect; + FVfoOverlay.OnDSPChange := OnVfoOverlayDSPChange; FVfoOverlay.Visible := False; FPanelHidden := False; FShowSpectrum := True; @@ -5479,8 +5481,9 @@ begin BtnHidePanel.Caption := '▶'; FVfoOverlay.Parent := PanelRight; FVfoOverlay.Width := 260; - FVfoOverlay.Height := 130; + FVfoOverlay.Height := 136; FVfoOverlay.SetState(FMode, FFilterBW, FVfoA, FLastSMeter); + FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0); FVfoOverlay.Visible := True; FVfoOverlay.BringToFront; PositionVfoOverlay; @@ -5544,10 +5547,20 @@ begin if Assigned(FVfoOverlay) and FVfoOverlay.Visible then begin FVfoOverlay.SetState(FMode, FFilterBW, FVfoA, FLastSMeter); + FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0); PositionVfoOverlay; // перепозиционируем при смене LSB↔USB end; end; +procedure TMainForm.OnVfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean); +begin + BtnNR.Tag := NRMode; UpdateNRButton; + BtnNB.Tag := NBMode; UpdateNBButton; + BtnSNB.Tag := Ord(SNBOn); UpdateSNBButton; + BtnANF.Tag := Ord(ANFOn); UpdateANFButton; + ApplyNoiseFilterButtonsToDSP; +end; + procedure TMainForm.BtnSpanClick(Sender: TObject); var NewRate: Integer; diff --git a/VfoOverlay.pas b/VfoOverlay.pas index a35d135..a69351e 100644 --- a/VfoOverlay.pas +++ b/VfoOverlay.pas @@ -9,6 +9,7 @@ uses type TModeFilterEvent = procedure(Mode: Integer; FilterBW: Integer) of object; + TDSPChangeEvent = procedure(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean) of object; TVfoOverlay = class(TCustomControl) private @@ -17,10 +18,16 @@ type FVfoHz: Double; FSMeterDB: Double; FOnSelect: TModeFilterEvent; - FModeFilter: array[0..7] of Integer; // запомненный фильтр для каждого режима + FOnDSPChange: TDSPChangeEvent; + FModeFilter: array[0..7] of Integer; - FHitRects: array[0..15] of TRect; - FHitActions: array[0..15] of Integer; // <0 = режим (-1=idx0..), >0 = BW фильтра + FNRMode: Integer; // 0=off, 1..4=NR/NR2/NR3/NR4 + FNBMode: Integer; // 0=off, 1=NB, 2=NB2 + FSNBOn: Boolean; + FANFOn: Boolean; + + FHitRects: array[0..19] of TRect; + FHitActions: array[0..19] of Integer; // <0 = режим/DSP, >0 = BW фильтра FHitCount: Integer; FHotIdx: Integer; @@ -28,6 +35,7 @@ type procedure DrawSMeterBar(C: TCanvas; X, Y, BW, BH: Integer); procedure DrawModeRow(C: TCanvas; X, Y, RowW, RowH: Integer); procedure DrawFilterRow(C: TCanvas; X, Y, RowW, RowH: Integer); + procedure DrawDSPRow(C: TCanvas; X, Y, RowW, RowH: Integer); procedure RegisterHit(HR: TRect; HV: Integer); function CalcSLabel: string; function FormatFreq(Hz: Double): string; @@ -45,14 +53,21 @@ type public constructor Create(AOwner: TComponent); override; procedure SetState(AMode, ABW: Integer; AVfoHz, ASMeterDB: Double); + procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean); procedure UpdateSMeter(DB: Double); procedure UpdateVfo(AVfoHz: Double); - property OnSelect: TModeFilterEvent read FOnSelect write FOnSelect; + property OnSelect: TModeFilterEvent read FOnSelect write FOnSelect; + property OnDSPChange: TDSPChangeEvent read FOnDSPChange write FOnDSPChange; end; implementation const + HIT_NR = -9; + HIT_NB = -10; + HIT_SNB = -11; + HIT_ANF = -12; + CLR_PANEL_BG = TColor($00141414); CLR_BORDER = TColor($00505050); CLR_TEXT = TColor($00E8E8E8); @@ -170,8 +185,12 @@ begin FModeFilter[5] := 11000; // FM (NFM по умолчанию) FModeFilter[6] := 8000; // AM FModeFilter[7] := 8000; // SAM - Width := 260; // шире — 8 кнопок режимов - Height := 130; + FNRMode := 0; + FNBMode := 0; + FSNBOn := False; + FANFOn := False; + Width := 260; + Height := 136; Cursor := crHandPoint; end; @@ -335,6 +354,62 @@ begin end; end; +procedure TVfoOverlay.DrawDSPRow(C: TCanvas; X, Y, RowW, RowH: Integer); +const + DSP_COUNT = 4; + DSP_HITS: array[0..3] of Integer = (HIT_NR, HIT_NB, HIT_SNB, HIT_ANF); +var + I, BX, BRight: Integer; + HR: TRect; + Lbl: string; + IsAct, IsHot: Boolean; +begin + BX := X; + for I := 0 to DSP_COUNT - 1 do + begin + BRight := X + ((I + 1) * RowW) div DSP_COUNT; + HR := Rect(BX, Y, BRight, Y + RowH); + case I of + 0: begin + case FNRMode of + 1: Lbl := 'NR'; + 2: Lbl := 'NR2'; + 3: Lbl := 'NR3'; + 4: Lbl := 'NR4'; + else Lbl := 'NR'; + end; + IsAct := FNRMode <> 0; + end; + 1: begin + if FNBMode = 2 then Lbl := 'NB2' else Lbl := 'NB'; + IsAct := FNBMode <> 0; + end; + 2: begin Lbl := 'SNB'; IsAct := FSNBOn; end; + 3: begin Lbl := 'ANF'; IsAct := FANFOn; end; + else Lbl := ''; IsAct := False; + end; + IsHot := (FHotIdx >= 0) and (FHitActions[FHotIdx] = DSP_HITS[I]); + + if IsAct then C.Brush.Color := CLR_BTN_ACT + else if IsHot then C.Brush.Color := CLR_BTN_HOT + else C.Brush.Color := CLR_BTN_NORM; + C.Brush.Style := bsSolid; C.Pen.Style := psClear; + C.FillRect(HR); + C.Pen.Style := psSolid; C.Pen.Color := CLR_BORDER; + C.Brush.Style := bsClear; C.Rectangle(HR); + + if IsAct then C.Font.Color := CLR_ACCENT + else C.Font.Color := CLR_TEXT; + C.Font.Size := 7; C.Font.Name := 'Courier New'; + C.Brush.Style := bsClear; + C.TextOut(BX + (BRight - BX - C.TextWidth(Lbl)) div 2, + Y + (RowH - C.TextHeight('A')) div 2, Lbl); + + RegisterHit(HR, DSP_HITS[I]); + BX := BRight + 1; + end; +end; + procedure TVfoOverlay.DrawSelf(C: TCanvas; W, H: Integer); const PAD = 4; @@ -386,6 +461,10 @@ begin // Кнопки фильтров DrawFilterRow(C, PAD, CurY, W - PAD * 2, ROW_H); + Inc(CurY, ROW_H + GAP); + + // Кнопки DSP (NR, NB, SNB, ANF) + DrawDSPRow(C, PAD, CurY, W - PAD * 2, ROW_H); end; procedure TVfoOverlay.Paint; @@ -407,15 +486,30 @@ begin if HV < 0 then begin NewMode := (-HV) - 1; - if NewMode <> FMode then + if NewMode < OVL_MODE_COUNT then begin - // Сохраняем текущий фильтр для старого режима - FModeFilter[FMode] := FFilterBW; - FMode := NewMode; - // Восстанавливаем запомненный фильтр для нового режима - FFilterBW := FModeFilter[FMode]; + // Кнопка режима + if NewMode <> FMode then + begin + FModeFilter[FMode] := FFilterBW; + FMode := NewMode; + FFilterBW := FModeFilter[FMode]; + Invalidate; + if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW); + end; + end + else + begin + // Кнопка DSP + case HV of + HIT_NR: FNRMode := (FNRMode + 1) mod 5; + HIT_NB: FNBMode := (FNBMode + 1) mod 3; + HIT_SNB: FSNBOn := not FSNBOn; + HIT_ANF: FANFOn := not FANFOn; + end; Invalidate; - if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW); + if Assigned(FOnDSPChange) then + FOnDSPChange(FNRMode, FNBMode, FSNBOn, FANFOn); end; end else if HV > 0 then @@ -424,7 +518,6 @@ begin if NewBW <> FFilterBW then begin FFilterBW := NewBW; - // Запоминаем выбранный фильтр для текущего режима FModeFilter[FMode] := FFilterBW; Invalidate; if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW); @@ -463,6 +556,15 @@ begin Invalidate; end; +procedure TVfoOverlay.SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean); +begin + FNRMode := ANRMode; + FNBMode := ANBMode; + FSNBOn := ASNBOn; + FANFOn := AANFOn; + Invalidate; +end; + procedure TVfoOverlay.UpdateSMeter(DB: Double); begin // Не клипаем — передаём как есть, как в основном S-метре