diff --git a/MainForm.pas b/MainForm.pas index 45302af..ac7a4b8 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -353,10 +353,6 @@ type TrkDrive: TFlatSlider; BtnMOX: TFlatButton; BtnTUN: TFlatButton; - PbFwdPower: TPaintBox; - PbSWR: TPaintBox; - LblFwdPwr: TLabel; - LblSWRVal: TLabel; // ---- Right panel ---- PanelRight: TPanel; @@ -1207,8 +1203,9 @@ begin FSpecView.SpecRange := FSpecRange; FSpecView.SpecGridStep := FSpecGridStep; // TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k) - FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE; + FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE; FSpecView.WfFrameInterval := FWaterfallFrameInterval; + FSpecView.PAMaxPower := FPAMaxPower; FNetwork := THPSDRNetwork.Create; FNetwork.OnDeviceFound := OnDeviceFound; @@ -1415,7 +1412,7 @@ begin begin Add; Items[0].Width := 150; Items[0].Text := 'IP: --'; Add; Items[1].Width := 120; Items[1].Text := 'Board: --'; - Add; Items[2].Width := 320; Items[2].Text := 'Supply: -- | FWD: -- | SWR: --'; + Add; Items[2].Width := 200; Items[2].Text := 'Supply: --'; Add; Items[3].Width := 250; Items[3].Text := 'RX: waiting...'; Add; Items[4].Width := 200; Items[4].Text := 'Not connected'; Add; Items[5].Width := 280; Items[5].Text := 'TX: --'; @@ -1707,7 +1704,7 @@ begin // TX controls PanelTX := TPanel.Create(Self); PanelTX.Parent := PanelLeft; - PanelTX.SetBounds(0, Y, LEFT_W, 104); + PanelTX.SetBounds(0, Y, LEFT_W, 74); PanelTX.BevelOuter := bvNone; MakeLbl(PanelTX, 'TX', 4, 2); @@ -1728,27 +1725,6 @@ begin BtnTUN := MakeBtn(PanelTX, 'TUN', 56, 38, 50, 32, BtnTUNClick); BtnTUN.Font.Size := 12; BtnTUN.Font.Bold := True; - LblFwdPwr := TLabel.Create(Self); - LblFwdPwr.Parent := PanelTX; LblFwdPwr.Left := 112; LblFwdPwr.Top := 42; - LblFwdPwr.Caption := '0W'; LblFwdPwr.Font.Color := CLR_METER_ON; - LblFwdPwr.Font.Name := 'Courier New'; LblFwdPwr.Font.Size := 8; - - LblSWRVal := TLabel.Create(Self); - LblSWRVal.Parent := PanelTX; LblSWRVal.Left := 156; LblSWRVal.Top := 42; - LblSWRVal.Caption := 'SWR:1.0'; LblSWRVal.Font.Color := CLR_TEXTDIM; - LblSWRVal.Font.Name := 'Courier New'; LblSWRVal.Font.Size := 8; - - MakeLbl(PanelTX, 'PWR', 4, 76); - PbFwdPower := TPaintBox.Create(Self); - PbFwdPower.Parent := PanelTX; - PbFwdPower.SetBounds(34, 74, LEFT_W - 38, 12); - PbFwdPower.OnPaint := FSpecView.PaintFwdPower; - - MakeLbl(PanelTX, 'SWR', 4, 92); - PbSWR := TPaintBox.Create(Self); - PbSWR.Parent := PanelTX; - PbSWR.SetBounds(34, 90, LEFT_W - 38, 12); - PbSWR.OnPaint := FSpecView.PaintSWR; // ---- Right panel ---- PanelRight := TPanel.Create(Self); @@ -2288,14 +2264,10 @@ begin PbWaterfall.Color := T.BG; if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel; if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel; - PbFwdPower.Color := T.BG; - PbSWR.Color := T.BG; // Метки DL(LblAGCTop, False); DL(LblDrv); DL(LblSpan); - LblFwdPwr.Font.Color := T.MeterOn; - LblSWRVal.Font.Color := T.TextDim; // Кнопки тулбара BtnDiscover.ClrNorm := T.TbDiscoverNorm; @@ -2656,11 +2628,12 @@ begin FSMeterPeak := FSMeterPeak + PeakAlpha * (PeakTarget - FSMeterPeak); FSMeterMin := FSMeterMin + MinAlpha * (MinTarget - FSMeterMin); - FSpecView.LastSMeter := FLastSMeter; - FSpecView.SMeterPeak := FSMeterPeak; - FSpecView.SMeterMin := FSMeterMin; - FSpecView.LastFwdW := FLastFwdW; - FSpecView.LastSWR := FLastSWR; + FSpecView.LastSMeter := FLastSMeter; + FSpecView.SMeterPeak := FSMeterPeak; + FSpecView.SMeterMin := FSMeterMin; + FSpecView.LastFwdW := FLastFwdW; + FSpecView.LastSWR := FLastSWR; + FSpecView.Transmitting := FTransmitting; if PbSMeterRight <> nil then PbSMeterRight.Invalidate; // PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus — @@ -2991,22 +2964,11 @@ procedure TMainForm.UpdateTXMeters; var PLLStr: string; begin - if LblFwdPwr <> nil then - LblFwdPwr.Caption := Format('%.0fW', [FLastFwdW]); - if LblSWRVal <> nil then - LblSWRVal.Caption := Format('SWR:%.1f', [FLastSWR]); - if PbFwdPower <> nil then PbFwdPower.Invalidate; - if PbSWR <> nil then PbSWR.Invalidate; - if FLastPLLLock then PLLStr := 'PLL OK' else PLLStr := 'PLL?'; if FLastSupplyV >= 0 then - StatusBar1.Panels[2].Text := - Format('Supply: %.1fV | FWD: %.0fW | SWR: %.1f | %s', - [FLastSupplyV, FLastFwdW, FLastSWR, PLLStr]) + StatusBar1.Panels[2].Text := Format('Supply: %.1fV | %s', [FLastSupplyV, PLLStr]) else - StatusBar1.Panels[2].Text := - Format('FWD: %.0fW | SWR: %.1f | %s', - [FLastFwdW, FLastSWR, PLLStr]); + StatusBar1.Panels[2].Text := PLLStr; end; procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket); @@ -3696,7 +3658,7 @@ begin BtnStartStop.Caption := 'STOP'; StyleButton(BtnStartStop, True); - StatusBar1.Panels[4].Text := 'RUNNING ' + FormatFreq(FVfoA); + StatusBar1.Panels[4].Text := 'RUNNING'; BtnMOX.Enabled := True; end; @@ -5535,6 +5497,7 @@ procedure TMainForm.OnPASettingsChange(MaxPower: Double; var i: Integer; begin FPAMaxPower := MaxPower; + if FSpecView <> nil then FSpecView.PAMaxPower := MaxPower; for i := 0 to BAND_COUNT - 1 do begin if i <= High(BandCal) then diff --git a/SpectrumView.pas b/SpectrumView.pas index e256d94..c401704 100644 --- a/SpectrumView.pas +++ b/SpectrumView.pas @@ -104,6 +104,8 @@ type FSMeterMin: Double; FLastFwdW: Double; FLastSWR: Double; + FPAMaxPower: Double; + FTransmitting: Boolean; // ── Буферы данных (пишутся из DSP-потока) ──────────────────────────────── FSpectrumBuf: array[0..1023] of Single; @@ -181,8 +183,10 @@ type property LastSMeter: Double read FLastSMeter write FLastSMeter; property SMeterPeak: Double read FSMeterPeak write FSMeterPeak; property SMeterMin: Double read FSMeterMin write FSMeterMin; - property LastFwdW: Double read FLastFwdW write FLastFwdW; - property LastSWR: Double read FLastSWR write FLastSWR; + property LastFwdW: Double read FLastFwdW write FLastFwdW; + property LastSWR: Double read FLastSWR write FLastSWR; + property PAMaxPower: Double read FPAMaxPower write FPAMaxPower; + property Transmitting: Boolean read FTransmitting write FTransmitting; // ── Флаги обновления ────────────────────────────────────────────────────── property SpectrumDirty: Boolean read FSpectrumDirty write FSpectrumDirty; @@ -212,6 +216,7 @@ type out ZX1, ZX2, ZY1, ZY2: Integer); procedure DrawSMeterZone(ACanvas: TCanvas; R: TRect; X1, X2, Y1, Y2: Integer); + procedure DrawTXMeter(ACanvas: TCanvas; R: TRect); // ── Управление размером bitmap ──────────────────────────────────────────── procedure SetSpectrumBitmapSize(W, H: Integer); @@ -427,6 +432,7 @@ begin FWaterfallDirty := True; FLastSMeter := -130; FSMeterPeak := -130; FSMeterMin := -130; FLastFwdW := 0; FLastSWR := 1; + FPAMaxPower := 100.0; FTransmitting := False; FLightTheme := False; FTheme := DarkTheme; ResetSpectrumBuf; @@ -1318,6 +1324,202 @@ begin finally Img.Free; end; end; +procedure TSpectrumView.DrawTXMeter(ACanvas: TCanvas; R: TRect); +// Визуально идентичен DrawSMeterWide: те же пропорции, тот же левый блок, +// те же тики — но центральная полоса разбита на две: мощность (верх) и КСВ (низ). +const + SWR_HIGH_THRESH = 2.5; + SWR_MAX = 10.0; + LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4; + TICK_LONG = 5; + PWR_MARKS: array[0..4] of Double = (0.0, 0.25, 0.5, 0.75, 1.0); + SWR_MARKS_VAL: array[0..3] of Double = (1.0, 2.5, 5.0, 10.0); + SWR_MARKS_LBL: array[0..3] of string = ('1', '2.5', '5', '10'); +var + CLR_SMETER_BG, CLR_BAR_GREEN, CLR_BAR_OVER: TColor; + CLR_TICK_GREEN, CLR_TICK_WHITE, CLR_TICK_BLUE: TColor; + CLR_LABEL_DBM, CLR_LABEL_S: TColor; + CLR_SCALE_DBM, CLR_SCALE_S_GREEN, CLR_SCALE_S_BLUE, CLR_BDR: TColor; + CLR_SWR_HIGH: TColor; + W, H, BX, BW, LeftInfo: Integer; + Y_BAR_TOP, Y_BAR_BOT, Y_BAR_MID: Integer; + PY1, PY2, SY1, SY2: Integer; + PBarEnd, SBarEnd, S_WarnX: Integer; + LblPwr, LblSWR: string; + TWPwr, LblZoneL, TH10, TH6, MaxTWPwr, MaxTWSWR, LabelNeed: Integer; + i, X, TW: Integer; + Lbl: string; + SWRHigh: Boolean; + PwrPct: Double; + + function PwrToX(Pct: Double): Integer; inline; + begin + Result := BX + Round(Max(0.0, Min(1.0, Pct)) * BW); + if Result < BX then Result := BX; + if Result > BX + BW then Result := BX + BW; + end; + + function SWRToX(SWR: Double): Integer; inline; + begin + Result := BX + Round(Max(0.0, Min(1.0, (SWR - 1.0) / (SWR_MAX - 1.0))) * BW); + if Result < BX then Result := BX; + if Result > BX + BW then Result := BX + BW; + end; + + procedure VLine(X2, Y1, Y2: Integer; C: TColor); + begin ACanvas.Pen.Color := C; ACanvas.MoveTo(X2, Y1); ACanvas.LineTo(X2, Y2); end; + + procedure FillBar(X1, Y1, X2, Y2: Integer; C: TColor); + begin + ACanvas.Brush.Color := C; ACanvas.Brush.Style := bsSolid; + ACanvas.Pen.Style := psClear; + if X2 > X1 then ACanvas.FillRect(Rect(X1, Y1, X2, Y2)); + ACanvas.Pen.Style := psSolid; + end; + +begin + CLR_SMETER_BG := FTheme.SMeterBG; + CLR_BAR_GREEN := FTheme.SMeterBarGreen; + CLR_BAR_OVER := FTheme.SMeterBarOver; + CLR_TICK_GREEN := FTheme.SMeterTickGreen; + CLR_TICK_WHITE := FTheme.SMeterTickWhite; + CLR_TICK_BLUE := FTheme.SMeterTickBlue; + CLR_LABEL_DBM := FTheme.SMeterLabelDbm; + CLR_LABEL_S := FTheme.SMeterLabelS; + CLR_SCALE_DBM := FTheme.SMeterScaleDbm; + CLR_SCALE_S_GREEN:= FTheme.SMeterScaleSGreen; + CLR_SCALE_S_BLUE := FTheme.SMeterScaleSBlue; + CLR_BDR := FTheme.SMeterBdr; + CLR_SWR_HIGH := TColor($000000CC); + + W := R.Right - R.Left; H := R.Bottom - R.Top; + if (W < 80) or (H < 16) then Exit; + + SWRHigh := FLastSWR > SWR_HIGH_THRESH; + LblPwr := Format('%.0fW', [FLastFwdW]); + LblSWR := Format('SWR:%.1f', [FLastSWR]); + + // Левая информационная зона — такая же логика как в DrawSMeterWide + ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold]; + ACanvas.Font.Size := 10; + TWPwr := ACanvas.TextWidth(LblPwr); + MaxTWPwr := ACanvas.TextWidth(Format('%.0fW', [FPAMaxPower])); + MaxTWSWR := ACanvas.TextWidth('SWR:10.0'); + ACanvas.Font.Size := 6; ACanvas.Font.Style := []; + LabelNeed := Max(MaxTWPwr, MaxTWSWR) + LABEL_PAD * 2; + LeftInfo := Max(LEFT_INFO_MIN, LabelNeed + 4); + BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD; + if BW < 10 then Exit; + LblZoneL := BX - LabelNeed; + + // Те же вертикальные позиции бара что в DrawSMeterWide (44% / 58%) + Y_BAR_TOP := R.Top + H * 44 div 100; + Y_BAR_BOT := R.Top + H * 58 div 100; + Y_BAR_MID := (Y_BAR_TOP + Y_BAR_BOT) div 2; + + // Два суб-бара в той же зоне + PY1 := Y_BAR_TOP; + PY2 := Y_BAR_MID - 1; // power bar + SY1 := Y_BAR_MID + 1; + SY2 := Y_BAR_BOT; // SWR bar + + S_WarnX := SWRToX(SWR_HIGH_THRESH); + + // Фон + ACanvas.Brush.Color := CLR_SMETER_BG; ACanvas.Brush.Style := bsSolid; + ACanvas.Pen.Style := psClear; ACanvas.FillRect(R); ACanvas.Pen.Style := psSolid; + + // Фоновые зоны (как зоны зелёный/красный в S-метре) + FillBar(BX, PY1, BX + BW, PY2, CLR_BAR_GREEN); // power: весь зелёный фон + FillBar(BX, SY1, S_WarnX, SY2, CLR_BAR_GREEN); // SWR: OK-зона + FillBar(S_WarnX, SY1, BX + BW, SY2, CLR_BAR_OVER); // SWR: warn-зона + + // Активный заполненный бар мощности + if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0; + PBarEnd := PwrToX(PwrPct); + FillBar(BX, PY1 + 1, PBarEnd, PY2 - 1, SV_CLR_METER_ON); + + // Активный заполненный бар КСВ + SBarEnd := SWRToX(FLastSWR); + if not SWRHigh then + FillBar(BX, SY1 + 1, SBarEnd, SY2 - 1, SV_CLR_AMBER) + else + begin + FillBar(BX, SY1 + 1, Min(S_WarnX, SBarEnd), SY2 - 1, SV_CLR_AMBER); + if SBarEnd > S_WarnX then + FillBar(S_WarnX, SY1 + 1, SBarEnd, SY2 - 1, CLR_SWR_HIGH); + end; + + // Граница KСВ 2.5 + VLine(S_WarnX, SY1, SY2, FTheme.SMeterDivider); + + // Рамки обоих баров + левая зона надписей + ACanvas.Brush.Style := bsClear; ACanvas.Pen.Color := CLR_BDR; + ACanvas.Rectangle(LblZoneL, PY1, BX + BW, PY2); + if SWRHigh then ACanvas.Pen.Color := CLR_SWR_HIGH + else ACanvas.Pen.Color := CLR_BDR; + ACanvas.Rectangle(LblZoneL, SY1, BX + BW, SY2); + + // Разделители между зоной надписей и шкалой + ACanvas.Pen.Color := FTheme.SMeterDivider; + ACanvas.MoveTo(BX, PY1 + 1); ACanvas.LineTo(BX, PY2 - 1); + ACanvas.MoveTo(BX, SY1 + 1); ACanvas.LineTo(BX, SY2 - 1); + + // --- Тики шкалы --- + ACanvas.Font.Size := 6; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear; + + // Мощность: тики ВЫШЕ power-бара (как dBm-тики в S-метре) + for i := 0 to 4 do + begin + X := PwrToX(PWR_MARKS[i]); + VLine(X, PY1 - TICK_LONG, PY1 - 1, CLR_TICK_WHITE); + Lbl := Format('%.0f', [PWR_MARKS[i] * FPAMaxPower]); + TW := ACanvas.TextWidth(Lbl); + ACanvas.Font.Color := CLR_SCALE_DBM; + ACanvas.TextOut(X - TW div 2, PY1 - TICK_LONG - ACanvas.TextHeight(Lbl) - 1, Lbl); + end; + + // КСВ: тики НИЖЕ SWR-бара (как S-тики в S-метре) + for i := 0 to 3 do + begin + X := SWRToX(SWR_MARKS_VAL[i]); + if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then + VLine(X, SY2 + 1, SY2 + TICK_LONG, CLR_TICK_BLUE) + else + VLine(X, SY2 + 1, SY2 + TICK_LONG, CLR_TICK_GREEN); + Lbl := SWR_MARKS_LBL[i]; TW := ACanvas.TextWidth(Lbl); + if SWR_MARKS_VAL[i] >= SWR_HIGH_THRESH then + begin + if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH + else ACanvas.Font.Color := CLR_SCALE_S_BLUE; + end else + ACanvas.Font.Color := CLR_SCALE_S_GREEN; + ACanvas.TextOut(X - TW div 2, SY2 + TICK_LONG + 1, Lbl); + end; + + // --- Надписи в левой зоне (те же позиции что в DrawSMeterWide) --- + ACanvas.Brush.Style := bsClear; + ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0'); + ACanvas.Font.Size := 6; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0'); + + // Мощность — над верхним баром, как dBm в S-метре + ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM; + ACanvas.TextOut(LblZoneL + 4, PY1 - TH10 - 2, LblPwr); + + // КСВ — под нижним баром, как S-значение в S-метре + ACanvas.Font.Size := 10; ACanvas.Font.Style := [fsBold]; + if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH + else ACanvas.Font.Color := CLR_LABEL_S; + ACanvas.TextOut(LblZoneL + 4, SY2 + 2, LblSWR); + + // "SWR High" рядом с надписью мощности (как "dBm" в S-метре) + if SWRHigh then + begin + ACanvas.Font.Size := 6; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_SWR_HIGH; + ACanvas.TextOut(LblZoneL + 4 + TWPwr + 2, PY1 - TH6 - 4, 'SWR High'); + end; +end; + // ──────────────────────────────────────────────────────────────────────────── // Paint-обработчики // ──────────────────────────────────────────────────────────────────────────── @@ -1370,10 +1572,16 @@ begin FSmBitmap := TBitmap.Create; FSmBitmap.SetSize(W, H); end; - DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, W, H), - FLastSMeter, FSMeterPeak, FSMeterMin, - ZX1, ZX2, ZY1, ZY2); - DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, W, H), ZX1, ZX2, ZY1, ZY2); + if FTransmitting then + begin + DrawTXMeter(FSmBitmap.Canvas, Rect(0, 0, W, H)); + end else + begin + DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, W, H), + FLastSMeter, FSMeterPeak, FSMeterMin, + ZX1, ZX2, ZY1, ZY2); + DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, W, H), ZX1, ZX2, ZY1, ZY2); + end; PB.Canvas.Draw(0, 0, FSmBitmap); end;