Files
ewsdr/ChannelsForm.pas

776 lines
24 KiB
ObjectPascal
Raw Permalink 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, DpiUtils;
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;
FBtnClose: TFlatButton;
FList: TFlatListBox;
// --- Правая панель (поля редактирования) ---
FPanelRight: TPanel;
FFooterPanel: TPanel;
FCardIdentity: TPanel;
FCardRadio: TPanel;
FCardRepeater: TPanel;
FLeftTitle: TLabel;
FLeftHint: TLabel;
FRightTitle: TLabel;
FRightHint: TLabel;
// 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; // значения по умолчанию для новых каналов
FTheme: TAppTheme;
procedure BuildUI;
procedure StyleBtn(B: TFlatButton; AActive: Boolean = False);
function MakeLabel(AParent: TWinControl; const Cap: string;
X, Y, W, H: Integer): TLabel;
function MakeGroupPanel(const Cap: string; Y, H: Integer): TPanel;
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 OnCloseClick(Sender: TObject);
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 = 240;
TOOLBAR_Y = 66;
TOOLBAR_H = 30;
BTN_H = 26;
FORM_W = 820;
FORM_H = 540;
PAGE_MARGIN = 18;
// Правая панель
LBL_X = 14;
LBL_W = 106;
CTRL_X = 126;
ROW_H = 28;
CTRL_H = 24;
constructor TChannelsForm.Create(AOwner: TComponent; AStore: TChannelStore;
AOnChanged: TNotifyEvent);
var
WorkArea: TRect;
MaxW, MaxH: Integer;
begin
inherited CreateNew(AOwner);
FStore := AStore;
FOnChanged := AOnChanged;
FUpdating := False;
FTheme := DarkTheme;
TChannelStore.DefaultChannel(FCurrentDefaults);
Scaled := False;
Caption := 'Channels';
BorderStyle := bsSizeable;
if AOwner is TCustomForm then
begin
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(FORM_W), MaxW);
Height := Min(DpiScale(FORM_H), MaxH);
Constraints.MinWidth := Min(DpiScale(680), Width);
Constraints.MinHeight := Min(DpiScale(530), Height);
KeyPreview := True;
OnClose := @OnFormClose;
BuildUI;
ApplyTheme(DarkTheme);
RefreshList(0);
end;
procedure TChannelsForm.BuildUI;
var
i, BtnW, CardW, Y: Integer;
L: TLabel;
procedure PlaceRightUnit(AParent: TWinControl; const Cap: string;
ATop: Integer);
begin
L := MakeLabel(AParent, Cap, 0, ATop + 1, 42, CTRL_H);
L.SetBounds(AParent.ClientWidth - DpiScale(50), DpiScale(ATop + 1),
DpiScale(42), DpiScale(CTRL_H));
L.Anchors := [akTop, akRight];
end;
begin
SetLength(FLbls, 0);
// ----- Левая панель -----
FPanelLeft := TPanel.Create(Self);
FPanelLeft.Parent := Self;
FPanelLeft.Align := alLeft;
FPanelLeft.Width := DpiScale(LPANEL_W);
FPanelLeft.BevelOuter := bvNone;
FLeftTitle := MakeLabel(FPanelLeft, 'CHANNEL MEMORY',
14, 12, LPANEL_W - 28, 24);
FLeftTitle.Font.Size := 9;
FLeftTitle.Font.Style := [fsBold];
FLeftTitle.Font.Color := FTheme.Text;
FLeftHint := MakeLabel(FPanelLeft, 'Saved frequencies and modes',
14, 37, LPANEL_W - 28, 18);
FLeftHint.Font.Size := 8;
BtnW := (DpiScale(LPANEL_W - 24) - 3 * DpiScale(6)) div 4;
FBtnAdd := TFlatButton.Create(Self);
FBtnAdd.Parent := FPanelLeft;
FBtnAdd.SetBounds(DpiScale(12), DpiScale(TOOLBAR_Y),
BtnW, DpiScale(BTN_H));
FBtnAdd.Caption := 'ADD';
FBtnAdd.OnClick := @OnAddClick;
FBtnDelete := TFlatButton.Create(Self);
FBtnDelete.Parent := FPanelLeft;
FBtnDelete.SetBounds(FBtnAdd.Left + BtnW + DpiScale(6),
FBtnAdd.Top, BtnW, FBtnAdd.Height);
FBtnDelete.Caption := 'DEL';
FBtnDelete.OnClick := @OnDeleteClick;
FBtnUp := TFlatButton.Create(Self);
FBtnUp.Parent := FPanelLeft;
FBtnUp.SetBounds(FBtnDelete.Left + BtnW + DpiScale(6),
FBtnAdd.Top, BtnW, FBtnAdd.Height);
FBtnUp.Caption := '↑';
FBtnUp.OnClick := @OnUpClick;
FBtnDown := TFlatButton.Create(Self);
FBtnDown.Parent := FPanelLeft;
FBtnDown.SetBounds(FBtnUp.Left + BtnW + DpiScale(6),
FBtnAdd.Top, BtnW, FBtnAdd.Height);
FBtnDown.Caption := '↓';
FBtnDown.OnClick := @OnDownClick;
FList := TFlatListBox.Create(Self);
FList.Parent := FPanelLeft;
FList.SetBounds(DpiScale(12), DpiScale(TOOLBAR_Y + TOOLBAR_H + 10),
FPanelLeft.ClientWidth - DpiScale(24),
FPanelLeft.ClientHeight - DpiScale(TOOLBAR_Y + TOOLBAR_H + 22));
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;
FFooterPanel := TPanel.Create(Self);
FFooterPanel.Parent := FPanelRight;
FFooterPanel.Align := alBottom;
FFooterPanel.Height := DpiScale(50);
FFooterPanel.BevelOuter := bvNone;
FBtnClose := TFlatButton.Create(Self);
FBtnClose.Parent := FFooterPanel;
FBtnClose.Caption := 'Close';
FBtnClose.SetBounds(FFooterPanel.ClientWidth - DpiScale(110),
DpiScale(10), DpiScale(92), DpiScale(30));
FBtnClose.Anchors := [akTop, akRight];
FBtnClose.OnClick := @OnCloseClick;
FRightTitle := MakeLabel(FPanelRight, 'CHANNEL DETAILS',
PAGE_MARGIN, 10, 420, 22);
FRightTitle.Font.Size := 9;
FRightTitle.Font.Style := [fsBold];
FRightTitle.Font.Color := FTheme.Text;
FRightTitle.Anchors := [akLeft, akTop, akRight];
FRightHint := MakeLabel(FPanelRight,
'Changes are saved automatically', PAGE_MARGIN, 31, 420, 18);
FRightHint.Font.Size := 8;
FRightHint.Anchors := [akLeft, akTop, akRight];
FCardIdentity := MakeGroupPanel('IDENTITY', 56, 90);
CardW := FCardIdentity.ClientWidth;
Y := 34;
MakeLabel(FCardIdentity, 'Group', LBL_X, Y + 1, LBL_W, CTRL_H);
FEdGroup := TFlatEdit.Create(Self);
FEdGroup.Parent := FCardIdentity;
FEdGroup.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
CardW - DpiScale(CTRL_X + 14), DpiScale(CTRL_H));
FEdGroup.Anchors := [akLeft, akTop, akRight];
FEdGroup.OnChange := @OnFieldChange;
Inc(Y, ROW_H);
MakeLabel(FCardIdentity, 'Name', LBL_X, Y + 1, LBL_W, CTRL_H);
FEdName := TFlatEdit.Create(Self);
FEdName.Parent := FCardIdentity;
FEdName.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
CardW - DpiScale(CTRL_X + 14), DpiScale(CTRL_H));
FEdName.Anchors := [akLeft, akTop, akRight];
FEdName.OnChange := @OnFieldChange;
FCardRadio := MakeGroupPanel('RADIO', 156, 120);
CardW := FCardRadio.ClientWidth;
Y := 34;
MakeLabel(FCardRadio, 'RX frequency', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinRXFreq := TFlatFloatSpinEdit.Create(Self);
FSpinRXFreq.Parent := FCardRadio;
FSpinRXFreq.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
CardW - DpiScale(CTRL_X + 56), DpiScale(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;
PlaceRightUnit(FCardRadio, 'MHz', Y);
Inc(Y, ROW_H);
MakeLabel(FCardRadio, 'TX frequency', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinTXFreq := TFlatFloatSpinEdit.Create(Self);
FSpinTXFreq.Parent := FCardRadio;
FSpinTXFreq.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
CardW - DpiScale(CTRL_X + 56), DpiScale(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;
PlaceRightUnit(FCardRadio, 'MHz', Y);
Inc(Y, ROW_H);
MakeLabel(FCardRadio, 'Mode', LBL_X, Y + 1, LBL_W, CTRL_H);
FCmbMode := TFlatComboBox.Create(Self);
FCmbMode.Parent := FCardRadio;
FCmbMode.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
DpiScale(180), DpiScale(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;
FCardRepeater := MakeGroupPanel('REPEATER / TRANSMIT', 286, 148);
CardW := FCardRepeater.ClientWidth;
Y := 34;
MakeLabel(FCardRepeater, 'Direction', LBL_X, Y + 1, LBL_W, CTRL_H);
FBtnRptNone := TFlatButton.Create(Self);
FBtnRptNone.Parent := FCardRepeater;
FBtnRptNone.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
DpiScale(72), DpiScale(CTRL_H));
FBtnRptNone.Caption := 'NONE';
FBtnRptNone.Tag := 0;
FBtnRptNone.OnClick := @OnRptBtnClick;
FBtnRptMinus := TFlatButton.Create(Self);
FBtnRptMinus.Parent := FCardRepeater;
FBtnRptMinus.SetBounds(DpiScale(CTRL_X + 78), DpiScale(Y),
DpiScale(46), DpiScale(CTRL_H));
FBtnRptMinus.Caption := '';
FBtnRptMinus.Tag := 1;
FBtnRptMinus.OnClick := @OnRptBtnClick;
FBtnRptPlus := TFlatButton.Create(Self);
FBtnRptPlus.Parent := FCardRepeater;
FBtnRptPlus.SetBounds(DpiScale(CTRL_X + 130), DpiScale(Y),
DpiScale(46), DpiScale(CTRL_H));
FBtnRptPlus.Caption := '+';
FBtnRptPlus.Tag := 2;
FBtnRptPlus.OnClick := @OnRptBtnClick;
Inc(Y, ROW_H);
MakeLabel(FCardRepeater, 'Offset', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinRptOffset := TFlatFloatSpinEdit.Create(Self);
FSpinRptOffset.Parent := FCardRepeater;
FSpinRptOffset.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
CardW - DpiScale(CTRL_X + 56), DpiScale(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;
PlaceRightUnit(FCardRepeater, 'MHz', Y);
Inc(Y, ROW_H);
MakeLabel(FCardRepeater, 'CTCSS tone', LBL_X, Y + 1, LBL_W, CTRL_H);
FChkCTCSS := TFlatCheckBox.Create(Self);
FChkCTCSS.Parent := FCardRepeater;
FChkCTCSS.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
DpiScale(28), DpiScale(CTRL_H));
FChkCTCSS.Caption := '';
FChkCTCSS.Checked := False;
FChkCTCSS.OnChange := @OnCTCSSChkChange;
FCmbCTCSS := TFlatComboBox.Create(Self);
FCmbCTCSS.Parent := FCardRepeater;
FCmbCTCSS.SetBounds(DpiScale(CTRL_X + 34), DpiScale(Y),
CardW - DpiScale(CTRL_X + 48), DpiScale(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);
MakeLabel(FCardRepeater, 'Power', LBL_X, Y + 1, LBL_W, CTRL_H);
FSpinPower := TFlatSpinEdit.Create(Self);
FSpinPower.Parent := FCardRepeater;
FSpinPower.SetBounds(DpiScale(CTRL_X), DpiScale(Y),
DpiScale(92), DpiScale(CTRL_H));
FSpinPower.MinValue := 0;
FSpinPower.MaxValue := 100;
FSpinPower.Value := 100;
FSpinPower.OnChange := @OnFieldChange;
MakeLabel(FCardRepeater, '%', CTRL_X + 98, Y + 1, 24, 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.AutoSize := False;
L.SetBounds(DpiScale(X), DpiScale(Y), DpiScale(W), DpiScale(H));
L.Layout := tlCenter;
L.Font.Size := 8;
L.Font.Color := FTheme.TextDim;
// Сохраняем для последующей перекраски темой
SetLength(FLbls, Length(FLbls) + 1);
FLbls[High(FLbls)] := L;
Result := L;
end;
function TChannelsForm.MakeGroupPanel(const Cap: string; Y, H: Integer): TPanel;
var
Title: TLabel;
Divider: TPanel;
begin
Result := TPanel.Create(Self);
Result.Parent := FPanelRight;
Result.SetBounds(DpiScale(PAGE_MARGIN), DpiScale(Y),
FPanelRight.ClientWidth - DpiScale(2 * PAGE_MARGIN), DpiScale(H));
Result.Anchors := [akLeft, akTop, akRight];
Result.BevelOuter := bvNone;
Result.Color := FTheme.Panel;
Title := MakeLabel(Result, Cap, 12, 2, 300, 24);
Title.Font.Size := 8;
Title.Font.Style := [fsBold];
Title.Font.Color := FTheme.FreqActive;
Divider := TPanel.Create(Self);
Divider.Parent := Result;
Divider.SetBounds(0, DpiScale(28), Result.ClientWidth, 1);
Divider.Anchors := [akLeft, akTop, akRight];
Divider.BevelOuter := bvNone;
Divider.Color := FTheme.Border;
end;
procedure TChannelsForm.StyleBtn(B: TFlatButton; AActive: Boolean);
begin
// Прямо созданные TFlatButton не проходят через MakeFlatBtn, поэтому
// явно даём им тот же системный шрифт, что и остальному интерфейсу.
B.Font.Assign(Font);
B.Font.Size := 8;
B.Font.Style := [];
B.Active := AActive;
B.ClrNorm := FTheme.BtnNorm;
B.ClrActive := FTheme.BtnActive;
B.ClrHot := FTheme.BtnHot;
B.ClrBorder := IfThen(AActive,
FTheme.BtnBorderActive, FTheme.BtnBorderNorm);
B.ClrText := FTheme.BtnText;
B.ClrTextAct := FTheme.BtnTextActive;
B.Invalidate;
end;
procedure TChannelsForm.ApplyTheme(const T: TAppTheme);
var
i: Integer;
Ch: TChannel;
procedure ThemeCard(P: TPanel);
var
n: Integer;
begin
if P = nil then Exit;
P.Color := T.Panel;
for n := 0 to P.ControlCount - 1 do
if (P.Controls[n] is TPanel) and (P.Controls[n].Height = 1) then
TPanel(P.Controls[n]).Color := T.Border;
end;
begin
FTheme := T;
Color := T.BG;
FPanelLeft.Color := T.Panel;
FPanelRight.Color := T.BG;
FFooterPanel.Color := T.BG;
ThemeCard(FCardIdentity);
ThemeCard(FCardRadio);
ThemeCard(FCardRepeater);
FList.SetAppTheme(T);
StyleBtn(FBtnAdd);
StyleBtn(FBtnDelete);
StyleBtn(FBtnUp);
StyleBtn(FBtnDown);
StyleBtn(FBtnClose);
// 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
if FLbls[i] = FLeftTitle then
FLbls[i].Font.Color := T.Text
else if FLbls[i] = FRightTitle then
FLbls[i].Font.Color := T.Text
else if fsBold in FLbls[i].Font.Style then
FLbls[i].Font.Color := T.FreqActive
else
FLbls[i].Font.Color := T.TextDim;
FLbls[i].Transparent := True;
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;
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.OnCloseClick(Sender: TObject);
begin
Close;
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.