unit MouseActionDialog;

{$mode objfpc}{$H+}

interface

uses
  Classes, Forms, Controls, ExtCtrls, StdCtrls, ButtonPanel, Spin, CheckLst,
  SynEditMouseCmds, LazarusIDEStrConsts, KeyMapping, IDECommands, types;

var
  ButtonName: Array [TSynMouseButton] of String;
  ClickName: Array [TSynMAClickCount] of String;
  ButtonDirName: Array [TSynMAClickDir] of String;

type

  { TMouseaActionDialog }

  TMouseaActionDialog = class(TForm)
    ActionBox: TComboBox;
    ActionLabel: TLabel;
    AltCheck: TCheckBox;
    BtnDefault: TButton;
    BtnLabel: TLabel;
    ButtonBox: TComboBox;
    ButtonPanel1: TButtonPanel;
    CaretCheck: TCheckBox;
    chkUpRestrict: TCheckListBox;
    ClickBox: TComboBox;
    DirCheck: TCheckBox;
    PaintBox1: TPaintBox;
    PriorLabel: TLabel;
    OptBox: TComboBox;
    CtrlCheck: TCheckBox;
    CapturePanel: TPanel;
    OptLabel: TLabel;
    Opt2Spin: TSpinEdit;
    Opt2Label: TLabel;
    ShiftCheck: TCheckBox;
    PriorSpin: TSpinEdit;
    procedure ActionBoxChange(Sender: TObject);
    procedure BtnDefaultClick(Sender: TObject);
    procedure ButtonBoxChange(Sender: TObject);
    procedure CapturePanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
      {%H-}X, {%H-}Y: Integer);
    procedure DirCheckChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer;
      {%H-}MousePos: TPoint; var {%H-}Handled: Boolean);
  private
    FKeyMap: TKeyCommandRelationList;
    procedure AddMouseCmd(const S: string);
    procedure FillListbox;
  public
    { public declarations }
    Procedure ResetInputs;
    Procedure ReadFromAction(MAct: TSynEditMouseAction);
    Procedure WriteToAction(MAct: TSynEditMouseAction);
    property KeyMap: TKeyCommandRelationList read FKeyMap write FKeyMap;
  end;

  function KeyMapIndexOfCommand(AKeyMap: TKeyCommandRelationList; ACmd: Word) : Integer;

implementation

uses Math;

{$R *.lfm}

const
  BtnToIndex: array [TSynMouseButton] of Integer = (0, 1, 2, 3, 4, 5, 6);
  ClickToIndex: array [ccSingle..ccAny] of Integer = (0, 1, 2, 3, 4);
  IndexToBtn: array [0..6] of TSynMouseButton = (mbXLeft, mbXRight, mbXMiddle, mbXExtra1, mbXExtra2, mbXWheelUp, mbXWheelDown);
  IndexToClick: array [0..4] of TSynMAClickCount = (ccSingle, ccDouble, ccTriple, ccQuad, ccAny);

function KeyMapIndexOfCommand(AKeyMap: TKeyCommandRelationList; ACmd: Word): Integer;
var
  i: Integer;
begin
  for i := 0 to AKeyMap.RelationCount - 1 do
    if AKeyMap.Relations[i].Command = ACmd then
      exit(i);
  Result := -1;
end;

{ MouseaActionDialog }

procedure TMouseaActionDialog.AddMouseCmd(const S: string);
var
  i: Integer;
  s2: String;
begin
  i:=0;
  if IdentToSynMouseCmd(S, i) then begin
    s2 := MouseCommandName(i);
    if s2 = '' then s2 := s;
    ActionBox.Items.AddObject(s2, TObject(ptrint(i)));
  end;
end;

procedure TMouseaActionDialog.FillListbox;
const
  cCheckSize=35;
var
  r: TSynMAUpRestriction;
  s: string;
  i, Len: integer;
