mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
CW не работал ни на приём, ни на передачу: фильтр стоял симметрично вокруг нуля (тона не было), понятия pitch не существовало, MOX в CWL отправлял в эфир голос 2.9 кГц (в WDSP TXA_CWL — ветка SSB), байт CW-опций DUC всегда был нулевым, поэтому кейер, сайдтон и break-in в прошивке молчали. Pitch сделан ОДНИМ сдвигом в движке. Кромки фильтра везде остаются относительно VFO (у CW — симметрично нулю), а гетеродин и полосу двигают три метода WDSPEngine: CWLOOffset / PushShift / PushPassband. Прямых вызовов SetRXAShiftFreq и RXASetPassband в проекте больше нет. Так таблица фильтров не перестраивается при смене pitch (в отличие от Thetis), полоска на спектре и маркер VFO верны без правок в UI, а слайсы получают свой сдвиг по своему режиму. Правила эфира: - голосовой TXA в CW не запускается вообще (гард по режиму), MOX = только PTT; несущую даёт кейер, бит CWX или тон TUN; - байт 5 собирается из настроек и перепосылается на каждой смене режима и TX-слайса — вне CW он обязан быть нулевым, иначе прошивка поднимет PTT на замыкание ключа посреди SSB; - TUN в CW: тон = pitch и встречный сдвиг DUC, несущая встаёт ровно на VFO; - приёмник на передаче в CW не глушится, иначе умолкает программный сайдтон и эфир между посылками. Программная передача текста (CWMorse): поток с абсолютными дедлайнами дёргает бит CWX в High Priority, элементы рисует прошивка. Касание манипулятора или снятие MOX обрывают передачу. Память сообщений — окно CW Messages и F1..F8 в главном окне. Оживлены CAT KS/KY/ZZKM/ZZKS/ZZKY. Настройки — вкладка Transmit, подвкладка Hardware, перед блоком FM/CTCSS. Программный сайдтон точен для передачи текста и прямого ключа; при иамбике таймингом владеет FPGA, поэтому там верен только аппаратный тон. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
232 lines
7.1 KiB
ObjectPascal
232 lines
7.1 KiB
ObjectPascal
unit CWMessagesForm;
|
|
|
|
{ TCWMessagesForm — память телеграфных сообщений (CW_MSG_COUNT ячеек).
|
|
|
|
Зачем отдельным окном. Передача текста нужна ПОСРЕДИ связи, поэтому кнопки
|
|
«отправить» обязаны быть под рукой, а не в настройках. В блоке TX левой
|
|
панели свободных ячеек нет (MOX/TUN/PS/2TON + DUP/RX MUTE/профиль + DRV),
|
|
так что окно висит рядом — ровно как окно CWX у Thetis. Те же ячейки
|
|
привязаны к F1..F8 в главном окне.
|
|
|
|
Правки текста пишутся в TCWSettings сразу (идиома EWSDR: кнопки «сохранить»
|
|
нет), через тот же OnChanged, что и вкладка настроек. }
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Math,
|
|
Forms, Controls, Graphics, StdCtrls, ExtCtrls,
|
|
FlatButton, FlatEdit, AppTheme, Settings, DpiUtils;
|
|
|
|
type
|
|
TCWSendEvent = procedure(const Text: string) of object;
|
|
TCWMsgsChanged = procedure(const C: TCWSettings) of object;
|
|
|
|
TCWMessagesForm = class(TForm)
|
|
private
|
|
FCW: TCWSettings;
|
|
FTheme: TAppTheme;
|
|
FUpdating: Boolean;
|
|
FLbls: array[0..CW_MSG_COUNT-1] of TLabel;
|
|
FEdits: array[0..CW_MSG_COUNT-1] of TFlatEdit;
|
|
FSendBtns: array[0..CW_MSG_COUNT-1] of TFlatButton;
|
|
FBtnStop: TFlatButton;
|
|
FBtnClose: TFlatButton;
|
|
FHint: TLabel;
|
|
FOnSend: TCWSendEvent;
|
|
FOnAbort: TNotifyEvent;
|
|
FOnChanged: TCWMsgsChanged;
|
|
procedure BuildUI;
|
|
procedure EditChange(Sender: TObject);
|
|
procedure SendClick(Sender: TObject);
|
|
procedure StopClick(Sender: TObject);
|
|
procedure CloseClick(Sender: TObject);
|
|
procedure StyleBtn(B: TFlatButton);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure LoadFrom(const C: TCWSettings);
|
|
procedure ApplyTheme(const T: TAppTheme);
|
|
// Отправить ячейку по индексу (F1..F8 из главного окна).
|
|
procedure SendSlot(Idx: Integer);
|
|
property OnSend: TCWSendEvent read FOnSend write FOnSend;
|
|
property OnAbort: TNotifyEvent read FOnAbort write FOnAbort;
|
|
property OnChanged: TCWMsgsChanged read FOnChanged write FOnChanged;
|
|
end;
|
|
|
|
implementation
|
|
|
|
const
|
|
{$IFDEF WINDOWS}
|
|
UI_FONT = 'Segoe UI';
|
|
{$ELSE}
|
|
UI_FONT = 'Sans';
|
|
{$ENDIF}
|
|
FORM_W = 470;
|
|
PAD = 14;
|
|
ROW_H = 26;
|
|
ROW_GAP = 6;
|
|
LBL_W = 30;
|
|
BTN_W = 66;
|
|
|
|
constructor TCWMessagesForm.Create(AOwner: TComponent);
|
|
begin
|
|
inherited CreateNew(AOwner);
|
|
FTheme := DarkTheme;
|
|
FUpdating := False;
|
|
TSettingsManager.DefaultCW(FCW);
|
|
|
|
Scaled := False;
|
|
Caption := 'CW Messages';
|
|
BorderStyle := bsSingle;
|
|
if AOwner is TCustomForm then Position := poOwnerFormCenter
|
|
else Position := poScreenCenter;
|
|
Width := DpiScale(FORM_W);
|
|
// Ряды + строка кнопок + подсказка в ДВЕ строки под ними (в один ряд с
|
|
// кнопками она налезала на Close).
|
|
Height := DpiScale(2 * PAD + CW_MSG_COUNT * (ROW_H + ROW_GAP)
|
|
+ ROW_H + 8 + 34);
|
|
BuildUI;
|
|
ApplyTheme(DarkTheme);
|
|
end;
|
|
|
|
procedure TCWMessagesForm.BuildUI;
|
|
var
|
|
i, Y: Integer;
|
|
begin
|
|
Y := PAD;
|
|
for i := 0 to CW_MSG_COUNT - 1 do
|
|
begin
|
|
FLbls[i] := TLabel.Create(Self);
|
|
FLbls[i].Parent := Self;
|
|
FLbls[i].Caption := 'F' + IntToStr(i + 1);
|
|
FLbls[i].Font.Name := UI_FONT;
|
|
FLbls[i].Font.Size := 9;
|
|
FLbls[i].SetBounds(DpiScale(PAD), DpiScale(Y + 5),
|
|
DpiScale(LBL_W), DpiScale(18));
|
|
|
|
FEdits[i] := TFlatEdit.Create(Self);
|
|
FEdits[i].Parent := Self;
|
|
FEdits[i].Font.Name := UI_FONT;
|
|
FEdits[i].Font.Size := 9;
|
|
FEdits[i].MaxLength := 63;
|
|
FEdits[i].Tag := i;
|
|
FEdits[i].OnChange := @EditChange;
|
|
FEdits[i].SetBounds(DpiScale(PAD + LBL_W), DpiScale(Y),
|
|
DpiScale(FORM_W - 2 * PAD - LBL_W - BTN_W - 8),
|
|
DpiScale(ROW_H - 2));
|
|
|
|
FSendBtns[i] := TFlatButton.Create(Self);
|
|
FSendBtns[i].Parent := Self;
|
|
FSendBtns[i].Caption := 'Send';
|
|
FSendBtns[i].Tag := i;
|
|
FSendBtns[i].OnClick := @SendClick;
|
|
FSendBtns[i].SetBounds(DpiScale(FORM_W - PAD - BTN_W), DpiScale(Y),
|
|
DpiScale(BTN_W), DpiScale(ROW_H - 2));
|
|
Inc(Y, ROW_H + ROW_GAP);
|
|
end;
|
|
|
|
FBtnStop := TFlatButton.Create(Self);
|
|
FBtnStop.Parent := Self;
|
|
FBtnStop.Caption := 'Stop';
|
|
FBtnStop.OnClick := @StopClick;
|
|
FBtnStop.SetBounds(DpiScale(PAD), DpiScale(Y), DpiScale(BTN_W), DpiScale(ROW_H));
|
|
|
|
FBtnClose := TFlatButton.Create(Self);
|
|
FBtnClose.Parent := Self;
|
|
FBtnClose.Caption := 'Close';
|
|
FBtnClose.OnClick := @CloseClick;
|
|
FBtnClose.SetBounds(DpiScale(FORM_W - PAD - BTN_W), DpiScale(Y),
|
|
DpiScale(BTN_W), DpiScale(ROW_H));
|
|
|
|
FHint := TLabel.Create(Self);
|
|
FHint.Parent := Self;
|
|
FHint.Font.Name := UI_FONT;
|
|
FHint.Font.Size := 8;
|
|
FHint.AutoSize := False;
|
|
FHint.WordWrap := True;
|
|
FHint.Caption := 'F1..F8 in the main window send the same slots. '
|
|
+ 'Touching the paddle or dropping MOX stops sending.';
|
|
FHint.SetBounds(DpiScale(PAD), DpiScale(Y + ROW_H + 8),
|
|
DpiScale(FORM_W - 2 * PAD), DpiScale(34));
|
|
end;
|
|
|
|
procedure TCWMessagesForm.StyleBtn(B: TFlatButton);
|
|
begin
|
|
if B = nil then Exit;
|
|
B.ClrNorm := FTheme.BtnNorm;
|
|
B.ClrBorder := FTheme.BtnBorderNorm;
|
|
B.ClrHot := FTheme.BtnHot;
|
|
B.ClrActive := FTheme.BtnActive;
|
|
B.ClrText := FTheme.BtnText;
|
|
B.ClrTextAct := FTheme.BtnTextActive;
|
|
B.Font.Name := UI_FONT;
|
|
B.Font.Size := 9;
|
|
B.Invalidate;
|
|
end;
|
|
|
|
procedure TCWMessagesForm.ApplyTheme(const T: TAppTheme);
|
|
var i: Integer;
|
|
begin
|
|
FTheme := T;
|
|
Color := T.BG;
|
|
for i := 0 to CW_MSG_COUNT - 1 do
|
|
begin
|
|
FLbls[i].Font.Color := T.TextDim;
|
|
FEdits[i].SetAppTheme(T);
|
|
StyleBtn(FSendBtns[i]);
|
|
end;
|
|
StyleBtn(FBtnStop);
|
|
StyleBtn(FBtnClose);
|
|
FHint.Font.Color := T.TextDim;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCWMessagesForm.LoadFrom(const C: TCWSettings);
|
|
var i: Integer;
|
|
begin
|
|
FUpdating := True;
|
|
try
|
|
FCW := C;
|
|
for i := 0 to CW_MSG_COUNT - 1 do
|
|
FEdits[i].Text := C.Messages[i];
|
|
finally
|
|
FUpdating := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TCWMessagesForm.EditChange(Sender: TObject);
|
|
var i: Integer;
|
|
begin
|
|
if FUpdating then Exit;
|
|
i := TFlatEdit(Sender).Tag;
|
|
if (i < 0) or (i >= CW_MSG_COUNT) then Exit;
|
|
FCW.Messages[i] := Copy(TFlatEdit(Sender).Text, 1, 63);
|
|
if Assigned(FOnChanged) then FOnChanged(FCW);
|
|
end;
|
|
|
|
procedure TCWMessagesForm.SendSlot(Idx: Integer);
|
|
begin
|
|
if (Idx < 0) or (Idx >= CW_MSG_COUNT) then Exit;
|
|
if FCW.Messages[Idx] = '' then Exit;
|
|
if Assigned(FOnSend) then FOnSend(FCW.Messages[Idx]);
|
|
end;
|
|
|
|
procedure TCWMessagesForm.SendClick(Sender: TObject);
|
|
begin
|
|
SendSlot(TFlatButton(Sender).Tag);
|
|
end;
|
|
|
|
procedure TCWMessagesForm.StopClick(Sender: TObject);
|
|
begin
|
|
if Assigned(FOnAbort) then FOnAbort(Self);
|
|
end;
|
|
|
|
procedure TCWMessagesForm.CloseClick(Sender: TObject);
|
|
begin
|
|
Hide;
|
|
end;
|
|
|
|
end.
|