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.