mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
TX meter: show power and SWR in S-meter panel during transmit
- S-meter panel switches to TX mode when transmitting: single bar splits into two sub-bars (power top, SWR bottom) at the same position/size - Power bar: 0..PAMaxPower W, green; SWR bar: 1..10, amber/red past 2.5 - SWR > 2.5: red border, red label, "SWR High" warning text - Remove PWR/SWR bars and labels from TX panel (left column) - Remove FWD/SWR from status bar; remove frequency from RUNNING status Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+214
-6
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user