{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
}

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.