begin
  for r := low(TSynMAUpRestriction) to high(TSynMAUpRestriction) do
    case r of
      crLastDownPos:          chkUpRestrict.AddItem(synfMatchActionPosOfMouseDown, nil);
      crLastDownPosSameLine:  chkUpRestrict.AddItem(synfMatchActionLineOfMouseDown, nil);
      crLastDownPosSearchAll: chkUpRestrict.AddItem(synfSearchAllActionOfMouseDown, nil);
      crLastDownButton:       chkUpRestrict.AddItem(synfMatchActionButtonOfMouseDown, nil);
      crLastDownShift:        chkUpRestrict.AddItem(synfMatchActionModifiersOfMouseDown, nil);
      crAllowFallback:        chkUpRestrict.AddItem(synfContinueWithNextMouseUpAction, nil);
      else begin
        WriteStr(s, r);
        chkUpRestrict.AddItem(s, nil);
      end;
    end;

  // update scrollbar
  Len := 0;
  with chkUpRestrict do
  begin
    for i := 0 to Items.Count-1 do
      Len := Max(Len, Canvas.TextWidth(Items[i])+cCheckSize);
    ScrollWidth := Len;
  end;
end;

procedure TMouseaActionDialog.FormCreate(Sender: TObject);
var
  mb: TSynMouseButton;
  cc: TSynMAClickCount;
begin
  ButtonName[mbXLeft]:=dlgMouseOptBtnLeft;
  ButtonName[mbXRight]:=dlgMouseOptBtnRight;
  ButtonName[mbXMiddle]:=dlgMouseOptBtnMiddle;
  ButtonName[mbXExtra1]:=dlgMouseOptBtnExtra1;
  ButtonName[mbXExtra2]:=dlgMouseOptBtnExtra2;
  ButtonName[mbXWheelUp]:=dlgMouseOptBtnWheelUp;
  ButtonName[mbXWheelDown]:=dlgMouseOptBtnWheelDown;

  ClickName[ccSingle]:=dlgMouseOptBtn1;
  ClickName[ccDouble]:=dlgMouseOptBtn2;
  ClickName[ccTriple]:=dlgMouseOptBtn3;
  ClickName[ccQuad]:=dlgMouseOptBtn4;
  ClickName[ccAny]:=dlgMouseOptBtnAny;

  FillListbox;

  ButtonDirName[cdUp]:=lisUp;
  ButtonDirName[cdDown]:=lisDown;

  Caption := dlgMouseOptDlgTitle;
  CapturePanel.Caption := dlgMouseOptCapture;
  CapturePanel.ControlStyle := ControlStyle + [csTripleClicks, csQuadClicks];
  CaretCheck.Caption := dlgMouseOptCaretMove;
  ActionBox.Clear;
  GetEditorMouseCommandValues(@AddMouseCmd);
  ButtonBox.Clear;
  for mb := low(TSynMouseButton) to high(TSynMouseButton) do
    ButtonBox.Items.add(ButtonName[mb]);
  ClickBox.Clear;
  for cc:= low(TSynMAClickCount) to high(TSynMAClickCount) do
    ClickBox.Items.add(ClickName[cc]);
  DirCheck.Caption   := dlgMouseOptCheckUpDown;
  ShiftCheck.Caption := dlgMouseOptModShift;
  AltCheck.Caption   := dlgMouseOptModAlt;
  CtrlCheck.Caption  := dlgMouseOptModCtrl;
  ActionLabel.Caption := dlgMouseOptDescAction;
  BtnLabel.Caption := dlgMouseOptDescButton;
  BtnDefault.Caption :=  dlgMouseOptBtnModDef;
  PriorLabel.Caption := dlgMouseOptPriorLabel;
  Opt2Label.Caption := dlgMouseOptOpt2Label;
end;

procedure TMouseaActionDialog.ResetInputs;
var
  r: TSynMAUpRestriction;
