mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
ui(beacon): redesign decoder diagnostics form
This commit is contained in:
+309
-72
@@ -17,19 +17,26 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Math, StrUtils,
|
Classes, SysUtils, Math, StrUtils,
|
||||||
Forms, Controls, Graphics, ExtCtrls, StdCtrls,
|
Forms, Controls, Graphics, ExtCtrls, StdCtrls,
|
||||||
AppTheme, BeaconDecoder, BeaconFEC, RadioController;
|
AppTheme, BeaconDecoder, BeaconFEC, RadioController, DpiUtils;
|
||||||
|
|
||||||
type
|
type
|
||||||
TBeaconScopeForm = class(TForm)
|
TBeaconScopeForm = class(TForm)
|
||||||
private
|
private
|
||||||
FCtl: TRadioController;
|
FCtl: TRadioController;
|
||||||
|
FHeader: TPanel;
|
||||||
|
FTitle: TLabel;
|
||||||
|
FSubtitle: TLabel;
|
||||||
FBox: TPaintBox;
|
FBox: TPaintBox;
|
||||||
|
FBulletin: TPanel;
|
||||||
|
FBulletinTitle: TLabel;
|
||||||
|
FBulletinHint: TLabel;
|
||||||
FMemo: TMemo; // декодированный текст бюллетеня (AO-40 кадры)
|
FMemo: TMemo; // декодированный текст бюллетеня (AO-40 кадры)
|
||||||
FTimer: TTimer;
|
FTimer: TTimer;
|
||||||
FTheme: TAppTheme;
|
FTheme: TAppTheme;
|
||||||
FLastFrames: Int64; // счётчик кадров на прошлом тике (детект нового)
|
FLastFrames: Int64; // счётчик кадров на прошлом тике (детект нового)
|
||||||
procedure DoTick(Sender: TObject);
|
procedure DoTick(Sender: TObject);
|
||||||
procedure DoPaint(Sender: TObject);
|
procedure DoPaint(Sender: TObject);
|
||||||
|
procedure FormResize(Sender: TObject);
|
||||||
procedure PollFrame;
|
procedure PollFrame;
|
||||||
function FrameToText(const Frame: TBeaconFrame): string;
|
function FrameToText(const Frame: TBeaconFrame): string;
|
||||||
public
|
public
|
||||||
@@ -40,26 +47,105 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
constructor TBeaconScopeForm.CreateWith(AOwner: TComponent; ACtl: TRadioController);
|
constructor TBeaconScopeForm.CreateWith(AOwner: TComponent; ACtl: TRadioController);
|
||||||
|
var
|
||||||
|
WorkArea: TRect;
|
||||||
|
MaxW, MaxH: Integer;
|
||||||
begin
|
begin
|
||||||
inherited CreateNew(AOwner);
|
inherited CreateNew(AOwner);
|
||||||
FCtl := ACtl;
|
FCtl := ACtl;
|
||||||
FTheme := DarkTheme;
|
FTheme := DarkTheme;
|
||||||
|
|
||||||
Caption := 'QO-100 Beacon — Constellation + Bulletin';
|
// Геометрия формы задаётся ниже уже в DPI-aware координатах.
|
||||||
|
Scaled := False;
|
||||||
|
Caption := 'QO-100 Beacon';
|
||||||
BorderStyle := bsSizeable;
|
BorderStyle := bsSizeable;
|
||||||
Width := 600;
|
if AOwner is TCustomForm then
|
||||||
Height := 680;
|
begin
|
||||||
Position := poScreenCenter;
|
WorkArea := TCustomForm(AOwner).BoundsRect;
|
||||||
|
WorkArea := Screen.MonitorFromRect(WorkArea).WorkareaRect;
|
||||||
|
Position := poOwnerFormCenter;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
WorkArea := Screen.PrimaryMonitor.WorkareaRect;
|
||||||
|
Position := poScreenCenter;
|
||||||
|
end;
|
||||||
|
MaxW := WorkArea.Right - WorkArea.Left - DpiScale(48);
|
||||||
|
MaxH := WorkArea.Bottom - WorkArea.Top - DpiScale(48);
|
||||||
|
Width := Min(DpiScale(760), MaxW);
|
||||||
|
Height := Min(DpiScale(700), MaxH);
|
||||||
|
Constraints.MinWidth := Min(DpiScale(520), Width);
|
||||||
|
Constraints.MinHeight := Min(DpiScale(560), Height);
|
||||||
|
Color := FTheme.BG;
|
||||||
|
|
||||||
// Текст бюллетеня (декодированные AO-40 кадры) — снизу, моноширинный, ReadOnly.
|
FHeader := TPanel.Create(Self);
|
||||||
|
FHeader.Parent := Self;
|
||||||
|
FHeader.Align := alTop;
|
||||||
|
FHeader.Height := DpiScale(72);
|
||||||
|
FHeader.BevelOuter := bvNone;
|
||||||
|
FHeader.Color := FTheme.Panel;
|
||||||
|
|
||||||
|
FTitle := TLabel.Create(Self);
|
||||||
|
FTitle.Parent := FHeader;
|
||||||
|
FTitle.Caption := 'QO-100 BEACON DECODER';
|
||||||
|
FTitle.AutoSize := False;
|
||||||
|
FTitle.SetBounds(DpiScale(20), DpiScale(12),
|
||||||
|
DpiScale(520), DpiScale(26));
|
||||||
|
FTitle.Font.Size := 15;
|
||||||
|
FTitle.Font.Style := [fsBold];
|
||||||
|
FTitle.Font.Color := FTheme.Text;
|
||||||
|
|
||||||
|
FSubtitle := TLabel.Create(Self);
|
||||||
|
FSubtitle.Parent := FHeader;
|
||||||
|
FSubtitle.Caption :=
|
||||||
|
'BPSK constellation, carrier recovery and AO-40 telemetry';
|
||||||
|
FSubtitle.AutoSize := False;
|
||||||
|
FSubtitle.SetBounds(DpiScale(20), DpiScale(40),
|
||||||
|
DpiScale(620), DpiScale(20));
|
||||||
|
FSubtitle.Font.Size := 9;
|
||||||
|
FSubtitle.Font.Color := FTheme.TextDim;
|
||||||
|
|
||||||
|
FBulletin := TPanel.Create(Self);
|
||||||
|
FBulletin.Parent := Self;
|
||||||
|
FBulletin.Align := alBottom;
|
||||||
|
FBulletin.Height := DpiScale(236);
|
||||||
|
FBulletin.BevelOuter := bvNone;
|
||||||
|
FBulletin.Color := FTheme.Panel;
|
||||||
|
|
||||||
|
FBulletinTitle := TLabel.Create(Self);
|
||||||
|
FBulletinTitle.Parent := FBulletin;
|
||||||
|
FBulletinTitle.Caption := 'DECODED BULLETIN';
|
||||||
|
FBulletinTitle.AutoSize := False;
|
||||||
|
FBulletinTitle.SetBounds(DpiScale(16), DpiScale(9),
|
||||||
|
DpiScale(220), DpiScale(20));
|
||||||
|
FBulletinTitle.Font.Size := 9;
|
||||||
|
FBulletinTitle.Font.Style := [fsBold];
|
||||||
|
FBulletinTitle.Font.Color := FTheme.MeterOn;
|
||||||
|
|
||||||
|
FBulletinHint := TLabel.Create(Self);
|
||||||
|
FBulletinHint.Parent := FBulletin;
|
||||||
|
FBulletinHint.Caption := 'latest AO-40 frames · read-only';
|
||||||
|
FBulletinHint.AutoSize := False;
|
||||||
|
FBulletinHint.SetBounds(DpiScale(250), DpiScale(9),
|
||||||
|
DpiScale(300), DpiScale(20));
|
||||||
|
FBulletinHint.Anchors := [akTop, akRight];
|
||||||
|
FBulletinHint.Left := FBulletin.ClientWidth - DpiScale(316);
|
||||||
|
FBulletinHint.Alignment := taRightJustify;
|
||||||
|
FBulletinHint.Font.Size := 8;
|
||||||
|
FBulletinHint.Font.Color := FTheme.TextDim;
|
||||||
|
|
||||||
|
// Бюллетень остаётся обычным read-only memo: текст можно выделить и скопировать.
|
||||||
FMemo := TMemo.Create(Self);
|
FMemo := TMemo.Create(Self);
|
||||||
FMemo.Parent := Self;
|
FMemo.Parent := FBulletin;
|
||||||
FMemo.Align := alBottom;
|
FMemo.Align := alClient;
|
||||||
FMemo.Height := 230;
|
FMemo.BorderSpacing.Left := DpiScale(12);
|
||||||
|
FMemo.BorderSpacing.Top := DpiScale(36);
|
||||||
|
FMemo.BorderSpacing.Right := DpiScale(12);
|
||||||
|
FMemo.BorderSpacing.Bottom := DpiScale(12);
|
||||||
|
FMemo.BorderStyle := bsNone;
|
||||||
FMemo.ReadOnly := True;
|
FMemo.ReadOnly := True;
|
||||||
FMemo.ScrollBars := ssVertical;
|
FMemo.ScrollBars := ssVertical;
|
||||||
FMemo.WordWrap := False;
|
FMemo.WordWrap := False;
|
||||||
FMemo.Font.Name := 'Courier New';
|
|
||||||
FMemo.Font.Size := 9;
|
FMemo.Font.Size := 9;
|
||||||
FMemo.Color := FTheme.BG;
|
FMemo.Color := FTheme.BG;
|
||||||
FMemo.Font.Color := FTheme.Text;
|
FMemo.Font.Color := FTheme.Text;
|
||||||
@@ -68,6 +154,8 @@ begin
|
|||||||
FBox.Parent := Self;
|
FBox.Parent := Self;
|
||||||
FBox.Align := alClient;
|
FBox.Align := alClient;
|
||||||
FBox.OnPaint := @DoPaint;
|
FBox.OnPaint := @DoPaint;
|
||||||
|
OnResize := @FormResize;
|
||||||
|
FormResize(nil);
|
||||||
|
|
||||||
FTimer := TTimer.Create(Self);
|
FTimer := TTimer.Create(Self);
|
||||||
FTimer.Interval := 40; // ~25 Гц
|
FTimer.Interval := 40; // ~25 Гц
|
||||||
@@ -78,10 +166,42 @@ end;
|
|||||||
procedure TBeaconScopeForm.ApplyTheme(const T: TAppTheme);
|
procedure TBeaconScopeForm.ApplyTheme(const T: TAppTheme);
|
||||||
begin
|
begin
|
||||||
FTheme := T;
|
FTheme := T;
|
||||||
|
Color := T.BG;
|
||||||
|
if FHeader <> nil then FHeader.Color := T.Panel;
|
||||||
|
if FTitle <> nil then FTitle.Font.Color := T.Text;
|
||||||
|
if FSubtitle <> nil then FSubtitle.Font.Color := T.TextDim;
|
||||||
|
if FBulletin <> nil then FBulletin.Color := T.Panel;
|
||||||
|
if FBulletinTitle <> nil then FBulletinTitle.Font.Color := T.MeterOn;
|
||||||
|
if FBulletinHint <> nil then FBulletinHint.Font.Color := T.TextDim;
|
||||||
if FMemo <> nil then
|
if FMemo <> nil then
|
||||||
begin
|
begin
|
||||||
FMemo.Color := FTheme.BG;
|
FMemo.Color := T.BG;
|
||||||
FMemo.Font.Color := FTheme.Text;
|
FMemo.Font.Color := T.Text;
|
||||||
|
end;
|
||||||
|
if FBox <> nil then FBox.Invalidate;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBeaconScopeForm.FormResize(Sender: TObject);
|
||||||
|
var
|
||||||
|
BulletinH, MaxBulletinH: Integer;
|
||||||
|
begin
|
||||||
|
if (FHeader <> nil) and (FBulletin <> nil) then
|
||||||
|
begin
|
||||||
|
MaxBulletinH := ClientHeight - FHeader.Height - DpiScale(270);
|
||||||
|
BulletinH := Min(DpiScale(236),
|
||||||
|
Max(DpiScale(150), MaxBulletinH));
|
||||||
|
if FBulletin.Height <> BulletinH then
|
||||||
|
FBulletin.Height := BulletinH;
|
||||||
|
end;
|
||||||
|
if FTitle <> nil then
|
||||||
|
FTitle.Width := Max(0, FHeader.ClientWidth - DpiScale(40));
|
||||||
|
if FSubtitle <> nil then
|
||||||
|
FSubtitle.Width := Max(0, FHeader.ClientWidth - DpiScale(40));
|
||||||
|
if FBulletinHint <> nil then
|
||||||
|
begin
|
||||||
|
FBulletinHint.Left := DpiScale(250);
|
||||||
|
FBulletinHint.Width := Max(0,
|
||||||
|
FBulletin.ClientWidth - DpiScale(266));
|
||||||
end;
|
end;
|
||||||
if FBox <> nil then FBox.Invalidate;
|
if FBox <> nil then FBox.Invalidate;
|
||||||
end;
|
end;
|
||||||
@@ -134,46 +254,163 @@ end;
|
|||||||
procedure TBeaconScopeForm.DoPaint(Sender: TObject);
|
procedure TBeaconScopeForm.DoPaint(Sender: TObject);
|
||||||
var
|
var
|
||||||
C: TCanvas;
|
C: TCanvas;
|
||||||
W, H, cx, cy, r, i, px, py: Integer;
|
W, H, M, Gap, CardGap, GraphSize, cx, cy, r, i, px, py: Integer;
|
||||||
|
GraphR, MetricsR, PlotR: TRect;
|
||||||
S: TBeaconScope;
|
S: TBeaconScope;
|
||||||
ok: Boolean;
|
ok, Wide: Boolean;
|
||||||
C1, C2: TColor;
|
CarrierText, SymbolText, SNRText, BaudText: string;
|
||||||
st: string;
|
OffsetText, ResidText, FramesText, FECText: string;
|
||||||
|
TechText: string;
|
||||||
|
CarrierColor, SymbolColor: TColor;
|
||||||
|
|
||||||
|
procedure SetUIFont(ASize: Integer; AStyle: TFontStyles = []);
|
||||||
|
begin
|
||||||
|
C.Font.Assign(Self.Font);
|
||||||
|
C.Font.Size := ASize;
|
||||||
|
C.Font.Style := AStyle;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure DrawCard(const R: TRect; const ATitle: string);
|
||||||
|
begin
|
||||||
|
C.Brush.Style := bsSolid;
|
||||||
|
C.Brush.Color := FTheme.Panel;
|
||||||
|
C.Pen.Style := psSolid;
|
||||||
|
C.Pen.Width := 1;
|
||||||
|
C.Pen.Color := FTheme.Border;
|
||||||
|
C.RoundRect(R.Left, R.Top, R.Right, R.Bottom,
|
||||||
|
DpiScale(8), DpiScale(8));
|
||||||
|
SetUIFont(8, [fsBold]);
|
||||||
|
C.Font.Color := FTheme.TextDim;
|
||||||
|
C.Brush.Style := bsClear;
|
||||||
|
C.TextOut(R.Left + DpiScale(14), R.Top + DpiScale(10), ATitle);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure DrawMetricTile(const R: TRect; const ALabel, AValue: string;
|
||||||
|
AValueColor: TColor);
|
||||||
|
begin
|
||||||
|
C.Brush.Style := bsSolid;
|
||||||
|
C.Brush.Color := FTheme.BG;
|
||||||
|
C.Pen.Style := psSolid;
|
||||||
|
C.Pen.Color := FTheme.Border;
|
||||||
|
C.RoundRect(R.Left, R.Top, R.Right, R.Bottom,
|
||||||
|
DpiScale(5), DpiScale(5));
|
||||||
|
|
||||||
|
C.Brush.Style := bsClear;
|
||||||
|
SetUIFont(8);
|
||||||
|
C.Font.Color := FTheme.TextDim;
|
||||||
|
C.TextOut(R.Left + DpiScale(9), R.Top + DpiScale(6), ALabel);
|
||||||
|
SetUIFont(9, [fsBold]);
|
||||||
|
C.Font.Color := AValueColor;
|
||||||
|
C.TextOut(R.Left + DpiScale(9), R.Top + DpiScale(21), AValue);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure DrawMetrics;
|
||||||
|
const
|
||||||
|
LABELS: array[0..7] of string =
|
||||||
|
('CARRIER', 'SYMBOL', 'SNR', 'SYMBOL RATE',
|
||||||
|
'TUNE OFFSET', 'RESIDUAL', 'FRAMES', 'FEC');
|
||||||
|
var
|
||||||
|
Values: array[0..7] of string;
|
||||||
|
Colors: array[0..7] of TColor;
|
||||||
|
Cols, Rows, Col, Row, TileW, TileH, X, Y, n: Integer;
|
||||||
|
R: TRect;
|
||||||
|
begin
|
||||||
|
Values[0] := CarrierText; Values[1] := SymbolText;
|
||||||
|
Values[2] := SNRText; Values[3] := BaudText;
|
||||||
|
Values[4] := OffsetText; Values[5] := ResidText;
|
||||||
|
Values[6] := FramesText; Values[7] := FECText;
|
||||||
|
for n := 0 to 7 do Colors[n] := FTheme.Text;
|
||||||
|
Colors[0] := CarrierColor;
|
||||||
|
Colors[1] := SymbolColor;
|
||||||
|
if ok and S.HasFrame then Colors[6] := FTheme.MeterOn;
|
||||||
|
if ok and S.HasFrame then Colors[7] := FTheme.MeterOn;
|
||||||
|
|
||||||
|
if Wide then begin Cols := 2; Rows := 4; end
|
||||||
|
else begin Cols := 4; Rows := 2; end;
|
||||||
|
CardGap := DpiScale(8);
|
||||||
|
TileW := (MetricsR.Right - MetricsR.Left - DpiScale(28)
|
||||||
|
- (Cols - 1) * CardGap) div Cols;
|
||||||
|
TileH := (MetricsR.Bottom - MetricsR.Top - DpiScale(70)
|
||||||
|
- (Rows - 1) * CardGap) div Rows;
|
||||||
|
for n := 0 to 7 do
|
||||||
|
begin
|
||||||
|
Col := n mod Cols;
|
||||||
|
Row := n div Cols;
|
||||||
|
X := MetricsR.Left + DpiScale(14) + Col * (TileW + CardGap);
|
||||||
|
Y := MetricsR.Top + DpiScale(34) + Row * (TileH + CardGap);
|
||||||
|
R := Rect(X, Y, X + TileW, Y + TileH);
|
||||||
|
DrawMetricTile(R, LABELS[n], Values[n], Colors[n]);
|
||||||
|
end;
|
||||||
|
SetUIFont(7);
|
||||||
|
C.Font.Color := FTheme.TextDim;
|
||||||
|
C.Brush.Style := bsClear;
|
||||||
|
C.TextOut(MetricsR.Left + DpiScale(14),
|
||||||
|
MetricsR.Bottom - DpiScale(20), TechText);
|
||||||
|
end;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
C := FBox.Canvas;
|
C := FBox.Canvas;
|
||||||
W := FBox.Width; H := FBox.Height;
|
W := FBox.Width; H := FBox.Height;
|
||||||
|
|
||||||
// фон
|
|
||||||
C.Brush.Style := bsSolid;
|
C.Brush.Style := bsSolid;
|
||||||
C.Brush.Color := FTheme.BG;
|
C.Brush.Color := FTheme.BG;
|
||||||
C.FillRect(0, 0, W, H);
|
C.FillRect(0, 0, W, H);
|
||||||
|
if (W <= 0) or (H <= 0) then Exit;
|
||||||
|
|
||||||
// область констелляции — квадрат сверху, текст снизу
|
M := DpiScale(16);
|
||||||
r := (Min(W, H - 70) - 20) div 2;
|
Gap := DpiScale(14);
|
||||||
if r < 20 then r := 20;
|
Wide := W >= DpiScale(600);
|
||||||
if r > 110 then r := 110; // кап: оставляем место метрикам над memo
|
if Wide then
|
||||||
cx := W div 2;
|
begin
|
||||||
cy := 10 + r;
|
GraphSize := Min(H - 2 * M, (W - 3 * M) div 2);
|
||||||
|
GraphR := Rect(M, M, M + GraphSize, H - M);
|
||||||
|
MetricsR := Rect(GraphR.Right + Gap, M, W - M, H - M);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
GraphSize := Max(DpiScale(74), H - DpiScale(154) - 3 * M);
|
||||||
|
GraphR := Rect(M, M, W - M, M + GraphSize);
|
||||||
|
MetricsR := Rect(M, GraphR.Bottom + Gap, W - M, H - M);
|
||||||
|
end;
|
||||||
|
DrawCard(GraphR, 'CONSTELLATION');
|
||||||
|
DrawCard(MetricsR, 'DECODER STATUS');
|
||||||
|
|
||||||
// сетка/оси
|
// Квадрат констелляции центрируется внутри своей карточки.
|
||||||
|
GraphSize := Min(GraphR.Right - GraphR.Left - DpiScale(28),
|
||||||
|
GraphR.Bottom - GraphR.Top - DpiScale(48));
|
||||||
|
GraphSize := Max(DpiScale(40), GraphSize);
|
||||||
|
PlotR.Left := GraphR.Left +
|
||||||
|
(GraphR.Right - GraphR.Left - GraphSize) div 2;
|
||||||
|
PlotR.Top := GraphR.Top + DpiScale(34) +
|
||||||
|
(GraphR.Bottom - GraphR.Top - DpiScale(40) - GraphSize) div 2;
|
||||||
|
PlotR.Right := PlotR.Left + GraphSize;
|
||||||
|
PlotR.Bottom := PlotR.Top + GraphSize;
|
||||||
|
cx := (PlotR.Left + PlotR.Right) div 2;
|
||||||
|
cy := (PlotR.Top + PlotR.Bottom) div 2;
|
||||||
|
r := GraphSize div 2 - DpiScale(8);
|
||||||
|
|
||||||
|
C.Brush.Style := bsSolid;
|
||||||
|
C.Brush.Color := FTheme.BG;
|
||||||
|
C.Pen.Style := psSolid;
|
||||||
|
C.Pen.Color := FTheme.Border;
|
||||||
|
C.Rectangle(PlotR.Left, PlotR.Top, PlotR.Right, PlotR.Bottom);
|
||||||
C.Pen.Style := psSolid;
|
C.Pen.Style := psSolid;
|
||||||
C.Pen.Color := FTheme.SpecGrid;
|
C.Pen.Color := FTheme.SpecGrid;
|
||||||
C.Line(cx - r, cy, cx + r, cy); // ось I (горизонт)
|
C.Line(cx - r, cy, cx + r, cy);
|
||||||
C.Line(cx, cy - r, cx, cy + r); // ось Q (вертикаль)
|
C.Line(cx, cy - r, cx, cy + r);
|
||||||
C.Pen.Color := FTheme.Border;
|
|
||||||
C.Brush.Style := bsClear;
|
C.Brush.Style := bsClear;
|
||||||
C.Rectangle(cx - r, cy - r, cx + r, cy + r);
|
C.Ellipse(cx - r, cy - r, cx + r, cy + r);
|
||||||
|
C.Ellipse(cx - r div 2, cy - r div 2, cx + r div 2, cy + r div 2);
|
||||||
|
|
||||||
// целевые точки BPSK (±1, 0) — ориентир
|
|
||||||
C.Pen.Color := FTheme.TextDim;
|
C.Pen.Color := FTheme.TextDim;
|
||||||
C.Ellipse(cx + r div 2 - 3, cy - 3, cx + r div 2 + 3, cy + 3);
|
C.Ellipse(cx + r div 2 - DpiScale(3), cy - DpiScale(3),
|
||||||
C.Ellipse(cx - r div 2 - 3, cy - 3, cx - r div 2 + 3, cy + 3);
|
cx + r div 2 + DpiScale(3), cy + DpiScale(3));
|
||||||
|
C.Ellipse(cx - r div 2 - DpiScale(3), cy - DpiScale(3),
|
||||||
|
cx - r div 2 + DpiScale(3), cy + DpiScale(3));
|
||||||
|
|
||||||
ok := (FCtl <> nil) and FCtl.GetBeaconScope(S);
|
ok := (FCtl <> nil) and FCtl.GetBeaconScope(S);
|
||||||
|
|
||||||
if ok and S.Enabled then
|
if ok and S.Enabled then
|
||||||
begin
|
begin
|
||||||
// точки констелляции (нормированы к ±1; масштаб r/2 → ±1 на половине радиуса)
|
|
||||||
C.Pen.Style := psClear;
|
C.Pen.Style := psClear;
|
||||||
C.Brush.Style := bsSolid;
|
C.Brush.Style := bsSolid;
|
||||||
C.Brush.Color := FTheme.MeterOn;
|
C.Brush.Color := FTheme.MeterOn;
|
||||||
@@ -182,53 +419,53 @@ begin
|
|||||||
px := cx + Round(S.PtI[i] * (r / 2));
|
px := cx + Round(S.PtI[i] * (r / 2));
|
||||||
py := cy - Round(S.PtQ[i] * (r / 2));
|
py := cy - Round(S.PtQ[i] * (r / 2));
|
||||||
if (px >= cx - r) and (px <= cx + r) and (py >= cy - r) and (py <= cy + r) then
|
if (px >= cx - r) and (px <= cx + r) and (py >= cy - r) and (py <= cy + r) then
|
||||||
C.FillRect(px - 1, py - 1, px + 2, py + 2);
|
C.FillRect(px - DpiScale(1), py - DpiScale(1),
|
||||||
|
px + DpiScale(2), py + DpiScale(2));
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ----- метрики -----
|
|
||||||
C.Brush.Style := bsClear;
|
|
||||||
C.Font.Name := 'Courier New';
|
|
||||||
C.Font.Size := 9;
|
|
||||||
py := cy + r + 12;
|
|
||||||
|
|
||||||
if not ok then
|
if not ok then
|
||||||
begin
|
begin
|
||||||
C.Font.Color := FTheme.TextDim;
|
CarrierText := 'UNAVAILABLE';
|
||||||
C.TextOut(14, py, 'decoder unavailable');
|
SymbolText := 'UNAVAILABLE';
|
||||||
Exit;
|
SNRText := '--'; BaudText := '--';
|
||||||
|
OffsetText := '--'; ResidText := '--';
|
||||||
|
FramesText := '--'; FECText := '--';
|
||||||
|
TechText := 'Decoder data unavailable';
|
||||||
|
CarrierColor := FTheme.TextDim;
|
||||||
|
SymbolColor := FTheme.TextDim;
|
||||||
end;
|
end;
|
||||||
if not S.Enabled then
|
if ok and (not S.Enabled) then
|
||||||
begin
|
begin
|
||||||
C.Font.Color := FTheme.TextDim;
|
CarrierText := 'OFF'; SymbolText := 'OFF';
|
||||||
C.TextOut(14, py, 'decoder OFF (enable via BEACON \ decode)');
|
SNRText := '--'; BaudText := '--';
|
||||||
Exit;
|
OffsetText := '--'; ResidText := '--';
|
||||||
|
FramesText := IntToStr(S.Frames);
|
||||||
|
FECText := '--';
|
||||||
|
TechText := 'Enable BEACON and select the beacon on the spectrum';
|
||||||
|
CarrierColor := FTheme.TextDim;
|
||||||
|
SymbolColor := FTheme.TextDim;
|
||||||
end;
|
end;
|
||||||
|
if ok and S.Enabled then
|
||||||
if S.CarrierLock then C1 := FTheme.MeterOn else C1 := FTheme.TextDim;
|
begin
|
||||||
if S.SymbolLock then C2 := FTheme.MeterOn else C2 := FTheme.TextDim;
|
CarrierText := IfThen(S.CarrierLock, 'LOCKED', 'SEARCH');
|
||||||
|
SymbolText := IfThen(S.SymbolLock, 'LOCKED', 'SEARCH');
|
||||||
C.Font.Color := C1;
|
if S.SNRdB > -50 then SNRText := Format('%.1f dB', [S.SNRdB])
|
||||||
C.TextOut(14, py, 'CARRIER ' + IfThen(S.CarrierLock, 'LOCK', '----'));
|
else SNRText := '--';
|
||||||
C.Font.Color := C2;
|
BaudText := Format('%.1f Bd', [S.SymRate]);
|
||||||
C.TextOut(14, py + 18, 'SYMBOL ' + IfThen(S.SymbolLock, 'LOCK', '----'));
|
OffsetText := Format('%.0f Hz', [S.OffsetHz]);
|
||||||
|
ResidText := Format('%.0f Hz', [S.ResidHz]);
|
||||||
C.Font.Color := FTheme.Text;
|
FramesText := IntToStr(S.Frames);
|
||||||
if S.SNRdB > -50 then st := Format('%.1f dB', [S.SNRdB]) else st := '--';
|
FECText := Format('RS %d · P%d', [S.LastRSErr, S.ManPhase]);
|
||||||
C.TextOut(14, py + 36, 'SNR ' + st);
|
TechText := Format('INV %s · PROM %.0f · Fs %.0f · Fd %.0f · SPS %.1f',
|
||||||
C.TextOut(14, py + 54, Format('BAUD %.1f (off %.0f Hz)', [S.SymRate, S.OffsetHz]));
|
[IfThen(FCtl.BeaconDecodeInvert, 'ON', 'OFF'), S.CarProm,
|
||||||
C.Font.Color := FTheme.TextDim;
|
S.FsHz, S.FdecHz, S.Sps]);
|
||||||
C.TextOut(14, py + 72, Format('CARRIER resid %.0f Hz prom %.0f', [S.ResidHz, S.CarProm]));
|
if S.CarrierLock then CarrierColor := FTheme.MeterOn
|
||||||
if FCtl <> nil then
|
else CarrierColor := FTheme.TextDim;
|
||||||
C.TextOut(14, py + 90, 'INVERT ' + IfThen(FCtl.BeaconDecodeInvert, 'ON', 'OFF'));
|
if S.SymbolLock then SymbolColor := FTheme.MeterOn
|
||||||
C.TextOut(14, py + 108, Format('RATE Fs %.0f Fdec %.0f sps %.1f',
|
else SymbolColor := FTheme.TextDim;
|
||||||
[S.FsHz, S.FdecHz, S.Sps]));
|
end;
|
||||||
|
DrawMetrics;
|
||||||
// ----- FEC (AO-40) -----
|
|
||||||
if S.HasFrame then C.Font.Color := FTheme.MeterOn else C.Font.Color := FTheme.TextDim;
|
|
||||||
C.TextOut(14, py + 132, Format('FRAMES %d (RS err %d)', [S.Frames, S.LastRSErr]));
|
|
||||||
C.Font.Color := FTheme.TextDim;
|
|
||||||
C.TextOut(14, py + 150, Format('MANCHESTER phase %d', [S.ManPhase]));
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user