Extract helpers into BoardUtils, WisdomBuilder, UISync, PlatformUtils

- BoardUtils: BoardTypeName (removes duplication with DeviceForm)
- WisdomBuilder: TWisdomBuildThread + TWisdomProgressDialog
- UISync: TDeviceFoundSync/TStatusUISync/TDDCSeqSync via callbacks
  (avoids circular dependency, removes TObject casts)
- PlatformUtils: HasOpenGLSpectrumSwitch + CurrentScreenDPI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:15:51 +03:00
co-authored by Claude Sonnet 4.6
parent aa252cb741
commit 9716b88b0c
7 changed files with 305 additions and 270 deletions
+29
View File
@@ -0,0 +1,29 @@
unit BoardUtils;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
function BoardTypeName(BoardType: Integer): string;
implementation
uses SysUtils;
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
end.
+2 -18
View File
@@ -6,10 +6,8 @@ interface
uses uses
Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs, Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, IniFiles; StdCtrls, ExtCtrls, ComCtrls, IniFiles,
BoardUtils;
// Декодирование типа платы (совпадает с MainForm.BoardTypeName)
function BoardTypeName(BoardType: Integer): string;
const const
DEVICE_CFG_FILE = 'hpsdr_devices.ini'; DEVICE_CFG_FILE = 'hpsdr_devices.ini';
@@ -114,20 +112,6 @@ type
implementation implementation
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
const const
CLR_BG = TColor($00121212); CLR_BG = TColor($00121212);
CLR_PANEL = TColor($001A1A1A); CLR_PANEL = TColor($001A1A1A);
+5 -251
View File
@@ -26,7 +26,6 @@ uses
FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs, FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, Menus, Math, Types, StdCtrls, ExtCtrls, Buttons, Menus, Math, Types,
OpenGLContext, OpenGLContext,
FlatProgressBar,
LCLIntf, LCLType, GraphType, LCLIntf, LCLType, GraphType,
HPSDRProtocol, HPSDRNetwork, HPSDRProtocol, HPSDRNetwork,
WDSP, WDSPEngine, AudioOutput, AudioInput, WDSP, WDSPEngine, AudioOutput, AudioInput,
@@ -37,7 +36,8 @@ uses
WidebandView, WidebandView,
StatusBar, StatusBar,
PlatformUtils, PlatformUtils,
WinFirewall; WinFirewall,
BoardUtils, WisdomBuilder, UISync;
const const
CLR_BG = TColor($00101010); CLR_BG = TColor($00101010);
@@ -138,45 +138,6 @@ type
Display: string; Display: string;
end; end;
// Синхронизирующий объект для OnDeviceFound
TDeviceFoundSync = class
private
FForm: TObject; // TMainForm, через forward ref
FDev: THPSDRDevice;
FEntry: string;
public
constructor Create(AForm: TObject; const D: THPSDRDevice; const E: string);
procedure Execute;
end;
// Синхронизирующий объект для HP Status
TStatusUISync = class
private
FForm: TObject;
FFwdW: Double;
FSWRV: Double;
FSupplyV: Double;
FSupplyA: Double;
FPLLLock: Boolean;
FHWPTT: Boolean;
FADCOverload: Byte;
public
constructor Create(AForm: TObject; FW, SW, SV, SA: Double; PLL, HWPTT: Boolean;
ADCOverload: Byte);
procedure Execute;
end;
// Синхронизирующий объект для DDC IQ
TDDCSeqSync = class
private
FForm: TObject;
FDDCIdx: Integer;
FSeq: LongWord;
public
constructor Create(AForm: TObject; Idx: Integer; Seq: LongWord);
procedure Execute;
end;
{ TMainForm } { TMainForm }
TMainForm = class(TForm) TMainForm = class(TForm)
private private
@@ -750,30 +711,6 @@ type
var Handled: Boolean); var Handled: Boolean);
end; end;
TWisdomBuildThread = class(TThread)
private
FDirectory: string;
FError: string;
protected
procedure Execute; override;
public
constructor Create(const ADirectory: string);
property ErrorText: string read FError;
end;
TWisdomProgressDialog = class(TForm)
private
FInfoLabel: TLabel;
FProgress: TFlatProgressBar;
FTimer: TTimer;
FThread: TWisdomBuildThread;
FPhase: Integer;
procedure TimerTick(Sender: TObject);
public
constructor Create(AOwner: TComponent; AThread: TWisdomBuildThread;
const ATheme: TAppTheme); reintroduce;
end;
var var
MainForm: TMainForm; MainForm: TMainForm;
@@ -783,20 +720,6 @@ uses SettingsForm;
{$R *.lfm} {$R *.lfm}
function HasOpenGLSpectrumSwitch: Boolean;
var
I: Integer;
S: string;
begin
Result := False;
for I := 1 to ParamCount do
begin
S := LowerCase(ParamStr(I));
if (S = '--opengl') or (S = '-opengl') or (S = '/opengl') then
Exit(True);
end;
end;
function TMainForm.LeftPanelButtonWidth(PanelWidth, ColCount, function TMainForm.LeftPanelButtonWidth(PanelWidth, ColCount,
ColIndex: Integer): Integer; ColIndex: Integer): Integer;
var var
@@ -822,179 +745,10 @@ begin
LEFT_PANEL_BTN_GAP); LEFT_PANEL_BTN_GAP);
end; end;
constructor TWisdomBuildThread.Create(const ADirectory: string);
begin
inherited Create(False);
FreeOnTerminate := False;
FDirectory := ADirectory;
FError := '';
end;
procedure TWisdomBuildThread.Execute;
var
DirA: AnsiString;
begin
try
if not Assigned(@WDSPwisdom) then Exit;
DirA := AnsiString(FDirectory);
WDSPwisdom(PAnsiChar(DirA));
except
on E: Exception do
FError := E.ClassName + ': ' + E.Message;
end;
end;
constructor TWisdomProgressDialog.Create(AOwner: TComponent;
AThread: TWisdomBuildThread; const ATheme: TAppTheme);
begin
inherited CreateNew(AOwner);
FThread := AThread;
FPhase := 0;
Caption := 'WDSP Wisdom';
Width := 520;
Height := 140;
Position := poScreenCenter;
BorderStyle := bsDialog;
BorderIcons := [];
Color := ATheme.BG;
Font.Name := 'Courier New';
Font.Size := 9;
Font.Color := ATheme.Text;
FInfoLabel := TLabel.Create(Self);
FInfoLabel.Parent := Self;
FInfoLabel.SetBounds(16, 16, 480, 40);
FInfoLabel.AutoSize := False;
FInfoLabel.WordWrap := True;
FInfoLabel.Font.Color := ATheme.Text;
FInfoLabel.Caption :=
'Creating FFTW wisdom for WDSP. This is done once and may take a while on the first run.';
FProgress := TFlatProgressBar.Create(Self);
FProgress.Parent := Self;
FProgress.SetBounds(16, 72, 480, 22);
FProgress.Min := 0;
FProgress.Max := 100;
FProgress.Position := 0;
FProgress.SetAppTheme(ATheme);
FTimer := TTimer.Create(Self);
FTimer.Interval := 250;
FTimer.OnTimer := TimerTick;
FTimer.Enabled := True;
end;
procedure TWisdomProgressDialog.TimerTick(Sender: TObject);
var
P: PAnsiChar;
S: string;
begin
FPhase := (FPhase + 7) mod 101;
FProgress.Position := FPhase;
if Assigned(@wisdom_get_status) then
begin
P := wisdom_get_status;
if P <> nil then
begin
S := Trim(string(AnsiString(P)));
if S <> '' then
FInfoLabel.Caption := S;
end;
end;
if Assigned(FThread) and FThread.Finished then
begin
FTimer.Enabled := False;
ModalResult := mrOk;
end;
end;
// ===========================================================================
// Общая функция декодирования типа платы — используется везде
// ===========================================================================
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
// ===========================================================================
// Sync helpers
// ===========================================================================
constructor TDeviceFoundSync.Create(AForm: TObject;
const D: THPSDRDevice; const E: string);
begin
inherited Create;
FForm := AForm;
FDev := D;
FEntry := E;
end;
procedure TDeviceFoundSync.Execute;
begin
TMainForm(FForm).DoAddDevice(FDev, FEntry);
end;
constructor TStatusUISync.Create(AForm: TObject;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
begin
inherited Create;
FForm := AForm;
FFwdW := FW;
FSWRV := SW;
FSupplyV := SV;
FSupplyA := SA;
FPLLLock := PLL;
FHWPTT := HWPTT;
FADCOverload := ADCOverload;
end;
procedure TStatusUISync.Execute;
begin
TMainForm(FForm).DoUpdateStatus(FFwdW, FSWRV, FSupplyV, FSupplyA, FPLLLock, FHWPTT,
FADCOverload);
end;
constructor TDDCSeqSync.Create(AForm: TObject; Idx: Integer; Seq: LongWord);
begin
inherited Create;
FForm := AForm;
FDDCIdx := Idx;
FSeq := Seq;
end;
procedure TDDCSeqSync.Execute;
begin
TMainForm(FForm).DoUpdateDDCSeqOrNoDevice(FDDCIdx, FSeq);
end;
// ===========================================================================
// FormCreate / FormDestroy
// ===========================================================================
// =========================================================================== // ===========================================================================
// Settings helpers // Settings helpers
// =========================================================================== // ===========================================================================
function CurrentScreenDPI: Integer;
begin
Result := Screen.PixelsPerInch;
if Result <= 0 then Result := 96;
end;
procedure TMainForm.RestoreWindowBounds; procedure TMainForm.RestoreWindowBounds;
var var
L, T, Wd, Ht, SavedDPI, CurDPI: Integer; L, T, Wd, Ht, SavedDPI, CurDPI: Integer;
@@ -3394,7 +3148,7 @@ begin
Entry := Format('%s %s FW:%d DDC:%d', Entry := Format('%s %s FW:%d DDC:%d',
[Dev.IPAddress, BoardName, [Dev.IPAddress, BoardName,
Dev.FirmwareVersion, Dev.NumDDCs]); Dev.FirmwareVersion, Dev.NumDDCs]);
Sync := TDeviceFoundSync.Create(Self, Dev, Entry); Sync := TDeviceFoundSync.Create(DoAddDevice, Dev, Entry);
try try
M := Sync.Execute; M := Sync.Execute;
TThread.Synchronize(nil, M); TThread.Synchronize(nil, M);
@@ -3483,7 +3237,7 @@ begin
end else end else
SWRV := 1.0; SWRV := 1.0;
if not IsTx then FwdW := 0; if not IsTx then FwdW := 0;
Sync := TStatusUISync.Create(Self, FwdW, SWRV, SupplyV, SupplyA, Sync := TStatusUISync.Create(DoUpdateStatus, FwdW, SWRV, SupplyV, SupplyA,
(Status.StatusBits and HPS_PLL_LOCKED) <> 0, (Status.StatusBits and HPS_PLL_LOCKED) <> 0,
(Status.StatusBits and HPS_PTT) <> 0, (Status.StatusBits and HPS_PTT) <> 0,
Status.ADCOverload); Status.ADCOverload);
@@ -3886,7 +3640,7 @@ begin
if Length(Devs) = 0 then if Length(Devs) = 0 then
begin begin
// DDCIdx = -1 is the sentinel for "no device found" // DDCIdx = -1 is the sentinel for "no device found"
Sync := TDDCSeqSync.Create(FForm, -1, 0); Sync := TDDCSeqSync.Create(FForm.DoUpdateDDCSeqOrNoDevice, -1, 0);
try try
M := Sync.Execute; M := Sync.Execute;
TThread.Synchronize(nil, M); TThread.Synchronize(nil, M);
+23 -1
View File
@@ -11,11 +11,13 @@ unit PlatformUtils;
interface interface
function QtPlatformAllowsOpenGLControls: Boolean; function QtPlatformAllowsOpenGLControls: Boolean;
function HasOpenGLSpectrumSwitch: Boolean;
function CurrentScreenDPI: Integer;
implementation implementation
uses uses
SysUtils; SysUtils, Forms;
function QtPlatformAllowsOpenGLControls: Boolean; function QtPlatformAllowsOpenGLControls: Boolean;
var var
@@ -39,4 +41,24 @@ begin
Result := SessionType <> 'wayland'; Result := SessionType <> 'wayland';
end; end;
function HasOpenGLSpectrumSwitch: Boolean;
var
I: Integer;
S: string;
begin
Result := False;
for I := 1 to ParamCount do
begin
S := LowerCase(ParamStr(I));
if (S = '--opengl') or (S = '-opengl') or (S = '/opengl') then
Exit(True);
end;
end;
function CurrentScreenDPI: Integer;
begin
Result := Screen.PixelsPerInch;
if Result <= 0 then Result := 96;
end;
end. end.
+105
View File
@@ -0,0 +1,105 @@
unit UISync;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, HPSDRNetwork;
type
TAddDeviceProc = procedure(const Dev: THPSDRDevice; const Entry: string) of object;
TUpdateStatusProc = procedure(FwdW, SWRV, SupplyV, SupplyA: Double;
PLLLock, HWPTT: Boolean; ADCOverload: Byte) of object;
TUpdateDDCSeqOrNoDeviceProc = procedure(DDCIdx: Integer; Seq: LongWord) of object;
TDeviceFoundSync = class
private
FCallback: TAddDeviceProc;
FDev: THPSDRDevice;
FEntry: string;
public
constructor Create(ACallback: TAddDeviceProc; const D: THPSDRDevice;
const E: string);
procedure Execute;
end;
TStatusUISync = class
private
FCallback: TUpdateStatusProc;
FFwdW: Double;
FSWRV: Double;
FSupplyV: Double;
FSupplyA: Double;
FPLLLock: Boolean;
FHWPTT: Boolean;
FADCOverload: Byte;
public
constructor Create(ACallback: TUpdateStatusProc;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
procedure Execute;
end;
TDDCSeqSync = class
private
FCallback: TUpdateDDCSeqOrNoDeviceProc;
FDDCIdx: Integer;
FSeq: LongWord;
public
constructor Create(ACallback: TUpdateDDCSeqOrNoDeviceProc;
Idx: Integer; Seq: LongWord);
procedure Execute;
end;
implementation
constructor TDeviceFoundSync.Create(ACallback: TAddDeviceProc;
const D: THPSDRDevice; const E: string);
begin
inherited Create;
FCallback := ACallback;
FDev := D;
FEntry := E;
end;
procedure TDeviceFoundSync.Execute;
begin
FCallback(FDev, FEntry);
end;
constructor TStatusUISync.Create(ACallback: TUpdateStatusProc;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
begin
inherited Create;
FCallback := ACallback;
FFwdW := FW;
FSWRV := SW;
FSupplyV := SV;
FSupplyA := SA;
FPLLLock := PLL;
FHWPTT := HWPTT;
FADCOverload := ADCOverload;
end;
procedure TStatusUISync.Execute;
begin
FCallback(FFwdW, FSWRV, FSupplyV, FSupplyA, FPLLLock, FHWPTT, FADCOverload);
end;
constructor TDDCSeqSync.Create(ACallback: TUpdateDDCSeqOrNoDeviceProc;
Idx: Integer; Seq: LongWord);
begin
inherited Create;
FCallback := ACallback;
FDDCIdx := Idx;
FSeq := Seq;
end;
procedure TDDCSeqSync.Execute;
begin
FCallback(FDDCIdx, FSeq);
end;
end.
+129
View File
@@ -0,0 +1,129 @@
unit WisdomBuilder;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, SysUtils, Forms, Controls, ExtCtrls, StdCtrls,
WDSP, AppTheme, FlatProgressBar;
type
TWisdomBuildThread = class(TThread)
private
FDirectory: string;
FError: string;
protected
procedure Execute; override;
public
constructor Create(const ADirectory: string);
property ErrorText: string read FError;
end;
TWisdomProgressDialog = class(TForm)
private
FInfoLabel: TLabel;
FProgress: TFlatProgressBar;
FTimer: TTimer;
FThread: TWisdomBuildThread;
FPhase: Integer;
procedure TimerTick(Sender: TObject);
public
constructor Create(AOwner: TComponent; AThread: TWisdomBuildThread;
const ATheme: TAppTheme); reintroduce;
end;
implementation
constructor TWisdomBuildThread.Create(const ADirectory: string);
begin
inherited Create(False);
FreeOnTerminate := False;
FDirectory := ADirectory;
FError := '';
end;
procedure TWisdomBuildThread.Execute;
var
DirA: AnsiString;
begin
try
if not Assigned(@WDSPwisdom) then Exit;
DirA := AnsiString(FDirectory);
WDSPwisdom(PAnsiChar(DirA));
except
on E: Exception do
FError := E.ClassName + ': ' + E.Message;
end;
end;
constructor TWisdomProgressDialog.Create(AOwner: TComponent;
AThread: TWisdomBuildThread; const ATheme: TAppTheme);
begin
inherited CreateNew(AOwner);
FThread := AThread;
FPhase := 0;
Caption := 'WDSP Wisdom';
Width := 520;
Height := 140;
Position := poScreenCenter;
BorderStyle := bsDialog;
BorderIcons := [];
Color := ATheme.BG;
Font.Name := 'Courier New';
Font.Size := 9;
Font.Color := ATheme.Text;
FInfoLabel := TLabel.Create(Self);
FInfoLabel.Parent := Self;
FInfoLabel.SetBounds(16, 16, 480, 40);
FInfoLabel.AutoSize := False;
FInfoLabel.WordWrap := True;
FInfoLabel.Font.Color := ATheme.Text;
FInfoLabel.Caption :=
'Creating FFTW wisdom for WDSP. This is done once and may take a while on the first run.';
FProgress := TFlatProgressBar.Create(Self);
FProgress.Parent := Self;
FProgress.SetBounds(16, 72, 480, 22);
FProgress.Min := 0;
FProgress.Max := 100;
FProgress.Position := 0;
FProgress.SetAppTheme(ATheme);
FTimer := TTimer.Create(Self);
FTimer.Interval := 250;
FTimer.OnTimer := TimerTick;
FTimer.Enabled := True;
end;
procedure TWisdomProgressDialog.TimerTick(Sender: TObject);
var
P: PAnsiChar;
S: string;
begin
FPhase := (FPhase + 7) mod 101;
FProgress.Position := FPhase;
if Assigned(@wisdom_get_status) then
begin
P := wisdom_get_status;
if P <> nil then
begin
S := Trim(string(AnsiString(P)));
if S <> '' then
FInfoLabel.Caption := S;
end;
end;
if Assigned(FThread) and FThread.Finished then
begin
FTimer.Enabled := False;
ModalResult := mrOk;
end;
end;
end.
+12
View File
@@ -214,6 +214,18 @@
<Filename Value="PlatformUtils.pas"/> <Filename Value="PlatformUtils.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit> </Unit>
<Unit>
<Filename Value="BoardUtils.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="WisdomBuilder.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UISync.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit> <Unit>
<Filename Value="SpectrumViewOpengl.pas"/> <Filename Value="SpectrumViewOpengl.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>