begin
  ActionBox.ItemIndex := 0;
  ButtonBox.ItemIndex := 0;
  ClickBox.ItemIndex  := 0;
  DirCheck.Checked    := False;
  ShiftCheck.State := cbGrayed;
  AltCheck.State := cbGrayed;
  CtrlCheck.State := cbGrayed;
  for r := low(TSynMAUpRestriction) to high(TSynMAUpRestriction) do
    chkUpRestrict.Checked[ord(r)] := False;

  ActionBoxChange(nil);
  OptBox.ItemIndex := 0;
end;

procedure TMouseaActionDialog.BtnDefaultClick(Sender: TObject);
begin
  ShiftCheck.State := cbGrayed;
  AltCheck.State := cbGrayed;
  CtrlCheck.State := cbGrayed;
end;

procedure TMouseaActionDialog.ButtonBoxChange(Sender: TObject);
begin
  DirCheck.Enabled := not(IndexToBtn[ButtonBox.ItemIndex] in [mbXWheelUp, mbXWheelDown]);
  chkUpRestrict.Enabled := DirCheck.Enabled and DirCheck.Checked;
end;

procedure TMouseaActionDialog.ActionBoxChange(Sender: TObject);
var
  ACmd: TSynEditorMouseCommand;
  i: Integer;
begin
  OptBox.Items.Clear;
  ACmd := TSynEditorMouseCommand({%H-}PtrUInt(Pointer(ActionBox.items.Objects[ActionBox.ItemIndex])));
  if ACmd =  emcSynEditCommand then begin
    OptBox.Enabled := True;
    OptBox.Clear;
    for i := 0 to KeyMap.RelationCount - 1 do
      if (KeyMap.Relations[i].Category.Scope = IDECmdScopeSrcEdit) or
         (KeyMap.Relations[i].Category.Scope = IDECmdScopeSrcEditOnly)
      then
        OptBox.Items.AddObject(KeyMap.Relations[i].GetLocalizedName,
                               TObject({%H-}Pointer(PtrUInt(KeyMap.Relations[i].Command))));
    OptLabel.Caption := dlgMouseOptionsynCommand;
    OptBox.ItemIndex := 0;
  end
  else
  begin
    OptBox.Items.CommaText := MouseCommandConfigName(ACmd);
    if OptBox.Items.Count > 0 then begin
      OptLabel.Caption := OptBox.Items[0];
      OptBox.Items.Delete(0);
      OptBox.Enabled := True;
      OptBox.ItemIndex := 0;
    end else begin
      OptLabel.Caption := '';
      OptBox.Enabled := False
    end;
  end;
end;

procedure TMouseaActionDialog.CapturePanelMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  ButtonBox.ItemIndex := BtnToIndex[SynMouseButtonMap[Button]];
  ClickBox.ItemIndex := 0;
  if ssDouble in Shift then ClickBox.ItemIndex := 1;
  if ssTriple in Shift then ClickBox.ItemIndex := 2;
  if ssQuad in Shift then ClickBox.ItemIndex := 3;
  ShiftCheck.Checked := ssShift in Shift;
  AltCheck.Checked   := ssAlt in Shift;
  CtrlCheck.Checked  := ssCtrl in Shift;
end;

procedure TMouseaActionDialog.DirCheckChange(Sender: TObject);
begin
  chkUpRestrict.Enabled := DirCheck.Checked;
end;

procedure TMouseaActionDialog.PaintBox1MouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
  if WheelDelta > 0
  then ButtonBox.ItemIndex := BtnToIndex[mbXWheelUp]
  else ButtonBox.ItemIndex := BtnToIndex[mbXWheelDown];
  ClickBox.ItemIndex := 4;
  ShiftCheck.Checked := ssShift in Shift;
  AltCheck.Checked   := ssAlt in Shift;
  CtrlCheck.Checked  := ssCtrl in Shift;
end;

procedure TMouseaActionDialog.ReadFromAction(MAct: TSynEditMouseAction);
var
  r: TSynMAUpRestriction;
