// SPDX-License-Identifier: LGPL-3.0-linking-exception
{******************************* CONTRIBUTOR(S) ******************************
- Edivando S. Santos Brasil | mailedivando@gmail.com
  (Compatibility with delphi VCL 11/2018)

***************************** END CONTRIBUTOR(S) *****************************}
unit BCKeyboard;

{$I bgracontrols.inc}

interface

uses
  Classes, SysUtils, {$IFDEF FPC}LCLType, LResources, LMessages,{$ENDIF}Forms, Controls, Graphics, Dialogs,
  {$IFNDEF FPC}Types, Windows, Messages, BGRAGraphics, GraphType, FPImage, BCBaseCtrls,{$ENDIF}
  BCThemeManager, BCButton, BCPanel, MouseAndKeyInput;

type

  { TBCKeyboard }

  TBCKeyboard = class(TComponent)
  private
    FBCThemeManager: TBCThemeManager;
    FButton: TBCButton;
    FOnUserChange: TNotifyEvent;
    FPanel, FRow1, FRow2, FRow3, FRow4: TBCPanel;
    FPanelsColor: TColor;
    F_q, F_w, F_e, F_r, F_t, F_y, F_u, F_i, F_o, F_p, F_a, F_s, F_d,
    F_f, F_g, F_h, F_j, F_k, F_l, F_z, F_x, F_c, F_v, F_b, F_n, F_m,
    F_shift, F_space, F_back: TBCButton;
    FVisible: boolean;
    procedure SetFButton(AValue: TBCButton);
    procedure SetFPanel(AValue: TBCPanel);
    procedure SetFPanelsColor(AValue: TColor);
    procedure SetFThemeManager(AValue: TBCThemeManager);
  protected
    procedure PressVirtKey(p: longint);
    procedure PressShiftVirtKey(p: longint);
    procedure OnButtonClick(Sender: TObject; {%H-}Button: TMouseButton;
      {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: integer); virtual;
    { When value is changed by the user }
    property OnUserChange: TNotifyEvent read FOnUserChange write FOnUserChange;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    // Show in a custom form or panel
    procedure Show(AControl: TWinControl); overload;
    // Try to Show in the form where this component is placed
    procedure Show(); overload;
    // Hide the component
    procedure Hide();
    // Update buttons style
    procedure UpdateButtonStyle;
  public
    { The real panel that's used as container for all the numeric buttons }
    property Panel: TBCPanel read FPanel write SetFPanel;
    { The color of all the panels involved in the control }
    property PanelsColor: TColor read FPanelsColor write SetFPanelsColor;
    { A fake button that's used as style base for all the numeric buttons }
    property ButtonStyle: TBCButton read FButton write SetFButton;
    { If it's visible or not }
    property Visible: boolean read FVisible;
  published
    property ThemeManager: TBCThemeManager read FBCThemeManager write SetFThemeManager;
  end;

{$IFDEF FPC}procedure Register;{$ENDIF}

implementation

{$IFDEF FPC}procedure Register;
begin
  RegisterComponents('BGRA Controls', [TBCKeyboard]);
end;
{$ENDIF}

{ TBCKeyboard }

procedure TBCKeyboard.SetFThemeManager(AValue: TBCThemeManager);
begin
  if FBCThemeManager = AValue then
    Exit;
  FBCThemeManager := AValue;
end;

procedure TBCKeyboard.PressVirtKey(p: longint);
begin
  KeyInput.Down(p);
  KeyInput.Up(p);
end;

procedure TBCKeyboard.PressShiftVirtKey(p: longint);
begin
  KeyInput.Down(VK_SHIFT);
  KeyInput.Down(p);
  KeyInput.Up(p);
  KeyInput.Up(VK_SHIFT);
end;

procedure TBCKeyboard.OnButtonClick(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: integer);
var
  btn: TBCButton;
  str: string;
begin
  btn := TBCButton(Sender);
  str := btn.Caption;

  if str = F_shift.Caption then
  begin
    F_shift.Down := not F_shift.Down;
    if not F_shift.Down then
    begin
      F_q.Caption := LowerCase(F_q.Caption);
      F_w.Caption := LowerCase(F_w.Caption);
      F_e.Caption := LowerCase(F_e.Caption);
      F_r.Caption := LowerCase(F_r.Caption);
      F_t.Caption := LowerCase(F_t.Caption);
      F_y.Caption := LowerCase(F_y.Caption);
      F_u.Caption := LowerCase(F_u.Caption);
      F_i.Caption := LowerCase(F_i.Caption);
      F_o.Caption := LowerCase(F_o.Caption);
      F_p.Caption := LowerCase(F_p.Caption);
      F_a.Caption := LowerCase(F_a.Caption);
      F_s.Caption := LowerCase(F_s.Caption);
      F_d.Caption := LowerCase(F_d.Caption);
      F_f.Caption := LowerCase(F_f.Caption);
      F_g.Caption := LowerCase(F_g.Caption);
      F_h.Caption := LowerCase(F_h.Caption);
      F_j.Caption := LowerCase(F_j.Caption);
      F_k.Caption := LowerCase(F_k.Caption);
      F_l.Caption := LowerCase(F_l.Caption);
      F_z.Caption := LowerCase(F_z.Caption);
      F_x.Caption := LowerCase(F_x.Caption);
      F_c.Caption := LowerCase(F_c.Caption);
      F_v.Caption := LowerCase(F_v.Caption);
      F_b.Caption := LowerCase(F_b.Caption);
      F_n.Caption := LowerCase(F_n.Caption);
      F_m.Caption := LowerCase(F_m.Caption);
    end
    else
    begin
      F_q.Caption := UpperCase(F_q.Caption);
      F_w.Caption := UpperCase(F_w.Caption);
      F_e.Caption := UpperCase(F_e.Caption);
      F_r.Caption := UpperCase(F_r.Caption);
      F_t.Caption := UpperCase(F_t.Caption);
      F_y.Caption := UpperCase(F_y.Caption);
      F_u.Caption := UpperCase(F_u.Caption);
      F_i.Caption := UpperCase(F_i.Caption);
      F_o.Caption := UpperCase(F_o.Caption);
      F_p.Caption := UpperCase(F_p.Caption);
      F_a.Caption := UpperCase(F_a.Caption);
      F_s.Caption := UpperCase(F_s.Caption);
      F_d.Caption := UpperCase(F_d.Caption);
      F_f.Caption := UpperCase(F_f.Caption);
      F_g.Caption := UpperCase(F_g.Caption);
      F_h.Caption := UpperCase(F_h.Caption);
      F_j.Caption := UpperCase(F_j.Caption);
      F_k.Caption := UpperCase(F_k.Caption);
      F_l.Caption := UpperCase(F_l.Caption);
      F_z.Caption := UpperCase(F_z.Caption);
      F_x.Caption := UpperCase(F_x.Caption);
      F_c.Caption := UpperCase(F_c.Caption);
      F_v.Caption := UpperCase(F_v.Caption);
      F_b.Caption := UpperCase(F_b.Caption);
      F_n.Caption := UpperCase(F_n.Caption);
      F_m.Caption := UpperCase(F_m.Caption);
    end;
  end
  else if str = F_back.Caption then
  begin
    {$IFDEF CPUX86_64}
    Application.ProcessMessages;
    KeyInput.Press(VK_BACK);
    Application.ProcessMessages;
    {$ELSE}
      {$IFDEF FPC}
      Application.QueueAsyncCall(@PressVirtKey, VK_BACK);
      {$ELSE}
      SendKey(VK_BACK);
      {$ENDIF}
    {$ENDIF}
  end
  else
  begin
    if str = F_space.Caption then
      str := ' ';
    {$IFDEF CPUX86_64}
    Application.ProcessMessages;
    if F_shift.Down then
      KeyInput.Down(VK_SHIFT);
    KeyInput.Press(Ord(UpperCase(str)[1]));
    if F_shift.Down then
      KeyInput.Up(VK_SHIFT);
    Application.ProcessMessages;
    {$ELSE}
    if F_shift.Down then
      {$IFDEF FPC}
      Application.QueueAsyncCall(@PressShiftVirtKey, Ord(UpperCase(str)[1]))
      {$ELSE}
      SendKey(Ord(UpperCase(str)[1]), Shift)
      {$ENDIF}
    else
      {$IFDEF FPC}
      Application.QueueAsyncCall(@PressVirtKey, Ord(UpperCase(str)[1]));
      {$ELSE}
      SendKey(Ord(UpperCase(str)[1]))
      {$ENDIF}
    {$ENDIF}
  end;

  if Assigned(FOnUserChange) then
    FOnUserChange(Self);
end;

constructor TBCKeyboard.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FVisible := False;

  FButton := TBCButton.Create(Self);

  FPanel := TBCPanel.Create(Self);
  FPanel.AutoSize := True;
  FPanel.ChildSizing.ControlsPerLine := 1;
  FPanel.ChildSizing.Layout := cclLeftToRightThenTopToBottom;
  FPanel.Caption := 'Panel1';
  FPanel.BorderBCStyle := bpsBorder;

  { qwertyuiop }

  FRow1 := TBCPanel.Create(FPanel);
  FRow1.AutoSize := True;
  FRow1.Caption := '';
  FRow1.BorderBCStyle := bpsBorder;
  FRow1.ChildSizing.ControlsPerLine := 10;
  FRow1.ChildSizing.Layout := cclLeftToRightThenTopToBottom;
  FRow1.Parent := FPanel;

  F_q := TBCButton.Create(FRow1);
  F_q.Caption := 'Q';
  F_q.Parent := FRow1;
  F_q.OnMouseDown := OnButtonClick;

  F_w := TBCButton.Create(FRow1);
  F_w.Caption := 'W';
  F_w.Parent := FRow1;
  F_w.OnMouseDown := OnButtonClick;

  F_e := TBCButton.Create(FRow1);
  F_e.Caption := 'E';
  F_e.Parent := FRow1;
  F_e.OnMouseDown := OnButtonClick;

  F_r := TBCButton.Create(FRow1);
  F_r.Caption := 'R';
  F_r.Parent := FRow1;
  F_r.OnMouseDown := OnButtonClick;

  F_t := TBCButton.Create(FRow1);
  F_t.Caption := 'T';
  F_t.Parent := FRow1;
  F_t.OnMouseDown := OnButtonClick;

  F_y := TBCButton.Create(FRow1);
  F_y.Caption := 'Y';
  F_y.Parent := FRow1;
  F_y.OnMouseDown := OnButtonClick;

  F_u := TBCButton.Create(FRow1);
  F_u.Caption := 'U';
  F_u.Parent := FRow1;
  F_u.OnMouseDown := OnButtonClick;

  F_i := TBCButton.Create(FRow1);
  F_i.Caption := 'I';
  F_i.Parent := FRow1;
  F_i.OnMouseDown := OnButtonClick;

  F_o := TBCButton.Create(FRow1);
  F_o.Caption := 'O';
  F_o.Parent := FRow1;
  F_o.OnMouseDown := OnButtonClick;

  F_p := TBCButton.Create(FRow1);
  F_p.Caption := 'P';
  F_p.Parent := FRow1;
  F_p.OnMouseDown := OnButtonClick;


  { asdfghjkl }

  FRow2 := TBCPanel.Create(FPanel);
  FRow2.AutoSize := True;
  FRow2.Caption := '';
  FRow2.BorderBCStyle := bpsBorder;
  FRow2.ChildSizing.ControlsPerLine := 9;
  FRow2.ChildSizing.Layout := cclLeftToRightThenTopToBottom;
  FRow2.Parent := FPanel;

  F_a := TBCButton.Create(FRow2);
  F_a.Caption := 'A';
  F_a.Parent := FRow2;
  F_a.OnMouseDown := OnButtonClick;

  F_s := TBCButton.Create(FRow2);
  F_s.Caption := 'S';
  F_s.Parent := FRow2;
  F_s.OnMouseDown := OnButtonClick;

  F_d := TBCButton.Create(FRow2);
  F_d.Caption := 'D';
  F_d.Parent := FRow2;
  F_d.OnMouseDown := OnButtonClick;

  F_f := TBCButton.Create(FRow2);
  F_f.Caption := 'F';
  F_f.Parent := FRow2;
  F_f.OnMouseDown := OnButtonClick;

  F_g := TBCButton.Create(FRow2);
  F_g.Caption := 'G';
  F_g.Parent := FRow2;
  F_g.OnMouseDown := OnButtonClick;

  F_h := TBCButton.Create(FRow2);
  F_h.Caption := 'H';
  F_h.Parent := FRow2;
  F_h.OnMouseDown := OnButtonClick;

  F_j := TBCButton.Create(FRow2);
  F_j.Caption := 'J';
  F_j.Parent := FRow2;
  F_j.OnMouseDown := OnButtonClick;

  F_k := TBCButton.Create(FRow2);
  F_k.Caption := 'K';
  F_k.Parent := FRow2;
  F_k.OnMouseDown := OnButtonClick;

  F_l := TBCButton.Create(FRow2);
  F_l.Caption := 'L';
  F_l.Parent := FRow2;
  F_l.OnMouseDown := OnButtonClick;

  { zxcvbnm }

  FRow3 := TBCPanel.Create(FPanel);
  FRow3.AutoSize := True;
  FRow3.Caption := '';
  FRow3.BorderBCStyle := bpsBorder;
  FRow3.ChildSizing.ControlsPerLine := 9;
  FRow3.ChildSizing.Layout := cclLeftToRightThenTopToBottom;
  FRow3.Parent := FPanel;

  F_shift := TBCButton.Create(FRow3);
  F_shift.Caption := '^';
  F_shift.Parent := FRow3;
  F_shift.OnMouseDown := OnButtonClick;
  F_shift.Down := True;

  F_z := TBCButton.Create(FRow3);
  F_z.Caption := 'Z';
  F_z.Parent := FRow3;
  F_z.OnMouseDown := OnButtonClick;

  F_x := TBCButton.Create(FRow3);
  F_x.Caption := 'X';
  F_x.Parent := FRow3;
  F_x.OnMouseDown := OnButtonClick;

  F_c := TBCButton.Create(FRow3);
  F_c.Caption := 'C';
  F_c.Parent := FRow3;
  F_c.OnMouseDown := OnButtonClick;

  F_v := TBCButton.Create(FRow3);
  F_v.Caption := 'V';
  F_v.Parent := FRow3;
  F_v.OnMouseDown := OnButtonClick;

  F_b := TBCButton.Create(FRow3);
  F_b.Caption := 'B';
  F_b.Parent := FRow3;
  F_b.OnMouseDown := OnButtonClick;

  F_n := TBCButton.Create(FRow3);
  F_n.Caption := 'N';
  F_n.Parent := FRow3;
  F_n.OnMouseDown := OnButtonClick;

  F_m := TBCButton.Create(FRow3);
  F_m.Caption := 'M';
  F_m.Parent := FRow3;
  F_m.OnMouseDown := OnButtonClick;

  F_back := TBCButton.Create(FRow3);
  F_back.Caption := '<-';
  F_back.Parent := FRow3;
  F_back.OnMouseDown := OnButtonClick;

  { shift space back }

  FRow4 := TBCPanel.Create(FPanel);
  FRow4.AutoSize := True;
  FRow4.Caption := '';
  FRow4.BorderBCStyle := bpsBorder;
  FRow4.ChildSizing.ControlsPerLine := 1;
  FRow4.ChildSizing.Layout := cclLeftToRightThenTopToBottom;
  FRow4.Parent := FPanel;

  F_space := TBCButton.Create(FRow4);
  F_space.Caption := '____________________';
  F_space.Parent := FRow4;
  F_space.OnMouseDown := OnButtonClick;
end;

destructor TBCKeyboard.Destroy;
begin
  { Everything inside the panel will be freed }
  FPanel.Free;
  inherited Destroy;
end;

procedure TBCKeyboard.Show(AControl: TWinControl);
begin
  FPanel.Parent := AControl;
  FVisible := True;
end;

procedure TBCKeyboard.Show;
begin
  if Self.Owner is TWinControl then
    Show(Self.Owner as TWinControl)
  else
    raise Exception.Create('The parent is not TWinControl descendant.');
end;

procedure TBCKeyboard.Hide;
begin
  FPanel.Parent := nil;
  FVisible := False;
end;

procedure TBCKeyboard.UpdateButtonStyle;
var
  shift_down: boolean;
begin
  F_q.Assign(FButton);
  F_w.Assign(FButton);
  F_e.Assign(FButton);
  F_r.Assign(FButton);
  F_t.Assign(FButton);
  F_y.Assign(FButton);
  F_u.Assign(FButton);
  F_i.Assign(FButton);
  F_o.Assign(FButton);
  F_p.Assign(FButton);
  F_a.Assign(FButton);
  F_s.Assign(FButton);
  F_d.Assign(FButton);
  F_f.Assign(FButton);
  F_g.Assign(FButton);
  F_h.Assign(FButton);
  F_j.Assign(FButton);
  F_k.Assign(FButton);
  F_l.Assign(FButton);
  F_z.Assign(FButton);
  F_x.Assign(FButton);
  F_c.Assign(FButton);
  F_v.Assign(FButton);
  F_b.Assign(FButton);
  F_n.Assign(FButton);
  F_m.Assign(FButton);

  shift_down := F_shift.Down;
  F_shift.Assign(FButton);
  F_shift.Down := shift_down;

  F_back.Assign(FButton);

  F_space.Assign(FButton);
end;

procedure TBCKeyboard.SetFButton(AValue: TBCButton);
begin
  if FButton = AValue then
    Exit;
  FButton := AValue;
end;

procedure TBCKeyboard.SetFPanel(AValue: TBCPanel);
begin
  if FPanel = AValue then
    Exit;
  FPanel := AValue;
end;

procedure TBCKeyboard.SetFPanelsColor(AValue: TColor);
begin
  if FPanelsColor = AValue then
    Exit;
  FPanelsColor := AValue;
  FPanel.Background.Color := AValue;
  FRow1.Background.Color := AValue;
  FRow2.Background.Color := AValue;
  FRow3.Background.Color := AValue;
  FRow4.Background.Color := AValue;
end;

end.
