Files
ewsdr/ChannelsForm.pas
T

621 lines
19 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
unit ChannelsForm;
{ TChannelsForm — редактор списка каналов (channel memory).
Открывается по правой кнопке мыши на кнопке CH в блоке RX левой панели.
Список каналов слева (FlatListBox), поля редактирования справа.
Все изменения сохраняются немедленно в TChannelStore и на диск. }
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Math,
Forms, Controls, Graphics, StdCtrls, ExtCtrls,
FlatButton, FlatCheckBox, FlatComboBox, FlatEdit,
FlatSpinEdit, FlatFloatSpinEdit, FlatListBox,
AppTheme, ChannelStore, RadioModes;
const
// Копии из MainForm: нужны для выпадающих списков
CH_MODE_COUNT = RadioModes.MODE_COUNT;
CH_MODE_NAMES: array[0..CH_MODE_COUNT-1] of string =
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM','DMR','FM RAW');
CH_CTCSS_COUNT = 38;
CH_CTCSS_NAMES: array[0..CH_CTCSS_COUNT-1] of string = (
'67.0', '71.9', '74.4', '77.0', '79.7', '82.5', '85.4', '88.5',
'91.5', '94.8', '97.4', '100.0', '103.5', '107.2', '110.9', '114.8',
'118.8', '123.0', '127.3', '131.8', '136.5', '141.3', '146.2', '151.4',
'156.7', '162.2', '167.9', '173.8', '179.9', '186.2', '192.8', '203.5',
'210.7', '218.1', '225.7', '233.6', '241.8', '250.3');
type
TChannelsForm = class(TForm)
private
// --- Левая панель ---
FPanelLeft: TPanel;
FBtnAdd: TFlatButton;
FBtnDelete: TFlatButton;
FBtnUp: TFlatButton;
FBtnDown: TFlatButton;
FList: TFlatListBox;
// --- Правая панель (поля редактирования) ---
FPanelRight: TPanel;
// Labels (для перекраски темой)
FLbls: array of TLabel;
// Поля
FEdGroup: TFlatEdit;
FEdName: TFlatEdit;
FSpinRXFreq: TFlatFloatSpinEdit;
FSpinTXFreq: TFlatFloatSpinEdit;
FCmbMode: TFlatComboBox;
FBtnRptNone: TFlatButton;
FBtnRptMinus: TFlatButton;
FBtnRptPlus: TFlatButton;
FSpinRptOffset: TFlatFloatSpinEdit;
FChkCTCSS: TFlatCheckBox;
FCmbCTCSS: TFlatComboBox;
FSpinPower: TFlatSpinEdit;
// Данные
FStore: TChannelStore;
FUpdating: Boolean;
FOnChanged: TNotifyEvent;
FCurrentDefaults: TChannel; // значения по умолчанию для новых каналов
procedure BuildUI;
procedure StyleBtn(B: TFlatButton; AActive: Boolean = False);
function MakeLabel(AParent: TWinControl; const Cap: string;
X, Y, W, H: Integer): TLabel;
procedure LoadChannelToFields(Idx: Integer);
procedure SaveFieldsToChannel;
procedure RefreshList(KeepIdx: Integer = -1);
function SelectedIdx: Integer;
// Handlers — список
procedure OnListClick(Sender: TObject);
procedure OnListDblClick(Sender: TObject);
// Handlers — тулбар
procedure OnAddClick(Sender: TObject);
procedure OnDeleteClick(Sender: TObject);
procedure OnUpClick(Sender: TObject);
procedure OnDownClick(Sender: TObject);
// Handlers — поля
procedure OnFieldChange(Sender: TObject);
procedure OnRptBtnClick(Sender: TObject);
procedure OnCTCSSChkChange(Sender: TObject);
procedure OnCTCSSCmbChange(Sender: TObject);
// Handlers — форма
procedure OnFormClose(Sender: TObject; var CloseAction: TCloseAction);
public
constructor Create(AOwner: TComponent; AStore: TChannelStore;
AOnChanged: TNotifyEvent); reintroduce;
procedure ApplyTheme(const T: TAppTheme);
procedure SetCurrentDefaults(const Ch: TChannel);
property OnChannelsChanged: TNotifyEvent read FOnChanged write FOnChanged;
end;
implementation
const
// Геометрия
LPANEL_W = 185; // ширина левой панели
TOOLBAR_H = 32; // высота тулбара с кнопками Add/Del/Up/Down
BTN_H = 24;
// Правая панель
LBL_X = 8;
LBL_W = 95;
CTRL_X = 106;
ROW_H = 30; // высота строки
CTRL_H = 24; // высота контрола
constructor TChannelsForm.Create(AOwner: TComponent; AStore: TChannelStore;
AOnChanged: TNotifyEvent);
begin
inherited CreateNew(AOwner);
FStore := AStore;
FOnChanged := AOnChanged;
FUpdating := False;
TChannelStore.DefaultChannel(FCurrentDefaults);
Caption := 'Channels';
BorderStyle := bsSizeable;
Width := 720;
Height := 480;
Position := poScreenCenter;
KeyPreview := True;
OnClose := @OnFormClose;
BuildUI;
ApplyTheme(DarkTheme);
RefreshList(0);
end;
procedure TChannelsForm.BuildUI;
var
i: Integer;
BtnW: Integer;
RW: Integer; // ширина правой панели
Y: Integer;
begin
// ----- Левая панель -----
FPanelLeft := TPanel.Create(Self);
FPanelLeft.Parent := Self;
FPanelLeft.Align := alLeft;
FPanelLeft.Width := LPANEL_W;
FPanelLeft.BevelOuter := bvNone;
BtnW := (LPANEL_W - 4 - 3 * 2) div 4; // 4 кнопки с зазорами
FBtnAdd := TFlatButton.Create(Self);
FBtnAdd.Parent := FPanelLeft;
FBtnAdd.SetBounds(2, 4, BtnW, BTN_H);
FBtnAdd.Caption := 'ADD';
FBtnAdd.OnClick := @OnAddClick;
FBtnDelete := TFlatButton.Create(Self);
FBtnDelete.Parent := FPanelLeft;
FBtnDelete.SetBounds(2 + (BtnW + 2), 4, BtnW, BTN_H);
FBtnDelete.Caption := 'DEL';
FBtnDelete.OnClick := @OnDeleteClick;
FBtnUp := TFlatButton.Create(Self);
FBtnUp.Parent := FPanelLeft;
FBtnUp.SetBounds(2 + 2 * (BtnW + 2), 4, BtnW, BTN_H);
FBtnUp.Caption := '↑';
FBtnUp.OnClick := @OnUpClick;
FBtnDown := TFlatButton.Create(Self);
FBtnDown.Parent := FPanelLeft;
FBtnDown.SetBounds(2 + 3 * (BtnW + 2), 4, BtnW, BTN_H);
FBtnDown.Caption := '↓';
FBtnDown.OnClick := @OnDownClick;
FList := TFlatListBox.Create(Self);
FList.Parent := FPanelLeft;
FList.SetBounds(0, TOOLBAR_H, LPANEL_W, FPanelLeft.Height - TOOLBAR_H);
FList.Anchors := [akLeft, akTop, akRight, akBottom];
FList.OnClick := @OnListClick;
FList.OnDblClick := @OnListDblClick;
// ----- Правая панель -----
FPanelRight := TPanel.Create(Self);
FPanelRight.Parent := Self;
FPanelRight.Align := alClient;
FPanelRight.BevelOuter := bvNone;
SetLength(FLbls, 0);
RW := Width - LPANEL_W;
Y := 8;
// Group
MakeLabel(FPanelRight, 'Group', LBL_X, Y + 1, LBL_W, CTRL_H);
FEdGroup := TFlatEdit.Create(Self);
FEdGroup.Parent := FPanelRight;
FEdGroup.SetBounds(CTRL_X, Y, RW - CTRL_X - 8, CTRL_H);
FEdGroup.Anchors := [akLeft, akTop, akRight];
FEdGroup.OnChange := @OnFieldChange;
Inc(Y, ROW_H);
// Name
MakeLabel(FPanelRight, 'Name', LBL_X, Y + 1, LBL_W, CTRL_H);
FEdName := TFlatEdit.Create(Self);
FEdName.Parent := FPanelRight;
FEdName.SetBounds(CTRL_X, Y, RW - CTRL_X - 8, CTRL_H);
FEdName.Anchors := [akLeft, akTop, akRight];
FEdName.OnChange := @OnFieldChange;
Inc(Y, ROW_H);
// RX Freq
MakeLabel(FPanelRight, 'RX Freq', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinRXFreq := TFlatFloatSpinEdit.Create(Self);
FSpinRXFreq.Parent := FPanelRight;
FSpinRXFreq.SetBounds(CTRL_X, Y, RW - CTRL_X - 50, CTRL_H);
FSpinRXFreq.Anchors := [akLeft, akTop, akRight];
FSpinRXFreq.MinValue := 0.001;
FSpinRXFreq.MaxValue := 2000.0;
FSpinRXFreq.Increment := 0.001;
FSpinRXFreq.DecimalPlaces := 6;
FSpinRXFreq.Value := 145.5;
FSpinRXFreq.OnChange := @OnFieldChange;
MakeLabel(FPanelRight, 'MHz', RW - 46, Y + 1, 40, CTRL_H);
Inc(Y, ROW_H);
// TX Freq
MakeLabel(FPanelRight, 'TX Freq', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinTXFreq := TFlatFloatSpinEdit.Create(Self);
FSpinTXFreq.Parent := FPanelRight;
FSpinTXFreq.SetBounds(CTRL_X, Y, RW - CTRL_X - 50, CTRL_H);
FSpinTXFreq.Anchors := [akLeft, akTop, akRight];
FSpinTXFreq.MinValue := 0.001;
FSpinTXFreq.MaxValue := 2000.0;
FSpinTXFreq.Increment := 0.001;
FSpinTXFreq.DecimalPlaces := 6;
FSpinTXFreq.Value := 145.5;
FSpinTXFreq.OnChange := @OnFieldChange;
MakeLabel(FPanelRight, 'MHz', RW - 46, Y + 1, 40, CTRL_H);
Inc(Y, ROW_H);
// Mode
MakeLabel(FPanelRight, 'Mode', LBL_X, Y + 1, LBL_W, CTRL_H);
FCmbMode := TFlatComboBox.Create(Self);
FCmbMode.Parent := FPanelRight;
FCmbMode.SetBounds(CTRL_X, Y, 160, CTRL_H);
for i := 0 to CH_MODE_COUNT - 1 do FCmbMode.Items.Add(CH_MODE_NAMES[i]);
FCmbMode.ItemIndex := 5; // FM default
FCmbMode.OnChange := @OnFieldChange;
Inc(Y, ROW_H);
// RPTR direction
MakeLabel(FPanelRight, 'RPTR', LBL_X, Y + 1, LBL_W, CTRL_H);
FBtnRptNone := TFlatButton.Create(Self);
FBtnRptNone.Parent := FPanelRight;
FBtnRptNone.SetBounds(CTRL_X, Y, 55, CTRL_H);
FBtnRptNone.Caption := 'NONE';
FBtnRptNone.Tag := 0;
FBtnRptNone.OnClick := @OnRptBtnClick;
FBtnRptMinus := TFlatButton.Create(Self);
FBtnRptMinus.Parent := FPanelRight;
FBtnRptMinus.SetBounds(CTRL_X + 58, Y, 38, CTRL_H);
FBtnRptMinus.Caption := '';
FBtnRptMinus.Tag := 1;
FBtnRptMinus.OnClick := @OnRptBtnClick;
FBtnRptPlus := TFlatButton.Create(Self);
FBtnRptPlus.Parent := FPanelRight;
FBtnRptPlus.SetBounds(CTRL_X + 99, Y, 38, CTRL_H);
FBtnRptPlus.Caption := '+';
FBtnRptPlus.Tag := 2;
FBtnRptPlus.OnClick := @OnRptBtnClick;
Inc(Y, ROW_H);
// RPTR Offset
MakeLabel(FPanelRight, 'RPT Offset', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinRptOffset := TFlatFloatSpinEdit.Create(Self);
FSpinRptOffset.Parent := FPanelRight;
FSpinRptOffset.SetBounds(CTRL_X, Y, RW - CTRL_X - 50, CTRL_H);
FSpinRptOffset.Anchors := [akLeft, akTop, akRight];
FSpinRptOffset.MinValue := 0.0;
FSpinRptOffset.MaxValue := 100.0;
FSpinRptOffset.Increment := 0.025;
FSpinRptOffset.DecimalPlaces := 3;
FSpinRptOffset.Value := 0.6;
FSpinRptOffset.OnChange := @OnFieldChange;
MakeLabel(FPanelRight, 'MHz', RW - 46, Y + 1, 40, CTRL_H);
Inc(Y, ROW_H);
// CTCSS
MakeLabel(FPanelRight, 'CTCSS', LBL_X, Y + 1, LBL_W, CTRL_H);
FChkCTCSS := TFlatCheckBox.Create(Self);
FChkCTCSS.Parent := FPanelRight;
FChkCTCSS.SetBounds(CTRL_X, Y, 26, CTRL_H);
FChkCTCSS.Caption := '';
FChkCTCSS.Checked := False;
FChkCTCSS.OnChange := @OnCTCSSChkChange;
FCmbCTCSS := TFlatComboBox.Create(Self);
FCmbCTCSS.Parent := FPanelRight;
FCmbCTCSS.SetBounds(CTRL_X + 30, Y, RW - CTRL_X - 38, CTRL_H);
FCmbCTCSS.Anchors := [akLeft, akTop, akRight];
for i := 0 to CH_CTCSS_COUNT - 1 do FCmbCTCSS.Items.Add(CH_CTCSS_NAMES[i]);
FCmbCTCSS.ItemIndex := 0;
FCmbCTCSS.OnChange := @OnCTCSSCmbChange;
Inc(Y, ROW_H);
// Power
MakeLabel(FPanelRight, 'Power', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinPower := TFlatSpinEdit.Create(Self);
FSpinPower.Parent := FPanelRight;
FSpinPower.SetBounds(CTRL_X, Y, 80, CTRL_H);
FSpinPower.MinValue := 0;
FSpinPower.MaxValue := 100;
FSpinPower.Value := 100;
FSpinPower.OnChange := @OnFieldChange;
MakeLabel(FPanelRight, '%', CTRL_X + 84, Y + 1, 20, CTRL_H);
end;
function TChannelsForm.MakeLabel(AParent: TWinControl; const Cap: string;
X, Y, W, H: Integer): TLabel;
var L: TLabel;
begin
L := TLabel.Create(Self);
L.Parent := AParent;
L.Caption := Cap;
L.SetBounds(X, Y, W, H);
L.Font.Name := 'Courier New';
L.Font.Size := 8;
L.Font.Color := DarkTheme.TextDim;
// Сохраняем для последующей перекраски темой
SetLength(FLbls, Length(FLbls) + 1);
FLbls[High(FLbls)] := L;
Result := L;
end;
procedure TChannelsForm.StyleBtn(B: TFlatButton; AActive: Boolean);
var T: TAppTheme;
begin
T := DarkTheme;
B.Active := AActive;
B.ClrNorm := T.BtnNorm;
B.ClrActive := T.BtnActive;
B.ClrHot := T.BtnHot;
B.ClrBorder := IfThen(AActive, T.BtnBorderActive, T.BtnBorderNorm);
B.ClrText := T.BtnText;
B.ClrTextAct := T.BtnTextActive;
end;
procedure TChannelsForm.ApplyTheme(const T: TAppTheme);
var
i: Integer;
Ch: TChannel;
begin
Color := T.Panel;
FPanelLeft.Color := T.Panel;
FPanelRight.Color := T.Panel;
FList.SetAppTheme(T);
StyleBtn(FBtnAdd);
StyleBtn(FBtnDelete);
StyleBtn(FBtnUp);
StyleBtn(FBtnDown);
// RPT buttons — пересчитываем Active из текущего состояния
if SelectedIdx >= 0 then
begin
Ch := FStore.GetChannel(SelectedIdx);
StyleBtn(FBtnRptNone, Ch.RptDir = 0);
StyleBtn(FBtnRptMinus, Ch.RptDir = 1);
StyleBtn(FBtnRptPlus, Ch.RptDir = 2);
end
else
begin
StyleBtn(FBtnRptNone, True);
StyleBtn(FBtnRptMinus, False);
StyleBtn(FBtnRptPlus, False);
end;
FEdGroup.SetAppTheme(T);
FEdName.SetAppTheme(T);
FSpinRXFreq.SetAppTheme(T);
FSpinTXFreq.SetAppTheme(T);
FCmbMode.SetAppTheme(T);
FSpinRptOffset.SetAppTheme(T);
FChkCTCSS.SetAppTheme(T);
FCmbCTCSS.SetAppTheme(T);
FSpinPower.SetAppTheme(T);
for i := 0 to High(FLbls) do
begin
FLbls[i].Font.Color := T.TextDim;
FLbls[i].Color := T.Panel;
FLbls[i].ParentColor := False;
end;
end;
// ---------------------------------------------------------------------------
// Список каналов
// ---------------------------------------------------------------------------
function TChannelsForm.SelectedIdx: Integer;
begin
Result := FList.ItemIndex;
end;
procedure TChannelsForm.RefreshList(KeepIdx: Integer);
var
i, NewIdx: Integer;
Ch: TChannel;
begin
FList.Items.Clear;
for i := 0 to FStore.Count - 1 do
begin
Ch := FStore.GetChannel(i);
if Ch.Group <> '' then
FList.Items.Add(Ch.Group + ': ' + Ch.Name)
else
FList.Items.Add(Ch.Name);
end;
if FStore.Count = 0 then
begin
LoadChannelToFields(-1);
Exit;
end;
NewIdx := EnsureRange(KeepIdx, 0, FStore.Count - 1);
FList.ItemIndex := NewIdx;
LoadChannelToFields(NewIdx);
end;
procedure TChannelsForm.LoadChannelToFields(Idx: Integer);
var Ch: TChannel;
HasSel: Boolean;
begin
FUpdating := True;
try
HasSel := (Idx >= 0) and (Idx < FStore.Count);
FEdGroup.Enabled := HasSel;
FEdName.Enabled := HasSel;
FSpinRXFreq.Enabled := HasSel;
FSpinTXFreq.Enabled := HasSel;
FCmbMode.Enabled := HasSel;
FBtnRptNone.Enabled := HasSel;
FBtnRptMinus.Enabled := HasSel;
FBtnRptPlus.Enabled := HasSel;
FSpinRptOffset.Enabled := HasSel;
FChkCTCSS.Enabled := HasSel;
FCmbCTCSS.Enabled := HasSel;
FSpinPower.Enabled := HasSel;
if not HasSel then Exit;
Ch := FStore.GetChannel(Idx);
FEdGroup.Text := Ch.Group;
FEdName.Text := Ch.Name;
FSpinRXFreq.Value := Ch.RXFreq / 1000000.0;
FSpinTXFreq.Value := Ch.TXFreq / 1000000.0;
FCmbMode.ItemIndex := EnsureRange(Ch.Mode, MODE_MIN, MODE_MAX);
StyleBtn(FBtnRptNone, Ch.RptDir = 0);
StyleBtn(FBtnRptMinus, Ch.RptDir = 1);
StyleBtn(FBtnRptPlus, Ch.RptDir = 2);
FSpinRptOffset.Value := Ch.RptOffsetHz / 1000000.0;
FChkCTCSS.Checked := Ch.CTCSSOn;
FCmbCTCSS.ItemIndex := EnsureRange(Ch.CTCSSToneIdx, 0, CH_CTCSS_COUNT - 1);
FSpinPower.Value := Ch.Power;
finally
FUpdating := False;
end;
end;
procedure TChannelsForm.SaveFieldsToChannel;
var
Idx: Integer;
Ch: TChannel;
i: Integer;
begin
if FUpdating then Exit;
Idx := SelectedIdx;
if (Idx < 0) or (Idx >= FStore.Count) then Exit;
Ch := FStore.GetChannel(Idx);
Ch.Group := FEdGroup.Text;
Ch.Name := FEdName.Text;
Ch.RXFreq := FSpinRXFreq.Value * 1000000.0;
Ch.TXFreq := FSpinTXFreq.Value * 1000000.0;
Ch.Mode := EnsureRange(FCmbMode.ItemIndex, MODE_MIN, MODE_MAX);
// RptDir берётся из кнопок (Active)
if FBtnRptMinus.Active then Ch.RptDir := 1
else if FBtnRptPlus.Active then Ch.RptDir := 2
else Ch.RptDir := 0;
Ch.RptOffsetHz := FSpinRptOffset.Value * 1000000.0;
Ch.CTCSSOn := FChkCTCSS.Checked;
Ch.CTCSSToneIdx := EnsureRange(FCmbCTCSS.ItemIndex, 0, CH_CTCSS_COUNT - 1);
Ch.Power := FSpinPower.Value;
FStore.UpdateChannel(Idx, Ch);
FStore.Save;
// Обновляем имя в списке
FUpdating := True;
try
if Ch.Group <> '' then
FList.Items[Idx] := Ch.Group + ': ' + Ch.Name
else
FList.Items[Idx] := Ch.Name;
finally
FUpdating := False;
end;
if Assigned(FOnChanged) then FOnChanged(Self);
end;
// ---------------------------------------------------------------------------
// Обработчики кнопок тулбара
// ---------------------------------------------------------------------------
procedure TChannelsForm.OnListClick(Sender: TObject);
begin
if FUpdating then Exit;
LoadChannelToFields(FList.ItemIndex);
end;
procedure TChannelsForm.OnListDblClick(Sender: TObject);
begin
LoadChannelToFields(FList.ItemIndex);
end;
procedure TChannelsForm.OnAddClick(Sender: TObject);
var
Ch: TChannel;
NewIdx: Integer;
begin
Ch := FCurrentDefaults; // берём значения текущего состояния радио
FStore.AddChannel(Ch);
FStore.Save;
NewIdx := FStore.Count - 1;
RefreshList(NewIdx);
if Assigned(FOnChanged) then FOnChanged(Self);
// Фокус на имя для быстрого переименования
if FEdName.Enabled then FEdName.SetFocus;
FEdName.SelectAll;
end;
procedure TChannelsForm.OnDeleteClick(Sender: TObject);
var Idx: Integer;
begin
Idx := SelectedIdx;
if (Idx < 0) or (Idx >= FStore.Count) then Exit;
FStore.DeleteChannel(Idx);
FStore.Save;
RefreshList(EnsureRange(Idx, 0, FStore.Count - 1));
if Assigned(FOnChanged) then FOnChanged(Self);
end;
procedure TChannelsForm.OnUpClick(Sender: TObject);
var Idx: Integer;
begin
Idx := SelectedIdx;
if Idx < 1 then Exit;
FStore.MoveUp(Idx);
FStore.Save;
RefreshList(Idx - 1);
if Assigned(FOnChanged) then FOnChanged(Self);
end;
procedure TChannelsForm.OnDownClick(Sender: TObject);
var Idx: Integer;
begin
Idx := SelectedIdx;
if (Idx < 0) or (Idx >= FStore.Count - 1) then Exit;
FStore.MoveDown(Idx);
FStore.Save;
RefreshList(Idx + 1);
if Assigned(FOnChanged) then FOnChanged(Self);
end;
// ---------------------------------------------------------------------------
// Обработчики полей редактирования
// ---------------------------------------------------------------------------
procedure TChannelsForm.OnFieldChange(Sender: TObject);
begin
SaveFieldsToChannel;
end;
procedure TChannelsForm.OnRptBtnClick(Sender: TObject);
var Dir: Integer;
begin
Dir := (Sender as TFlatButton).Tag;
StyleBtn(FBtnRptNone, Dir = 0);
StyleBtn(FBtnRptMinus, Dir = 1);
StyleBtn(FBtnRptPlus, Dir = 2);
SaveFieldsToChannel;
end;
procedure TChannelsForm.OnCTCSSChkChange(Sender: TObject);
begin
SaveFieldsToChannel;
end;
procedure TChannelsForm.OnCTCSSCmbChange(Sender: TObject);
begin
SaveFieldsToChannel;
end;
procedure TChannelsForm.OnFormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
CloseAction := caHide; // скрываем, не уничтожаем — указатель в MainForm остаётся валидным
end;
procedure TChannelsForm.SetCurrentDefaults(const Ch: TChannel);
begin
FCurrentDefaults := Ch;
end;
end.