291 lines
5.9 KiB
ObjectPascal

unit ATSynEdit_Keymap;
{$mode objfpc}{$H+}
//{$define test_correct_keynames}
interface
uses
Classes, SysUtils, Forms;
const
cMaxKeyCombo = 3; //3 must be enougth for everybody..
type
TATKeyArray = array[0..Pred(cMaxKeyCombo)] of TShortcut;
function KeyArrayToString(const K: TATKeyArray): string;
function KeyArraysEqualNotEmpty(const a1, a2: TATKeyArray): boolean;
function KeyArrayLength(const K: TATKeyArray): integer;
type
{ TATKeymapItem }
TATKeymapItem = class
public
Command: integer;
Name: string;
Keys1,
Keys2: TATKeyArray;
end;
type
{ TATKeymap }
TATKeymap = class
private
FList: TList;
FHistory: TATKeyArray;
function GetItem(N: integer): TATKeymapItem;
procedure ClearHistory;
procedure AddToHistory(sh: TShortcut);
function IsMatchedKeys(const AKeys: TATKeyArray; AKey: TShortcut;
AAllowOneKey: boolean): boolean;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function Count: integer;
function IsIndexValid(N: integer): boolean;
property Items[N: integer]: TATKeymapItem read GetItem; default;
procedure Add(ACmd: integer; const AName: string; const AKeys1, AKeys2: array of string);
procedure Delete(N: integer);
function IndexOf(ACmd: integer): integer;
function GetShortcutFromCommand(ACode: integer): TShortcut;
function GetCommandFromShortcut(AKey: TShortcut): integer;
end;
implementation
uses
Math,
LCLProc,
Dialogs;
function KeyArrayLength(const K: TATKeyArray): integer;
var
i: integer;
begin
Result:= 0;
for i:= Low(K) to High(K) do
if K[i]<>0 then
Inc(Result);
end;
{ TATKeymap }
constructor TATKeymap.Create;
begin
FList:= TList.Create;
ClearHistory;
end;
destructor TATKeymap.Destroy;
begin
ClearHistory;
Clear;
FreeAndNil(FList);
inherited;
end;
function TATKeymap.GetItem(N: integer): TATKeymapItem;
begin
if IsIndexValid(N) then
Result:= TATKeymapItem(FList[N])
else
Result:= nil;
end;
procedure TATKeymap.Clear;
var
i: integer;
begin
for i:= FList.Count-1 downto 0 do
TObject(FList[i]).Free;
FList.Clear;
end;
function TATKeymap.Count: integer;
begin
Result:= FList.Count;
end;
function TATKeymap.IsIndexValid(N: integer): boolean;
begin
Result:= (N>=0) and (N<FList.Count);
end;
function _TextToShortcut(const S: string): TShortcut;
begin
Result:= TextToShortCut(S);
{$ifdef test_correct_keynames}
if Result=0 then
Showmessage('Incorrect key in keymap: "'+S+'"');
{$endif}
end;
procedure TATKeymap.Add(ACmd: integer; const AName: string; const AKeys1,
AKeys2: array of string);
var
Item: TATKeymapItem;
i: integer;
begin
Item:= TATKeymapItem.Create;
Item.Command:= ACmd;
Item.Name:= AName;
FillChar(Item.Keys1, Sizeof(Item.Keys1), 0);
FillChar(Item.Keys2, Sizeof(Item.Keys2), 0);
for i:= 0 to Min(High(AKeys1), High(Item.Keys1)) do Item.Keys1[i]:= _TextToShortcut(AKeys1[i]);
for i:= 0 to Min(High(AKeys2), High(Item.Keys2)) do Item.Keys2[i]:= _TextToShortcut(AKeys2[i]);
FList.Add(Item);
end;
procedure TATKeymap.Delete(N: integer);
begin
if IsIndexValid(N) then
FList.Delete(N);
end;
function TATKeymap.IndexOf(ACmd: integer): integer;
var
i: integer;
begin
Result:= -1;
for i:= 0 to Count-1 do
if Items[i].Command=ACmd then
begin Result:= i; Exit end;
end;
function TATKeymap.GetShortcutFromCommand(ACode: integer): TShortcut;
var
i: integer;
begin
Result:= scNone;
for i:= 0 to Count-1 do
if Items[i].Command=ACode then
begin
Result:= Items[i].Keys1[0];
Exit
end;
end;
function TATKeymap.GetCommandFromShortcut(AKey: TShortcut): integer;
var
bCheckSingle: boolean;
i: integer;
begin
Result:= 0;
//first check combos, then check single-keys
for bCheckSingle:= false to true do
for i:= 0 to Count-1 do
if IsMatchedKeys(Items[i].Keys1, AKey, bCheckSingle) or
IsMatchedKeys(Items[i].Keys2, AKey, bCheckSingle) then
begin
Result:= Items[i].Command;
ClearHistory;
Exit
end;
if AKey>0 then
AddToHistory(AKey);
end;
function TATKeymap.IsMatchedKeys(const AKeys: TATKeyArray; AKey: TShortcut;
AAllowOneKey: boolean): boolean;
//function called first for all items with Allow=false (for combos)
//if not found, called for all items with Allow=true (for single keys)
var
LenThis, LenStack, IndexStack, i: integer;
begin
Result:= false;
LenThis:= KeyArrayLength(AKeys);
if LenThis=0 then Exit;
if LenThis=1 then
begin
Result:= AAllowOneKey and (AKeys[0]=AKey);
Exit
end;
//AKey is last in combo AKeys?
if AKeys[LenThis-1]<>AKey then Exit;
//stack filled?
LenStack:= KeyArrayLength(FHistory);
if LenStack<LenThis-1 then
begin
//showmessage('no match: if lenstack');
Exit;
end;
//first keys (except last) of combo lie in stack?
for i:= LenThis-2 downto 0 do
begin
IndexStack:= LenStack-1-(LenThis-2-i);
if (IndexStack>=Low(FHistory)) and (IndexStack<=High(FHistory)) then
if AKeys[i]<>FHistory[IndexStack] then
begin
//showmessage('no match: check items');
Exit;
end;
end;
Result:= true;
end;
procedure TATKeymap.ClearHistory;
begin
FillChar(FHistory, Sizeof(FHistory), 0);
end;
procedure TATKeymap.AddToHistory(sh: TShortcut);
var
len: integer;
begin
len:= KeyArrayLength(FHistory);
if len>=Length(FHistory) then
begin
ClearHistory;
len:= KeyArrayLength(FHistory);
end;
FHistory[len]:= sh;
end;
function KeyArrayToString(const K: TATKeyArray): string;
var
i: integer;
begin
result:= '';
for i:= Low(K) to High(K) do
if K[i]<>0 then
begin
if result<>'' then
result:= result+' * ';
result:= result+ShortcutToText(K[i]);
end;
end;
function KeyArraysEqualNotEmpty(const a1, a2: TATKeyArray): boolean;
var
i: integer;
begin
Result:= true;
if a1[0]=0 then Exit(false);
if a2[0]=0 then Exit(false);
for i:= Low(a1) to High(a1) do
if a1[i]<>a2[i] then Exit(false);
end;
end.