mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
В TBackendCaps восемь флагов заполнялись обоими бэкендами и не читались нигде, а в ApplyBackendCapsToUI на этом месте стояла расписка «(TODO) гейтинг полей, которых нет у бэкенда». Из-за этого на AD936x оператору предлагали крутить то, чего у платы нет, — и хуже всего страница PA: калибровка драйва по диапазонам там просто ничего не делает (мощность задаётся железной аттенюацией, drive-байта нет вовсе), но выглядела рабочей. Теперь гейт идёт по возможностям, а не по виду устройства: - HasPA — потолок мощности и калибровки драйва (HF + трансвертеры) против «TX att @100% drive»; заголовок и подзаголовок страницы следуют содержимому; - HasHWMic — строка микрофонного разъёма (DUC Specific байт 50). Mic gain остаётся: он идёт в WDSP и работает с любым источником, включая звук с ПК, поэтому поднимается в освободившуюся строку; - HasWideband — галки широкополосного АЦП; сам режим принудительно гасится, иначе сохранённый в конфиге wideband оставлял бы пустую панель без способа её выключить; - HasDitherRandom — группа ADC целиком, Web Server встаёт на её место. Вкладка QO-100 убрана на openHPSDR: TRadioController.InQO100 требует IsPluto, то есть транспондер там недостижим. Симметрично OC Control, которого нет у AD936x. Измеритель: TSMeterView.HasPATelemetry. Без моста детектора FLastFwdW/FLastSWR стоят на значениях инициализации, и «0W / SWR 1.0» на передаче — не показание, а выдумка. Шкала рисуется пустой: деления и рамка на месте, заливки нет, ватты на делениях не подписаны (они считаются от Max power, которого у AD936x нет). ★Отдельно — дефект раскладки, который всё это вскрыло. Перекладка страницы настроек ломается в двух случаях, и оба случались при смене трансивера на живой программе (Stop/Discover/Connect/Start), а не при старте: 1. Страница ПРОКРУЧЕНА. TScrollBox двигает детей, и SetBounds задаёт координаты относительно прокрученного клиента: всё уезжало вверх, а первая группа оставалась на месте — под переключателями Profile/EQ/Hardware зияла дыра. Лечение — сброс прокрутки первой строкой (так уже делал RelayoutCalTab). 2. Страница СПРЯТАНА. Тогда сброс не доходит до виджета, а сохранённое смещение возвращается при показе — и дыра та же. А попадает перекладка почти всегда именно на спрятанную страницу: гейты отрабатывают при открытии окна, когда активна одна вкладка, а до Transmit/Calibration оператор доходит потом. Лечение — перекладка спрятанной страницы взводит флаг и выходит, а SelectPage исполняет её в момент показа (RunDueRelayout). Гард стоит внутри каждой Relayout*, а не у вызывающих: у RelayoutCalTab их два, и второй (LoadXvtrSettings) приходит как раз на спрятанную страницу. Проверено офскрин-рендером окна настроек в обе стороны (openHPSDR ↔ AD936x) для Transmit/Power Amplifier/Calibration/Advanced, в том числе на прокрученной и спрятанной странице. При полном железе координаты совпадают со старыми. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M37eCTZZ9aqppKWkZRxXkP
554 lines
22 KiB
ObjectPascal
554 lines
22 KiB
ObjectPascal
unit SMeterView;
|
||
|
||
{
|
||
SMeterView.pas — рендеринг S-метра, TX-метра и индикаторов мощности/SWR.
|
||
TSMeterView — изолированный класс без зависимостей на SpectrumView.
|
||
}
|
||
|
||
{$IFDEF FPC}
|
||
{$MODE Delphi}
|
||
{$ENDIF}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
||
IntfGraphics, FPImage, LCLType, GraphType, AppTheme, PlatformUtils;
|
||
|
||
const
|
||
SM_CLR_METER_ON = TColor($0000CC44);
|
||
SM_CLR_AMBER = TColor($0000AAFF);
|
||
|
||
type
|
||
TSMeterView = class
|
||
private
|
||
FSmBitmap: TBitmap;
|
||
FTheme: TAppTheme;
|
||
FLastSMeter: Double;
|
||
FSMeterPeak: Double;
|
||
FSMeterMin: Double;
|
||
FLastFwdW: Double;
|
||
FLastSWR: Double;
|
||
FPAMaxPower: Double;
|
||
// Есть ли у железа мост детектора мощности (Caps.HasPA). У AD936x его нет:
|
||
// FLastFwdW/FLastSWR так и стоят на значениях инициализации, и «0W / SWR
|
||
// 1.0» на передаче — не показание, а выдумка. Шкала в этом случае рисуется
|
||
// пустой: деления есть, заливки и цифр нет.
|
||
FHasPATelemetry: Boolean;
|
||
FTransmitting: Boolean;
|
||
FPbSMeterRight: TPaintBox;
|
||
// Масштаб канвы: 2 на Retina, 1 на Windows/Linux. Битмап растёт во столько
|
||
// же раз, пиксельные константы и шрифты умножаются на него.
|
||
FScale: Integer;
|
||
|
||
procedure DrawSMeterWide(ACanvas: TCanvas; R: TRect;
|
||
Value, Peak, MinVal: Double;
|
||
out ZX1, ZX2, ZY1, ZY2: Integer);
|
||
procedure DrawSMeterZone(ACanvas: TCanvas; R: TRect;
|
||
X1, X2, Y1, Y2: Integer);
|
||
procedure DrawTXMeter(ACanvas: TCanvas; R: TRect);
|
||
public
|
||
constructor Create;
|
||
destructor Destroy; override;
|
||
|
||
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 PAMaxPower: Double read FPAMaxPower write FPAMaxPower;
|
||
property HasPATelemetry: Boolean read FHasPATelemetry write FHasPATelemetry;
|
||
property Transmitting: Boolean read FTransmitting write FTransmitting;
|
||
property PbSMeterRight: TPaintBox write FPbSMeterRight;
|
||
|
||
procedure PaintSMeterRight(Sender: TObject);
|
||
procedure SetTheme(const T: TAppTheme);
|
||
end;
|
||
|
||
implementation
|
||
|
||
// ════════════════════════════════════════════════════════════════════════════
|
||
// TSMeterView
|
||
// ════════════════════════════════════════════════════════════════════════════
|
||
|
||
constructor TSMeterView.Create;
|
||
begin
|
||
inherited Create;
|
||
FSmBitmap := nil;
|
||
FTheme := DarkTheme;
|
||
FLastSMeter := -130;
|
||
FSMeterPeak := -130;
|
||
FSMeterMin := -130;
|
||
FLastFwdW := 0;
|
||
FLastSWR := 1;
|
||
FPAMaxPower := 100.0;
|
||
FHasPATelemetry := True;
|
||
FTransmitting := False;
|
||
FScale := 1;
|
||
end;
|
||
|
||
destructor TSMeterView.Destroy;
|
||
begin
|
||
FreeAndNil(FSmBitmap);
|
||
inherited;
|
||
end;
|
||
|
||
procedure TSMeterView.SetTheme(const T: TAppTheme);
|
||
begin
|
||
FTheme := T;
|
||
end;
|
||
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
// DrawSMeterWide
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
|
||
procedure TSMeterView.DrawSMeterWide(ACanvas: TCanvas; R: TRect;
|
||
Value, Peak, MinVal: Double; out ZX1, ZX2, ZY1, ZY2: Integer);
|
||
const
|
||
DB_MIN = -127.0; DB_MAX = -13.0; DB_S9 = -73.0; DB_OVR = -43.0;
|
||
DBM_MARKS: array[0..5] of Double = (-120,-100,-80,-60,-40,-20);
|
||
DBM_LABELS: array[0..5] of string = ('-120','-100','-80','-60','-40','-20');
|
||
S_MARKS_DBM: array[0..7] of Double = (-121,-109,-97,-85,-73,-53,-33,-13);
|
||
S_MARKS_LBL: array[0..7] of string = ('S1','S3','S5','S7','S9','+20','+40','+60');
|
||
LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4;
|
||
TICK_LONG = 5; TICK_MED = 3;
|
||
var
|
||
CLR_SMETER_BG, CLR_BAR_GREEN, CLR_BAR_OVER: TColor;
|
||
CLR_PEAK_MARKER, CLR_TICK_GREEN, CLR_TICK_WHITE, CLR_TICK_BLUE: TColor;
|
||
CLR_LABEL_DBM, CLR_LABEL_S, CLR_SCALE_DBM: TColor;
|
||
CLR_SCALE_S_GREEN, CLR_SCALE_S_BLUE, CLR_BDR: TColor;
|
||
W, H, BX, BW, LeftInfo: Integer;
|
||
Y_BAR_TOP, Y_BAR_BOT: Integer;
|
||
BarEnd, PeakLeft, PeakRight, S9X, OvrX: Integer;
|
||
i, X, TW: Integer; Lbl: string; SNum, Over: Integer;
|
||
LblDbm, LblS: string;
|
||
TWDbm, LblZoneL, TH10, TH6, MaxTWDbm, MaxTWS, LabelNeed: Integer;
|
||
Sc: Integer; // масштаб канвы, см. FScale
|
||
|
||
function DBtoX(dB: Double): Integer; inline;
|
||
begin
|
||
Result := BX + Round((dB - DB_MIN) / (DB_MAX - DB_MIN) * 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_PEAK_MARKER := FTheme.SMeterPeakMarker;
|
||
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;
|
||
|
||
Sc := FScale;
|
||
W := R.Right - R.Left; H := R.Bottom - R.Top;
|
||
ZX1 := 0; ZX2 := 0; ZY1 := 0; ZY2 := 0;
|
||
if (W < 80 * Sc) or (H < 16 * Sc) then Exit;
|
||
|
||
LblDbm := Format('%.1f', [Value]);
|
||
if Value >= -73.0 then
|
||
begin
|
||
Over := Round(Value - (-73.0));
|
||
if Over < 5 then LblS := 'S9'
|
||
else begin Over := ((Over + 5) div 10) * 10; if Over = 0 then Over := 10; LblS := Format('S9+%d', [Over]); end;
|
||
end else if Value <= -121.0 then LblS := 'S1'
|
||
else begin
|
||
LblS := 'S1';
|
||
for SNum := 1 to 8 do
|
||
if Value >= (-121.0 + (SNum - 1) * 6.0) then LblS := 'S' + IntToStr(SNum);
|
||
end;
|
||
|
||
ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold];
|
||
ACanvas.Font.Size := 10 * Sc; TWDbm := ACanvas.TextWidth(LblDbm);
|
||
MaxTWDbm := ACanvas.TextWidth('-120.0');
|
||
MaxTWS := ACanvas.TextWidth('S9+60');
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [];
|
||
LabelNeed := Max(MaxTWDbm + 2 * Sc + ACanvas.TextWidth('dBm '), MaxTWS) + LABEL_PAD * 2 * Sc;
|
||
LeftInfo := Max(LEFT_INFO_MIN * Sc, LabelNeed + 4 * Sc);
|
||
BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD * Sc;
|
||
if BW < 10 * Sc then Exit;
|
||
LblZoneL := BX - LabelNeed;
|
||
|
||
Y_BAR_TOP := R.Top + H * 44 div 100;
|
||
Y_BAR_BOT := R.Top + H * 58 div 100;
|
||
S9X := DBtoX(DB_S9); OvrX := DBtoX(DB_OVR);
|
||
|
||
ACanvas.Brush.Color := CLR_SMETER_BG; ACanvas.Brush.Style := bsSolid;
|
||
ACanvas.Pen.Style := psClear; ACanvas.FillRect(R); ACanvas.Pen.Style := psSolid;
|
||
// Перо в физических пикселях: иначе на Retina все риски станут вдвое тоньше.
|
||
ACanvas.Pen.Width := Sc;
|
||
FillBar(BX, Y_BAR_TOP, S9X, Y_BAR_BOT, CLR_BAR_GREEN);
|
||
FillBar(S9X, Y_BAR_TOP, OvrX, Y_BAR_BOT, CLR_BAR_GREEN);
|
||
FillBar(OvrX, Y_BAR_TOP, BX + BW, Y_BAR_BOT, CLR_BAR_OVER);
|
||
|
||
BarEnd := DBtoX(Value);
|
||
PeakLeft := DBtoX(MinVal);
|
||
PeakRight := DBtoX(Peak);
|
||
ZX1 := Max(BX + 1, Min(PeakLeft, PeakRight));
|
||
ZX2 := Min(BX + BW - 1, Max(PeakLeft, PeakRight));
|
||
ZY1 := Y_BAR_TOP + Sc; ZY2 := Y_BAR_BOT - Sc;
|
||
|
||
if BarEnd > BX then
|
||
begin
|
||
if BarEnd <= S9X then
|
||
FillBar(BX, Y_BAR_TOP+Sc, BarEnd, Y_BAR_BOT-Sc, CLR_BAR_GREEN)
|
||
else begin
|
||
FillBar(BX, Y_BAR_TOP+Sc, S9X, Y_BAR_BOT-Sc, CLR_BAR_GREEN);
|
||
FillBar(S9X, Y_BAR_TOP+Sc, BarEnd, Y_BAR_BOT-Sc, CLR_BAR_OVER);
|
||
end;
|
||
end;
|
||
if (PeakRight > BX) and (PeakRight <= BX + BW) then
|
||
begin
|
||
ACanvas.Pen.Color := CLR_PEAK_MARKER; ACanvas.Pen.Width := 2 * Sc;
|
||
ACanvas.MoveTo(PeakRight, Y_BAR_TOP - (Y_BAR_BOT - Y_BAR_TOP));
|
||
ACanvas.LineTo(PeakRight, Y_BAR_BOT + (Y_BAR_BOT - Y_BAR_TOP));
|
||
ACanvas.Pen.Width := Sc;
|
||
end;
|
||
|
||
ACanvas.Brush.Style := bsClear; ACanvas.Pen.Color := CLR_BDR;
|
||
ACanvas.Rectangle(LblZoneL, Y_BAR_TOP, BX + BW, Y_BAR_BOT);
|
||
ACanvas.Pen.Color := FTheme.SMeterDivider;
|
||
ACanvas.MoveTo(BX, Y_BAR_TOP + Sc); ACanvas.LineTo(BX, Y_BAR_BOT - Sc);
|
||
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear;
|
||
for i := 0 to High(DBM_MARKS) do
|
||
begin
|
||
X := DBtoX(DBM_MARKS[i]);
|
||
VLine(X, Y_BAR_TOP - TICK_LONG * Sc, Y_BAR_TOP - Sc, CLR_TICK_WHITE);
|
||
Lbl := DBM_LABELS[i]; TW := ACanvas.TextWidth(Lbl);
|
||
ACanvas.Font.Color := CLR_SCALE_DBM;
|
||
ACanvas.TextOut(X - TW div 2, Y_BAR_TOP - TICK_LONG * Sc - ACanvas.TextHeight(Lbl) - Sc, Lbl);
|
||
end;
|
||
i := -125;
|
||
while i < -13 do
|
||
begin
|
||
X := DBtoX(i); VLine(X, Y_BAR_TOP - TICK_MED * Sc, Y_BAR_TOP - Sc, CLR_TICK_GREEN);
|
||
Inc(i, 5);
|
||
end;
|
||
for i := 0 to High(S_MARKS_DBM) do
|
||
begin
|
||
X := DBtoX(S_MARKS_DBM[i]);
|
||
if S_MARKS_DBM[i] >= DB_S9 then VLine(X, Y_BAR_BOT+Sc, Y_BAR_BOT+TICK_LONG*Sc, CLR_TICK_BLUE)
|
||
else VLine(X, Y_BAR_BOT+Sc, Y_BAR_BOT+TICK_LONG*Sc, CLR_TICK_GREEN);
|
||
Lbl := S_MARKS_LBL[i]; TW := ACanvas.TextWidth(Lbl);
|
||
if S_MARKS_DBM[i] >= DB_S9 then ACanvas.Font.Color := CLR_SCALE_S_BLUE
|
||
else ACanvas.Font.Color := CLR_SCALE_S_GREEN;
|
||
ACanvas.TextOut(X - TW div 2, Y_BAR_BOT + TICK_LONG * Sc + Sc, Lbl);
|
||
end;
|
||
|
||
ACanvas.Brush.Style := bsClear;
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0');
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0');
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc, Y_BAR_TOP - TH10 - 2 * Sc, LblDbm);
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Font.Color := CLR_TICK_BLUE;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc + TWDbm + 2 * Sc, Y_BAR_TOP - TH6 - 4 * Sc, 'dBm');
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_S;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc, Y_BAR_BOT + 2 * Sc, LblS);
|
||
end;
|
||
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
// DrawSMeterZone
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
|
||
procedure TSMeterView.DrawSMeterZone(ACanvas: TCanvas; R: TRect;
|
||
X1, X2, Y1, Y2: Integer);
|
||
const ALPHA = 16384;
|
||
var Img: TLazIntfImage; FC: TFPColor; IX, IY: Integer;
|
||
begin
|
||
if (X2 <= X1) or (FSmBitmap = nil) then Exit;
|
||
Img := FSmBitmap.CreateIntfImage;
|
||
try
|
||
for IY := Y1 to Y2 - 1 do
|
||
for IX := X1 to X2 - 1 do
|
||
begin
|
||
FC := Img.Colors[IX, IY];
|
||
FC.Red := Min(65535, FC.Red + ALPHA);
|
||
FC.Green := Min(65535, FC.Green + ALPHA);
|
||
FC.Blue := Min(65535, FC.Blue + ALPHA);
|
||
Img.Colors[IX, IY] := FC;
|
||
end;
|
||
FSmBitmap.LoadFromIntfImage(Img);
|
||
finally Img.Free; end;
|
||
end;
|
||
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
// DrawTXMeter
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
|
||
procedure TSMeterView.DrawTXMeter(ACanvas: TCanvas; R: TRect);
|
||
const
|
||
SWR_HIGH_THRESH = 2.5;
|
||
SWR_MAX = 10.0;
|
||
LEFT_INFO_MIN = 62; RIGHT_PAD = 8; LABEL_PAD = 4;
|
||
TICK_LONG = 5; TICK_MED = 3;
|
||
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;
|
||
Sc: Integer; // масштаб канвы, см. FScale
|
||
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);
|
||
|
||
Sc := FScale;
|
||
W := R.Right - R.Left; H := R.Bottom - R.Top;
|
||
if (W < 80 * Sc) or (H < 16 * Sc) then Exit;
|
||
|
||
SWRHigh := FHasPATelemetry and (FLastSWR > SWR_HIGH_THRESH);
|
||
if FHasPATelemetry then
|
||
begin
|
||
LblPwr := Format('%.0fW', [FLastFwdW]);
|
||
LblSWR := Format('SWR:%.1f', [FLastSWR]);
|
||
end
|
||
else
|
||
begin
|
||
LblPwr := ''; // измерять нечем — показания не выдумываем
|
||
LblSWR := '';
|
||
end;
|
||
|
||
ACanvas.Font.Name := 'Courier New'; ACanvas.Font.Style := [fsBold];
|
||
ACanvas.Font.Size := 10 * Sc;
|
||
TWPwr := ACanvas.TextWidth(LblPwr);
|
||
MaxTWPwr := ACanvas.TextWidth(Format('%.0fW', [FPAMaxPower]));
|
||
MaxTWSWR := ACanvas.TextWidth('SWR:10.0');
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [];
|
||
LabelNeed := Max(MaxTWPwr, MaxTWSWR) + LABEL_PAD * 2 * Sc;
|
||
LeftInfo := Max(LEFT_INFO_MIN * Sc, LabelNeed + 4 * Sc);
|
||
BX := R.Left + LeftInfo; BW := W - LeftInfo - RIGHT_PAD * Sc;
|
||
if BW < 10 * Sc then Exit;
|
||
LblZoneL := BX - LabelNeed;
|
||
|
||
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 - Sc;
|
||
SY1 := Y_BAR_MID + Sc;
|
||
SY2 := Y_BAR_BOT;
|
||
|
||
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;
|
||
// Перо в физических пикселях, как и в DrawSMeterWide.
|
||
ACanvas.Pen.Width := Sc;
|
||
|
||
FillBar(BX, PY1, BX + BW, PY2, CLR_BAR_GREEN);
|
||
FillBar(BX, SY1, S_WarnX, SY2, CLR_BAR_GREEN);
|
||
FillBar(S_WarnX, SY1, BX + BW, SY2, CLR_BAR_OVER);
|
||
|
||
if FHasPATelemetry then
|
||
begin
|
||
if FPAMaxPower > 0 then PwrPct := FLastFwdW / FPAMaxPower else PwrPct := 0;
|
||
PBarEnd := PwrToX(PwrPct);
|
||
FillBar(BX, PY1 + Sc, PBarEnd, PY2 - Sc, SM_CLR_METER_ON);
|
||
end;
|
||
|
||
if FHasPATelemetry then
|
||
begin
|
||
SBarEnd := SWRToX(FLastSWR);
|
||
if not SWRHigh then
|
||
FillBar(BX, SY1 + Sc, SBarEnd, SY2 - Sc, SM_CLR_AMBER)
|
||
else
|
||
begin
|
||
FillBar(BX, SY1 + Sc, Min(S_WarnX, SBarEnd), SY2 - Sc, SM_CLR_AMBER);
|
||
if SBarEnd > S_WarnX then
|
||
FillBar(S_WarnX, SY1 + Sc, SBarEnd, SY2 - Sc, CLR_SWR_HIGH);
|
||
end;
|
||
end;
|
||
|
||
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 + Sc); ACanvas.LineTo(BX, PY2 - Sc);
|
||
ACanvas.MoveTo(BX, SY1 + Sc); ACanvas.LineTo(BX, SY2 - Sc);
|
||
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; ACanvas.Brush.Style := bsClear;
|
||
|
||
i := 5;
|
||
while i < 100 do
|
||
begin
|
||
if (i mod 25) <> 0 then
|
||
begin
|
||
X := PwrToX(i / 100.0);
|
||
VLine(X, PY1 - TICK_MED * Sc, PY1 - Sc, CLR_TICK_GREEN);
|
||
end;
|
||
Inc(i, 5);
|
||
end;
|
||
|
||
for i := 0 to 4 do
|
||
begin
|
||
X := PwrToX(PWR_MARKS[i]);
|
||
VLine(X, PY1 - TICK_LONG * Sc, PY1 - Sc, CLR_TICK_WHITE);
|
||
// Ватты на делениях считаются от Max power, а он есть только там, где есть
|
||
// детектор. Без него оставляем голые деления: подписать их нечем.
|
||
if not FHasPATelemetry then Continue;
|
||
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 * Sc - ACanvas.TextHeight(Lbl) - Sc, Lbl);
|
||
end;
|
||
|
||
for i := 2 to 9 do
|
||
begin
|
||
if i = 5 then Continue;
|
||
X := SWRToX(i);
|
||
if i >= 3 then VLine(X, SY2 + Sc, SY2 + TICK_MED * Sc, CLR_TICK_BLUE)
|
||
else VLine(X, SY2 + Sc, SY2 + TICK_MED * Sc, CLR_TICK_GREEN);
|
||
end;
|
||
|
||
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 + Sc, SY2 + TICK_LONG * Sc, CLR_TICK_BLUE)
|
||
else
|
||
VLine(X, SY2 + Sc, SY2 + TICK_LONG * Sc, 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 * Sc + Sc, Lbl);
|
||
end;
|
||
|
||
ACanvas.Brush.Style := bsClear;
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; TH10 := ACanvas.TextHeight('0');
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := []; TH6 := ACanvas.TextHeight('0');
|
||
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_LABEL_DBM;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc, PY1 - TH10 - 2 * Sc, LblPwr);
|
||
|
||
ACanvas.Font.Size := 10 * Sc; ACanvas.Font.Style := [fsBold];
|
||
if SWRHigh then ACanvas.Font.Color := CLR_SWR_HIGH
|
||
else ACanvas.Font.Color := CLR_LABEL_S;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc, SY2 + 2 * Sc, LblSWR);
|
||
|
||
if SWRHigh then
|
||
begin
|
||
ACanvas.Font.Size := 6 * Sc; ACanvas.Font.Style := [fsBold]; ACanvas.Font.Color := CLR_SWR_HIGH;
|
||
ACanvas.TextOut(LblZoneL + 4 * Sc + TWPwr + 2 * Sc, PY1 - TH6 - 4 * Sc, 'SWR High');
|
||
end;
|
||
end;
|
||
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
// PaintSMeterRight
|
||
// ────────────────────────────────────────────────────────────────────────────
|
||
|
||
procedure TSMeterView.PaintSMeterRight(Sender: TObject);
|
||
var PB: TPaintBox; W, H, BmpW, BmpH, ZX1, ZX2, ZY1, ZY2: Integer;
|
||
begin
|
||
PB := TPaintBox(Sender);
|
||
W := PB.Width; H := PB.Height;
|
||
if (W <= 0) or (H <= 0) then Exit;
|
||
|
||
// Битмап держим в физических пикселях канвы, иначе Cocoa растянет его вдвое
|
||
// и картинка замылится. На Windows/Linux FScale=1 и всё как было.
|
||
FScale := GetControlScale(PB);
|
||
BmpW := W * FScale; BmpH := H * FScale;
|
||
|
||
if (FSmBitmap = nil) or (FSmBitmap.Width <> BmpW) or (FSmBitmap.Height <> BmpH) then
|
||
begin
|
||
FreeAndNil(FSmBitmap);
|
||
FSmBitmap := TBitmap.Create;
|
||
FSmBitmap.SetSize(BmpW, BmpH);
|
||
end;
|
||
if FTransmitting then
|
||
begin
|
||
DrawTXMeter(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH));
|
||
end else
|
||
begin
|
||
DrawSMeterWide(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH),
|
||
FLastSMeter, FSMeterPeak, FSMeterMin,
|
||
ZX1, ZX2, ZY1, ZY2);
|
||
DrawSMeterZone(FSmBitmap.Canvas, Rect(0, 0, BmpW, BmpH), ZX1, ZX2, ZY1, ZY2);
|
||
end;
|
||
|
||
if FScale = 1 then
|
||
PB.Canvas.Draw(0, 0, FSmBitmap)
|
||
else
|
||
PB.Canvas.StretchDraw(Rect(0, 0, W, H), FSmBitmap);
|
||
end;
|
||
|
||
end.
|