unit uKeyEvent;

interface

uses
  System.SysUtils, System.Types, Windows, System.Classes, System.UITypes,
  FMX.Types, FMX.Edit, FMX.Controls, FMX.Forms, FMX.Platform, FMX.ListBox;

type
  TKE = class(TObject)
  private
    fEdit: TEdit;
    Fcombo1: TComboBox;
    Fcombo2: TComboBox;
    Fcombo3: TComboBox;

    procedure UpdateCombination;
    function GetKeyPriority(const Key: string): Integer;
    procedure ComboBoxChange(Sender: TObject);
    function FindLastDuplicate(List: TStringList; StartIndex: Integer): Integer;
    function StrToVirtualKey(const KeyStr: string): Word;
    procedure InitializeComboBox(cb: TComboBox);
    function CompareKeys(List: TStringList; Index1, Index2: Integer): Integer;
  public
    constructor Create( edt: TEdit;  c1, c2, c3: TComboBox);
    procedure SimulateKeyPress(const KeyCombo: string; Delay: Integer);
  end;

const
  KeyOrder: array[0..82] of string = (
    // Модификаторы
    'Control', 'Ctrl', 'Alt', 'Shift', 'Win',

    // Функциональные клавиши
    'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',

    // Специальные клавиши
    'Esc', 'Tab', 'CapsLock', 'Enter', 'Space', 'Backspace', 'Delete', 'Insert',
    'Home', 'End', 'PageUp', 'PageDown', 'PrintScreen', 'ScrollLock', 'Pause',

    // Навигация
    'Up', 'Down', 'Left', 'Right',

    // Цифры
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',

    // Буквы
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',

    // Символы
    '`', '-', '=', '[', ']', '\', ';', '''', ',', '.', '/'
  );

implementation

  var
  SortingInstance: TKE;

  function GlobalCompare(List: TStringList; Index1, Index2: Integer): Integer;
begin
  if Assigned(SortingInstance) then
    Result := SortingInstance.CompareKeys(List, Index1, Index2)
  else
    Result := 0;
end;

constructor TKE.Create( edt: TEdit;  c1, c2, c3: TComboBox);
begin
  inherited Create;

  // Проверка на валидность компонентов
  if not Assigned(edt) then
    raise Exception.Create('TEdit is nil');
  if not Assigned(c1) or not Assigned(c2) or not Assigned(c3) then
    raise Exception.Create('ComboBox is nil');


  fEdit := edt;
  Fcombo1 := c1;
  Fcombo2 := c2;
  Fcombo3 := c3;

  InitializeComboBox(Fcombo1);
  InitializeComboBox(Fcombo2);
  InitializeComboBox(Fcombo3);

  Fcombo1.OnChange := ComboBoxChange;
  Fcombo2.OnChange := ComboBoxChange;
  Fcombo3.OnChange := ComboBoxChange;
end;

procedure TKE.InitializeComboBox(cb: TComboBox);
var
  i: Integer;
begin
  cb.Items.BeginUpdate;
  try
    cb.Items.Clear;
    cb.Items.Add(''); // Пустой элемент
    for i := 0 to High(KeyOrder) do
      cb.Items.Add(KeyOrder[i]);
  finally
    cb.Items.EndUpdate;
  end;
  cb.ItemIndex := 0;
end;

procedure TKE.ComboBoxChange(Sender: TObject);
begin
  UpdateCombination;
end;

function TKE.GetKeyPriority(const Key: string): Integer;
var
  I: Integer;
begin
  for I := 0 to High(KeyOrder) do
    if SameText(KeyOrder[I], Key) then
    begin
      Result := I;
      Exit;
    end;
  Result := High(KeyOrder) + 1; // Для неизвестных клавиш
end;

function TKE.FindLastDuplicate(List: TStringList; StartIndex: Integer): Integer;
var
  I: Integer;
begin
  Result := -1;
  for I := List.Count-1 downto StartIndex+1 do
    if SameText(List[StartIndex], List[I]) then
    begin
      Result := I;
      Break;
    end;
end;

function TKE.CompareKeys(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := GetKeyPriority(List[Index1]) - GetKeyPriority(List[Index2]);
end;

procedure TKE.UpdateCombination;
var
  List: TStringList;
  I: Integer;
  TempStr: string;
begin
  List := TStringList.Create;
  try
    // Собираем выбранные клавиши
    if Fcombo1.Text <> '' then List.Add(Fcombo1.Text);
    if Fcombo2.Text <> '' then List.Add(Fcombo2.Text);
    if Fcombo3.Text <> '' then List.Add(Fcombo3.Text);

    // Удаление дубликатов
    I := 0;
    while I < List.Count do
    begin
      if FindLastDuplicate(List, I) > I then
        List.Delete(I)
      else
        Inc(I);
    end;

    // Сортировка с использованием глобальной переменной
    SortingInstance := Self;
    try
      List.CustomSort(GlobalCompare);
    finally
      SortingInstance := nil;
    end;

    // Формирование строки
    TempStr := '';
    for I := 0 to List.Count-1 do
    begin
      if TempStr <> '' then TempStr := TempStr + '+';
      TempStr := TempStr + List[I];
    end;

    fEdit.Text := TempStr;
  finally
    List.Free;
  end;
end;

function TKE.StrToVirtualKey(const KeyStr: string): Word;
begin
  // Модификаторы
  if SameText(KeyStr, 'Control') or SameText(KeyStr, 'Ctrl') then Exit(VK_CONTROL);
  if SameText(KeyStr, 'Alt') then Exit(VK_MENU);
  if SameText(KeyStr, 'Shift') then Exit(VK_SHIFT);
  if SameText(KeyStr, 'Win') then Exit(VK_LWIN);

  // Функциональные клавиши
  if SameText(KeyStr, 'F1') then Exit(VK_F1);
  if SameText(KeyStr, 'F2') then Exit(VK_F2);
  if SameText(KeyStr, 'F3') then Exit(VK_F3);
  if SameText(KeyStr, 'F4') then Exit(VK_F4);
  if SameText(KeyStr, 'F5') then Exit(VK_F5);
  if SameText(KeyStr, 'F6') then Exit(VK_F6);
  if SameText(KeyStr, 'F7') then Exit(VK_F7);
  if SameText(KeyStr, 'F8') then Exit(VK_F8);
  if SameText(KeyStr, 'F9') then Exit(VK_F9);
  if SameText(KeyStr, 'F10') then Exit(VK_F10);
  if SameText(KeyStr, 'F11') then Exit(VK_F11);
  if SameText(KeyStr, 'F12') then Exit(VK_F12);

  // Специальные клавиши
  if SameText(KeyStr, 'Esc') then Exit(VK_ESCAPE);
  if SameText(KeyStr, 'Tab') then Exit(VK_TAB);
  if SameText(KeyStr, 'CapsLock') then Exit(VK_CAPITAL);
  if SameText(KeyStr, 'Enter') then Exit(VK_RETURN);
  if SameText(KeyStr, 'Space') then Exit(VK_SPACE);
  if SameText(KeyStr, 'Backspace') then Exit(VK_BACK);
  if SameText(KeyStr, 'Delete') then Exit(VK_DELETE);
  if SameText(KeyStr, 'Insert') then Exit(VK_INSERT);
  if SameText(KeyStr, 'Home') then Exit(VK_HOME);
  if SameText(KeyStr, 'End') then Exit(VK_END);
  if SameText(KeyStr, 'PageUp') then Exit(VK_PRIOR);
  if SameText(KeyStr, 'PageDown') then Exit(VK_NEXT);
  if SameText(KeyStr, 'PrintScreen') then Exit(VK_SNAPSHOT);
  if SameText(KeyStr, 'ScrollLock') then Exit(VK_SCROLL);
  if SameText(KeyStr, 'Pause') then Exit(VK_PAUSE);

  // Навигация
  if SameText(KeyStr, 'Up') then Exit(VK_UP);
  if SameText(KeyStr, 'Down') then Exit(VK_DOWN);
  if SameText(KeyStr, 'Left') then Exit(VK_LEFT);
  if SameText(KeyStr, 'Right') then Exit(VK_RIGHT);

  // Буквы и цифры
  if Length(KeyStr) = 1 then
  begin
    case KeyStr[1] of
      'A'..'Z': Exit(Ord(UpCase(KeyStr[1])));
      '0'..'9': Exit(Ord(KeyStr[1]));
      '`': Exit(VK_OEM_3);
      '-': Exit(VK_OEM_MINUS);
      '=': Exit(VK_OEM_PLUS);
      '[': Exit(VK_OEM_4);
      ']': Exit(VK_OEM_6);
      '\': Exit(VK_OEM_5);
      ';': Exit(VK_OEM_1);
      '''': Exit(VK_OEM_7);
      ',': Exit(VK_OEM_COMMA);
      '.': Exit(VK_OEM_PERIOD);
      '/': Exit(VK_OEM_2);
    end;
  end;

  Result := 0; // Если клавиша не распознана
end;

procedure TKE.SimulateKeyPress(const KeyCombo: string; Delay: Integer);
var
  Parts: TArray<string>;
  Inputs: array of TInput;
  i, Index: Integer;
  Key: Word;
  KeyState: TKeyboardState;
begin
  Parts := KeyCombo.Split(['+'], TStringSplitOptions.ExcludeEmpty);
  SetLength(Inputs, Length(Parts) * 2); // Каждая клавиша: нажатие и отпускание
  Index := 0;

  // Сначала собираем все события нажатия
  for i := 0 to High(Parts) do
  begin
    Key := StrToVirtualKey(Parts[i].Trim);
    if Key = 0 then Continue;

    // Нажатие клавиши
    Inputs[Index].Itype := INPUT_KEYBOARD;
    Inputs[Index].ki.wVk := Key;
    Inputs[Index].ki.dwFlags := 0;
    Inputs[Index].ki.time := 0;
    Inputs[Index].ki.wScan := 0;
    Inputs[Index].ki.dwExtraInfo := 0;
    Inc(Index);
  end;

  // Отправляем нажатия
  if Index > 0 then
    SendInput(Index, Inputs[0], SizeOf(TInput));

  // Задержка
  Sleep(Delay);

  // Теперь собираем события отпускания в обратном порядке
  for i := High(Parts) downto 0 do
  begin
    Key := StrToVirtualKey(Parts[i].Trim);
    if Key = 0 then Continue;

    // Отпускание клавиши
    Inputs[Index].Itype := INPUT_KEYBOARD;
    Inputs[Index].ki.wVk := Key;
    Inputs[Index].ki.dwFlags := KEYEVENTF_KEYUP;
    Inputs[Index].ki.time := 0;
    Inputs[Index].ki.wScan := 0;
    Inputs[Index].ki.dwExtraInfo := 0;
    Inc(Index);
  end;

  // Отправляем отпускания
  if Index > Length(Parts) then
    SendInput(Index - Length(Parts), Inputs[Length(Parts)], SizeOf(TInput));

  // Принудительно обновляем состояние клавиатуры
  GetKeyboardState(KeyState);
  SetKeyboardState(KeyState);
end;

end.