begin
  ActionBox.ItemIndex := ActionBox.Items.IndexOfObject(TObject({%H-}Pointer(PtrUInt(MAct.Command))));
  ButtonBox.ItemIndex := BtnToIndex[MAct.Button];
  ClickBox.ItemIndex  := ClickToIndex[MAct.ClickCount];
  DirCheck.Checked    := MAct.ClickDir = cdUp;
  CaretCheck.Checked := MAct.MoveCaret;
  ShiftCheck.Checked := (ssShift in MAct.ShiftMask) and (ssShift in MAct.Shift);
  if not(ssShift in MAct.ShiftMask) then ShiftCheck.State := cbGrayed;
  AltCheck.Checked := (ssAlt in MAct.ShiftMask) and (ssAlt in MAct.Shift);
  if not(ssAlt in MAct.ShiftMask) then AltCheck.State := cbGrayed;
  CtrlCheck.Checked := (ssCtrl in MAct.ShiftMask) and (ssCtrl in MAct.Shift);
  if not(ssCtrl in MAct.ShiftMask) then CtrlCheck.State := cbGrayed;
  PriorSpin.Value := MAct.Priority;
  Opt2Spin.Value := MAct.Option2;
  for r := low(TSynMAUpRestriction) to high(TSynMAUpRestriction) do
    chkUpRestrict.Checked[ord(r)] := r in MAct.ButtonUpRestrictions;

  ActionBoxChange(nil);
  ButtonBoxChange(nil);
  if OptBox.Enabled then begin
    if MAct.Command =  emcSynEditCommand then
      OptBox.ItemIndex := OptBox.Items.IndexOfObject(TObject({%H-}Pointer(PtrUInt(MAct.Option))))
    else
      OptBox.ItemIndex := MAct.Option;
  end;
end;

procedure TMouseaActionDialog.WriteToAction(MAct: TSynEditMouseAction);
var
  r: TSynMAUpRestriction;
begin
  MAct.Command := TSynEditorMouseCommand({%H-}PtrUInt(Pointer(ActionBox.items.Objects[ActionBox.ItemIndex])));
  MAct.Button := IndexToBtn[ButtonBox.ItemIndex];
  MAct.ClickCount := IndexToClick[ClickBox.ItemIndex];
  MAct.MoveCaret := CaretCheck.Checked;
  if DirCheck.Checked
  then MAct.ClickDir := cdUp
  else MAct.ClickDir := cdDown;
  MAct.Shift := [];
  MAct.ShiftMask := [];
  if ShiftCheck.State <> cbGrayed then MAct.ShiftMask := MAct.ShiftMask + [ssShift];
  if AltCheck.State <> cbGrayed then MAct.ShiftMask := MAct.ShiftMask + [ssAlt];
  if CtrlCheck.State <> cbGrayed then MAct.ShiftMask := MAct.ShiftMask + [ssCtrl];
  if ShiftCheck.Checked then MAct.Shift := MAct.Shift + [ssShift];
  if AltCheck.Checked then MAct.Shift := MAct.Shift + [ssAlt];
  if CtrlCheck.Checked then MAct.Shift := MAct.Shift + [ssCtrl];
  MAct.Priority := PriorSpin.Value;
  MAct.Option2 := Opt2Spin.Value;
  MAct.ButtonUpRestrictions := [];
  for r := low(TSynMAUpRestriction) to high(TSynMAUpRestriction) do
    if chkUpRestrict.Checked[ord(r)] then
      MAct.ButtonUpRestrictions := MAct.ButtonUpRestrictions + [r];

  if OptBox.Enabled then begin
    if MAct.Command =  emcSynEditCommand then begin
      MAct.Option := TSynEditorMouseCommandOpt({%H-}PtrUInt(Pointer(OptBox.Items.Objects[OptBox.ItemIndex])));
    end
    else
      MAct.Option := OptBox.ItemIndex;
  end
  else
    MAct.Option := 0;
end;

end.

