mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
Add channel memory: CH button, dropdown, editor form, smart CTCSS/RPT auto-clear
- New ChannelStore.pas: TChannel record + JSON persistence (channels.json) - New ChannelsForm.pas: full channel editor (list + fields, pre-fills from current radio state) - CH button in RX block: left-click opens dropdown, right-click opens editor - Active channel: button highlights with channel name, deactivates on VFO move - ApplyChannel: auto-switches XVTR/HF band based on channel frequency; saves old band state before switching to prevent cache corruption - SaveCurrentBand: skip save when VFO > 61 MHz (prevents HF cache corruption from VHF channel application without XVTR) - DeactivateXvtr: save full XVTR state (was only saving LastFreq) - CTCSS/RPT from channel marked as auto: cleared automatically when tuning away from channel frequency; survives band switches via FXvtrSettings auto-flags - TXvtrEntry: add LastCTCSSAutoActive, LastFMRptAutoActive fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,620 @@
|
||||
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;
|
||||
|
||||
const
|
||||
// Копии из MainForm: нужны для выпадающих списков
|
||||
CH_MODE_COUNT = 8;
|
||||
CH_MODE_NAMES: array[0..CH_MODE_COUNT-1] of string =
|
||||
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
||||
|
||||
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, 0, CH_MODE_COUNT - 1);
|
||||
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, 0, CH_MODE_COUNT - 1);
|
||||
// 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.
|
||||
Reference in New Issue
Block a user