Input.pas
上传用户:yj_qiu
上传日期:2022-08-08
资源大小:23636k
文件大小:42k
源码类别:

游戏引擎

开发平台:

Delphi

  1. (*
  2.  @Abstract(Input unit)
  3.  (C) 2006 George "Mirage" Bakhtadze. <a href="http://www.casteng.com">www.casteng.com</a> <br>
  4.  The source code may be used under either MPL 1.1 or LGPL 2.1 license. See included license.txt file <br>
  5.  Unit contains basic input classes
  6. *)
  7. {$Include GDefines.inc}
  8. unit Input;
  9. { ToDo
  10.   Optionally break sequence if another sequense completed }
  11. interface
  12. uses SysUtils, TextFile, BaseTypes, OSUtils, Basics, BaseMsg;
  13. const
  14.   // Default value of maximum timeout between two consequent events
  15.   DefaultTimeout = 500;
  16.   // Maximum simultaneous events
  17.   MaxEvents = 127;
  18.   // Amount of mouse move to count it as a stroke
  19.   MouseStrokeTolerance = 3;
  20.       // Action types
  21.   // Set a boolean flag
  22.   atBooleanOn = 1;
  23.   // Reset a boolean flag
  24.   atBooleanOff = 2;
  25.   // Toggle a boolean flag
  26.   atBooleanToggle = 3;
  27.   // Set a byte value
  28.   atSetByte = 4;
  29.   // Set a word (two byte) value
  30.   atSetWord = 5;
  31.   // Set a long word (four byte) value
  32.   atSetLongWord = 6;
  33. type
  34.   (* Binding string type. Syntax: <br>
  35.     BindElement = (&lt;<b>Key</b>&gt;&lt;<b>Specifier</b>&gt;)|&lt;<b>Gesture</b>&gt;"^" <br>
  36.     <b>Key</b>         = A key name <br>
  37.     <b>Specifier</b>   = ","|"+"|"-"|":" - can be omitted at the end of the binding ("," will be assumed) <br>
  38.     <b>Gesture</b>      = "MouseMove"|"MouseMoveH"|"MouseMoveV"|"MouseRoll"|
  39.                          "MouseStrokeLeft"|"MouseStrokeRight"|"MouseStrokeUp"|"MouseStrokeDown"|
  40.                          "MouseStrokeLeftUp"|"MouseStrokeRightUp"|"MouseStrokeLeftDown"|"MouseStrokeRightDown"
  41.     <b>Binding</b>     = &lt;<b>BindElement</b>&gt; {&lt;<b>BindElement</b>&gt;} <br>
  42.   Specifiers: <br>
  43.     ,   - click <br>
  44.     +   - key down <br>
  45.     -   - key up <br>
  46.     :   - double click <br>
  47.   Examples: <br>
  48.     <b>Alt+Q</b> - the binding will be activated when user press Alt, then click (press and release) Q (without releasing Alt) <br>
  49.     <b>A,B,C</b> - the binding will be activated when user click A, then B and then C <br>
  50.     <b>A+B,A-</b> - the binding will be activated when user press A, then press and release B and then release A <br>
  51.   A maximum timeout between two consequent events can be specified when binding *)
  52.   TBindingStr = string;
  53.   // Key query states
  54.   TKeyQueryState = (// Key is up
  55.                     kqsUp,
  56.                     // Key is down
  57.                     kqsDown);
  58.   // Query input results elements
  59.   TInputQueryResultElement = (// Some key (including mouse buttons) was pressed
  60.                               iqrKeyPressed,
  61.                               // Some key (including mouse buttons) state was changed
  62.                               iqrKeyChanged,
  63.                               // Mouse was moved
  64.                               iqrMouseMoved);
  65.   // Query input results
  66.   TInputQueryResult = set of TInputQueryResultElement;
  67.   // Input event filters
  68.   TInputFilterElement = (// Query only events which was bound using <b>@Link(BindCommand)</b>, <b>@Link(BindDelegate)</b> or <b>@Link(BindPointer)</b>
  69.                          ifBound,
  70.                          // Query all events
  71.                          ifNotBound);
  72.   // Input filter
  73.   TInputFilter = set of TInputFilterElement;
  74.   // Modifier keys
  75.   TKeyModifier  = (// Any CTRL
  76.                    kmControl,
  77.                    // Left CTRL
  78.                    kmLControl,
  79.                    // Right CTRL
  80.                    kmRControl,
  81.                    // Any Shift
  82.                    kmShift,
  83.                    // Left Shift
  84.                    kmLShift,
  85.                    // Right Shift
  86.                    kmRShift,
  87.                    // Any Alt
  88.                    kmAlt,
  89.                    // Left Alt
  90.                    kmLAlt,
  91.                    // Right Alt
  92.                    kmRAlt,
  93.                    // Left Win
  94.                    kmLWin,
  95.                    // Right Win
  96.                    kmRWin,
  97.                    // Any Win key
  98.                    kmWin);
  99.   // Modifier keys set
  100.   TKeyModifiers = set of TKeyModifier;
  101.   // Hotkey
  102.   THotkey = type Longword;
  103.   // Possible event types
  104.   TEventType = (// Some key (including mouse buttons) was pushed down
  105.                 btKeyDown,
  106.                 // Some previously pressed key was released
  107.                 btKeyUp,
  108.                 // A key was clicked (btKeyDown + btKeyUp)
  109.                 btKeyClick,
  110.                 // A key was clicked two times within double click timeout
  111.                 btKeyDblClick,
  112.                 // Mouse was moved
  113.                 btMouseMove,
  114.                 // Mouse was moved horizontally
  115.                 btMouseHMove,
  116.                 // Mouse was moved vertically
  117.                 btMouseVMove,
  118.                 // Mouse wheel was rolled
  119.                 btMouseRoll,
  120.                 // Mouse was moved left at least by @Link(MouseStrokeTolerance) units
  121.                 btStrokeLeft,
  122.                 // Mouse was moved right at least by @Link(MouseStrokeTolerance) units
  123.                 btStrokeRight,
  124.                 // Mouse was moved up at least by @Link(MouseStrokeTolerance) units
  125.                 btStrokeUp,
  126.                 // Mouse was moved down at least by @Link(MouseStrokeTolerance) units
  127.                 btStrokeDown,
  128.                 // Mouse was moved left+up at least by @Link(MouseStrokeTolerance) units
  129.                 btStrokeLeftUp,
  130.                 // Mouse was moved right+up at least by @Link(MouseStrokeTolerance) units
  131.                 btStrokeRightUp,
  132.                 // Mouse was moved left+down at least by @Link(MouseStrokeTolerance) units
  133.                 btStrokeLeftDown,
  134.                 // Mouse was moved right+down at least by @Link(MouseStrokeTolerance) units
  135.                 btStrokeRightDown,
  136.                 // No event
  137.                 btNone);
  138.   // Pointer to a binding
  139.   PBinding = ^TBinding;
  140.   // Binding contains an event type, event data and a pointer to next binding (or nil) to be able to bind a sequense of input events to a single action
  141.   TBinding = record
  142.     BindType: TEventType;
  143.     BindData: Word;
  144.     Next: PBinding;
  145.   end;
  146.   TBindingName = ShortString;
  147.   { Type of a method to which an input event sequence can be bound. <b>CustomData</b> is an optional user data.
  148.     Value of <b>EventData</b> depends on type of event which finishes the sequence (the last one).
  149.     If it's a keyboard event <b>EventData</b> contains a key code, if it's a mouse event <b>EventData</b> contains event-specific
  150.     mouse coordinate or both (for @Link(btMouseMove) event type) in format where low 16 bit is X coordinate and high 16 bit is Y coordinate. }
  151.   TInputDelegate = procedure(EventData: Integer; CustomData: SmallInt) of object;
  152.   // Input event. <b>EventData</b> is an event type-specific data such as a key code
  153.   TInputEvent = packed record
  154.     EventType: TEventType;
  155.     EventData: SmallInt;
  156.   end;
  157.   TInputEvents = array of TInputEvent;
  158.   // Mouse state data structure. <b>lX</b>, <b>lY</b> and <b>lZ</b> is mouse position at corresponding axis. <b>Buttons</b> - mouse buttons state
  159.   TMouseState = packed record
  160.     lX, lY, lZ: LongInt;
  161.     Buttons: array[0..3] of Byte;
  162.   end;
  163.   CController = class of TController;
  164.   { @Abstract(CAST II input controller base class)
  165.     TController polls input, checks if it matches specified in bindings event sequences and performs bond actions.
  166.     Also some input-related routines and properties are provided. }
  167.   TController = class(TSubsystem)
  168.   private
  169.     CurrentMs: Cardinal;
  170.     MouseQueryMs: Cardinal;
  171.     FSystemCursor, MouseCaptureActuallyActive: Boolean;
  172. //    LastMouseEvent: TBindTypes;
  173.     InputEvents: array[0..MaxEvents] of TInputEvent; TotalEvents: Integer;
  174.     MouseXCounter, MouseYCounter: LongInt;
  175.     Pointers: array of Pointer;
  176.     Delegates: array of TInputDelegate;
  177.     Bindings: array of packed record
  178.       First, Current, Terminator: PBinding;
  179.       LastMs: Cardinal; TimeOutMs: Word;
  180.       MembersKind: Word;                                        // Keyboard or mouse, also indicates binding kind (message, delegate or pointer)
  181.       case Integer of
  182.         0: (MessageType: CMessage);
  183.         1: (DelegateIndex: Word; CustomData: Smallint);
  184.         2: (PTRIndex, AType: Byte; Value: Word);
  185.     end;
  186.     TotalBindings, TotalDelegates, TotalPointers: Word;
  187.     procedure SetSystemCursor(const Value: Boolean); 
  188.     procedure RecalcCaptureCoords;
  189.     function NewBinding(BType: TEventType; BData: Word; ANext: PBinding = nil): PBinding;
  190.     function GetLastBinding(Binding: PBinding): PBinding;
  191.     procedure ParseBinding(s: string; out Binding, Terminator: PBinding);
  192.     procedure UnBind(const Index: Longword);
  193.     procedure CleanPointers; virtual;
  194.   protected
  195.     // Application window handle
  196.     Handle: Cardinal;
  197.     // Last keyboard state
  198.     LastKeyState,
  199.     // Current keyboard state
  200.     KeyboardState: TKbdState;
  201.     // Last mouse state
  202.     LastMouseState,
  203.     // Current mouse state
  204.     MouseState: TMouseState;
  205.     // Current user input buffer
  206.     FInputBuffer: string;
  207.     // Current key query state
  208.     KeyQueryState: array[0..255] of record
  209.       State: TKeyQueryState;
  210.       LastClickedTime: Cardinal;
  211.     end;
  212.     // Mouse capture state
  213.     FMouseCapture: Boolean;
  214.     // Mouse X before mouse was captured
  215.     CaptureMouseX,
  216.     // Mouse Y before mouse was captured
  217.     CaptureMouseY: Integer;
  218.     // A rectangle where the mouse cursor can be
  219.     MouseWindow: OSUtils.TRect;
  220.     // String names of keys
  221.     KeyStr: array[0..255] of TBindingName;
  222.     // Returns mouse event which occured since last process
  223.     function GetMouseEvent: TEventType;
  224.     // Implementation-specific mouse capture routine
  225.     procedure ApplyMouseCapture(Value: Boolean); virtual; 
  226.     // If set to <b>True</b> captures mouse and hide its cursor
  227.     procedure SetMouseCapture(const Value: Boolean); virtual;
  228.     // Places mouse cursor to capture position (center of current window)
  229.     procedure SetCursorCapturePos;
  230.     // Returns user input buffer and clear it
  231.     function GetInputBuffer: string;
  232.     // Performs input query. Returns set of input query elements and fills internal events array.
  233.     function QueryInput: TInputQueryResult; virtual;
  234.   public
  235.     // A message handler to direct events to
  236.     MessageHandler: BaseMsg.TMessageHandler;
  237.     // Double click timeout. Used by events with @Link(btKeyDblClick) event type.
  238.     DblClickTimeout: Cardinal;
  239.     // If <b>True</b> all entered aplhabetical characters will be stored in @Link(InputBuffer)
  240.     EnableCharactersInput: Boolean;
  241.     // Current mouse X
  242.     MouseX,
  243.     // Current mouse Y
  244.     MouseY: Integer;
  245.     // Minimum time in ms between queries for mouse strokes
  246.     MouseQueryTimeOut: Cardinal;
  247.     constructor Create(AHandle: Cardinal; AMessageHandler: BaseMsg.TMessageHandler); virtual;
  248.     destructor Destroy; override;
  249.     // Returns a string name of the specified key
  250.     function KeyToStr(Key: Integer): string;
  251.     // Returns a key which corresponds to the specified name
  252.     function StrToKey(Name: string): Integer;
  253.     // Returns <b>True</b> if <b>Key</b> is a one of modifiers key (CTRL, ALT, etc)
  254.     function IsModifierKey(Key: Word): Boolean;
  255.     // Returns a hot key by key and set of modifiers
  256.     function GetHotKey(Key: Word; Modifiers: TKeyModifiers): THotkey;
  257.     // Returns a string representation of the specified hot key
  258.     function HotKeyToStr(HotKey: THotkey): string;
  259.     // Parses a binding string and returns hot key
  260.     function StrToHotKey(const BindStr: TBindingStr): THotkey;
  261.     // Returns a set of modifiers from a hotkey
  262.     function GetHotkeyModifiers(HotKey: THotkey): TKeyModifiers;
  263.     // Default message handler
  264.     procedure HandleMessage(const Msg: TMessage); override;
  265.     // Parses the specified binding string and binds the specified message class to it. <b>ATimeoutMs</b> specifies the maximum time between two consequent events.
  266.     procedure BindCommand(const ABinding: TBindingStr; MsgType: CMessage; const ATimeoutMs: LongWord = DefaultTimeout); virtual;
  267.     // Parses the specified binding string and binds the specified delegate call to it. <b>ATimeoutMs</b> specifies the maximum time between two consequent events.
  268.     procedure BindDelegate(const ABinding: TBindingStr; Delegate: TInputDelegate; ACustomData: SmallInt; const ATimeoutMs: LongWord = 0); virtual;
  269.     { Parses the specified binding string and binds the specified pointer action to it. <b>ATimeoutMs</b> specifies the maximum time between two consequent events. <br>
  270.       ActionType can be one of the following: <br>
  271.       @Link(atBooleanOn), @Link(atBooleanOff), @Link(atBooleanToggle), @Link(atSetByte), @Link(atSetWord), @Link(atSetLongWord)
  272.       It's recommended to use BindCommand or BindDelegate instead. }
  273.     procedure BindPointer(const ABinding: TBindingStr; const ActionType: Longword; const Data: Pointer; const Value: Word = 0; const ATimeoutMs: LongWord = 0); virtual;
  274.     // Clears all bindings
  275.     procedure UnBindAll;
  276.     // Sets mouse window
  277.     procedure SetMouseWindow(const X1, Y1, X2, Y2: Longint); virtual;
  278.     // Implementation-specific input poll
  279.     procedure GetInputState; virtual; abstract;
  280.     // Pools input and checks if any of bindings should be activated
  281.     procedure ProcessInput(const EventFilter: TInputFilter);
  282.     // Transforms all input events to corresponding messages and directs them to @Link(MessageHandler)
  283.     procedure InputEventsToMessages;
  284.     // Current user input buffer. Reading this property will clear the buffer.
  285.     property InputBuffer: string read GetInputBuffer;
  286.     // Read/write this property to determine if system mouse cursor is used.
  287.     property SystemCursor: Boolean read FSystemCursor write SetSystemCursor;
  288.     // If set to <b>True</b> captures mouse and hide its cursor
  289.     property MouseCapture: Boolean read FMouseCapture write SetMouseCapture;
  290.   end;
  291. implementation
  292. const
  293.   // Maximum number of bound pointers (currently must fit in a single byte)
  294.   MaxPointers = 256;
  295.   // Maximum number of bound delegates
  296.   MaxDelegates = $FFFF;
  297.     // Binding members
  298.   bmNone = 0;
  299.   bmKeyboard = 1;
  300.   bmMouseButtons = 2;
  301.   bmMouseMotion = 4;
  302.     // Binding kinds
  303.   bkMessage = 0;
  304.   bkDelegate = $4000;
  305.   bkPointer  = $8000;
  306.     // Modifier keys
  307.   hmControl  = 1 shl 30;
  308.   hmLControl = 1 shl 29;
  309.   hmRControl = 1 shl 28;
  310.   hmShift    = 1 shl 27;
  311.   hmLShift   = 1 shl 26;
  312.   hmRShift   = 1 shl 25;
  313.   hmAlt      = 1 shl 24;
  314.   hmLAlt     = 1 shl 23;
  315.   hmRAlt     = 1 shl 22;
  316.   hmLWin     = 1 shl 20;
  317.   hmRWin     = 1 shl 19;
  318.   hmWin      = 1 shl 18;
  319. { TController }
  320. procedure TController.SetSystemCursor(const Value: Boolean);
  321. begin
  322. //  if not Active then Exit;
  323.   FSystemCursor := Value;
  324.   if FSystemCursor then ShowCursor else HideCursor;
  325. end;
  326. procedure TController.RecalcCaptureCoords;
  327. var Rect: TRect;
  328. begin
  329.   OSUtils.GetWindowRect(Handle, Rect);
  330.   CaptureMouseX := (Rect.Left + Rect.Right)  div 2;
  331.   CaptureMouseY := (Rect.Top  + Rect.Bottom) div 2;
  332. end;
  333. function TController.NewBinding(BType: TEventType; BData: Word; ANext: PBinding = nil): PBinding;
  334. var BindKey: Byte;
  335. begin
  336.   BindKey := BData and 255;
  337.   GetMem(Result, SizeOf(TBinding));
  338.   case BType of
  339.     btKeyClick: begin
  340.       with Result^ do begin
  341.         BindType := btKeyDown; BindData := 0*BData and $FF00 + BindKey;
  342.       end;
  343.       GetMem(Result^.Next, SizeOf(TBinding));
  344.       with Result^.Next^ do begin
  345.         BindType := btKeyUp; BindData := 0*BData and $FF00 + BindKey; Next := ANext;
  346.       end;
  347.     end;
  348.     else with Result^ do begin
  349.       BindType := BType; BindData := 0*BData and $FF00 + BindKey; Next := ANext;
  350.     end;
  351.   end;
  352. end;
  353. function TController.GetLastBinding(Binding: PBinding): PBinding;
  354. begin
  355.   Result  := Binding;
  356.   if Binding = nil then Exit;
  357.   Binding := Result^.Next;
  358.   while Binding <> nil do begin
  359.     Result  := Binding;
  360.     Binding := Result^.Next;
  361.   end;
  362. end;
  363. function TController.GetMouseEvent: TEventType;   // ToDo: mouse roll support
  364. var Rel, RelX, RelY: Single;
  365. begin
  366.   Result := btNone;
  367.   Inc(MouseXCounter, MouseState.LX);
  368.   Inc(MouseYCounter, MouseState.LY);
  369.   if Abs(MouseXCounter) > 0 then Result := btMouseHMove;
  370.   if Abs(MouseYCounter) > Abs(MouseXCounter) then Result := btMouseVMove;
  371.   if CurrentMs - MouseQueryMs >= MouseQueryTimeOut then begin
  372.     MouseQueryMs := CurrentMs;
  373.     if MouseYCounter <> 0 then begin
  374.       Rel := MouseXCounter / MouseYCounter;
  375.       if MouseYCounter > 0 then RelX := Rel else RelX := -Rel;
  376.       if RelX < -MouseStrokeTolerance then Result := btStrokeLeft;
  377.       if RelX >  MouseStrokeTolerance then Result := btStrokeRight;
  378.       if MouseXCounter > 0 then RelY := Rel else RelY := -Rel;
  379.       if (RelY <   1/MouseStrokeTolerance) and (RelY >   0) then Result := btStrokeDown;
  380.       if (RelY >  -1/MouseStrokeTolerance) and (RelY <   0) then Result := btStrokeUp;
  381.       if (Rel  > 1-1/MouseStrokeTolerance) and (Rel  < 1+1/MouseStrokeTolerance) then begin
  382.         if MouseYCounter > 0 then Result := btStrokeRightDown else Result := btStrokeLeftUp;
  383.       end;
  384.       if (Rel > -1-1/MouseStrokeTolerance) and (Rel < -1+1/MouseStrokeTolerance) then begin
  385.         if MouseYCounter > 0 then Result := btStrokeLeftDown else Result := btStrokeRightUp;
  386.       end;
  387.     end else begin
  388.       if MouseXCounter < -MouseStrokeTolerance then Result := btStrokeLeft;
  389.       if MouseXCounter >  MouseStrokeTolerance then Result := btStrokeRight;
  390.     end;
  391.     MouseXCounter := 0; MouseYCounter := 0;
  392.   end;
  393. end;
  394. procedure TController.ApplyMouseCapture(Value: Boolean);
  395. begin
  396.   MouseCaptureActuallyActive := Value;
  397. end;
  398. procedure TController.SetMouseCapture(const Value: Boolean);
  399. var SX, SY: Integer;
  400. begin
  401.   if FMouseCapture = Value then Exit;
  402.   if Value then begin
  403.     CaptureMouseX := MouseX;
  404.     CaptureMouseY := MouseY;
  405.     RecalcCaptureCoords;
  406.   end else begin
  407.     MouseX := CaptureMouseX;
  408.     MouseY := CaptureMouseY;
  409.     SX := MouseX; SY := MouseY;
  410.     ClientToScreen(Handle, SX, SY);
  411.     SetCursorPos(SX, SY);
  412.   end;
  413.   FMouseCapture := Value;
  414.   ApplyMouseCapture(Value);
  415. end;
  416. procedure TController.SetCursorCapturePos;
  417. begin
  418.   if not MouseCaptureActuallyActive then Exit;
  419.   SetCursorPos(CaptureMouseX, CaptureMouseY);
  420. end;
  421. function TController.GetInputBuffer: string;
  422. begin
  423.   Result := FInputBuffer;
  424.   FInputBuffer := '';
  425. end;
  426. function TController.QueryInput: TInputQueryResult;
  427. var i: Integer;
  428.   procedure AddEvent(EType: TEventType; EData: Smallint);
  429.   begin
  430.     Assert(TotalEvents < MaxEvents, 'TController.QueryInput: too many events');
  431.     if (EType = btNone) or (TotalEvents >= MaxEvents) then Exit;
  432.     Inc(TotalEvents);
  433.     InputEvents[TotalEvents-1].EventType := EType;
  434.     InputEvents[TotalEvents-1].EventData := EData;
  435.     case EType of
  436.       btKeyDown: begin
  437.         Result := Result + [iqrKeyPressed, iqrKeyChanged];
  438.   {      if (EData = IK_LSHIFT)   or (EData = IK_RSHIFT)   or (EData = IK_SHIFT)   then ShiftState := True;
  439.         if (EData = IK_LCONTROL) or (EData = IK_RCONTROL) or (EData = IK_CONTROL) then CtrlState  := True;
  440.         if (EData = IK_LALT)     or (EData = IK_RALT)     or (EData = IK_ALT)     then AltState   := True;}
  441.       end;
  442.       btKeyUp: begin
  443.         Result := Result + [iqrKeyChanged];
  444.   {      if (EData = IK_LSHIFT)   or (EData = IK_RSHIFT)   or (EData = IK_SHIFT)   then ShiftState := False;
  445.         if (EData = IK_LCONTROL) or (EData = IK_RCONTROL) or (EData = IK_CONTROL) then CtrlState  := False;
  446.         if (EData = IK_LALT)     or (EData = IK_RALT)     or (EData = IK_ALT)     then AltState   := False;}
  447.       end;
  448.       btStrokeLeft..btStrokeRightDown: Result := Result + [iqrMouseMoved];
  449.     end;
  450.   end;
  451. begin
  452.   Result := [];
  453.   TotalEvents := 0;
  454.   AddEvent(GetMouseEvent, 0);
  455.   for i := 0 to 255 do begin
  456.     if KeyboardState[i] <> LastKeyState[i] then begin
  457.       if KeyboardState[i] >= 128 then AddEvent(btKeyDown, i) else AddEvent(btKeyUp, i);
  458.     end;
  459.   end;
  460. end;
  461. constructor TController.Create(AHandle: Cardinal; AMessageHandler: BaseMsg.TMessageHandler);
  462. begin
  463.   Handle := AHandle;
  464.   FSystemCursor := True;
  465.   {$Include I_KeyStr.inc}
  466.   MouseQueryTimeOut := 0;
  467.   DblClickTimeout   := 300;
  468. {  ShiftState := False;
  469.   CtrlState := False;
  470.   AltState := False;}
  471.   MessageHandler := AMessageHandler;
  472.   GetInputState;
  473.   MouseWindow := GetClipCursor;
  474.   SetMouseWindow(MouseWindow.Left, MouseWindow.Top, MouseWindow.Right, MouseWindow.Bottom);
  475.   FillChar(KeyQueryState, SizeOf(KeyQueryState), 0);
  476. end;
  477. destructor TController.Destroy;
  478. var i: Integer;
  479. begin
  480.   MouseCapture := False;
  481.   for i := 0 to TotalBindings - 1 do with Bindings[i] do begin
  482.     Current := First;
  483.     while Current <> nil do begin
  484.       First := Current;
  485.       Current := Current^.Next;
  486.       FreeMem(First);
  487.     end;
  488.   end;
  489.   SetLength(Bindings, 0);
  490.   inherited;
  491. end;
  492. function TController.KeyToStr(Key: Integer): string;
  493. begin
  494.   Result := KeyStr[Key];
  495. end;
  496. function TController.StrToKey(Name: string): Integer;
  497. begin
  498.   Name := UpperCase(TrimSpaces(Name));
  499.   Result := 255;
  500.   while (Result >= 0) and (UpperCase(KeyStr[Result]) <> Name) do Dec(Result);
  501. end;
  502. function TController.IsModifierKey(Key: Word): Boolean;
  503. begin
  504.   Result := (Key = IK_CONTROL) or (Key = IK_LCONTROL) or (Key = IK_RCONTROL) or
  505.             (Key = IK_ALT)     or (Key = IK_LALT)     or (Key = IK_RALT)     or
  506.             (Key = IK_SHIFT)   or (Key = IK_LSHIFT)   or (Key = IK_RSHIFT)   or
  507.                                   (Key = IK_RWIN)     or (Key = IK_LWIN);
  508. end;
  509. function TController.GetHotKey(Key: Word; Modifiers: TKeyModifiers): THotkey;
  510. begin
  511.   Result := Key;
  512.   if kmControl  in Modifiers then Result := Result or hmControl;
  513.   if kmLControl in Modifiers then Result := Result or hmLControl;
  514.   if kmRControl in Modifiers then Result := Result or hmRControl;
  515.   if kmShift    in Modifiers then Result := Result or hmShift;
  516.   if kmLShift   in Modifiers then Result := Result or hmLShift;
  517.   if kmRShift   in Modifiers then Result := Result or hmRShift;
  518.   if kmAlt      in Modifiers then Result := Result or hmAlt;
  519.   if kmLAlt     in Modifiers then Result := Result or hmLAlt;
  520.   if kmRAlt     in Modifiers then Result := Result or hmRAlt;
  521.   if kmLWin     in Modifiers then Result := Result or hmLWin;
  522.   if kmRWin     in Modifiers then Result := Result or hmRWin;
  523.   if kmWin      in Modifiers then Result := Result or hmWin;
  524. end;
  525. function TController.HotKeyToStr(HotKey: THotkey): string;
  526. begin
  527.   Result := '';
  528.   if HotKey and hmControl  = hmControl  then Result := Result + 'Ctrl + ';
  529.   if HotKey and hmLControl = hmLControl then Result := Result + 'LControl + ';
  530.   if HotKey and hmRControl = hmRControl then Result := Result + 'RControl + ';
  531.   if HotKey and hmShift    = hmShift    then Result := Result + 'Shift + ';
  532.   if HotKey and hmLShift   = hmLShift   then Result := Result + 'LShift + ';
  533.   if HotKey and hmRShift   = hmRShift   then Result := Result + 'RShift + ';
  534.   if HotKey and hmAlt      = hmAlt      then Result := Result + 'Alt + ';
  535.   if HotKey and hmLAlt     = hmLAlt     then Result := Result + 'LAlt + ';
  536.   if HotKey and hmRAlt     = hmRAlt     then Result := Result + 'RAlt + ';
  537.   if HotKey and hmLWin     = hmLWin     then Result := Result + 'LWin + ';
  538.   if HotKey and hmRWin     = hmRWin     then Result := Result + 'RWin + ';
  539.   if HotKey and hmWin      = hmWin      then Result := Result + 'Win  + ';
  540.   if (HotKey and $FFFF <> 0) and not IsModifierKey(HotKey and $FFFF) then
  541.     Result := Result + KeyToStr(HotKey and $FFFF) else
  542.       Result := Result + 'NONE';
  543. end;
  544. function TController.StrToHotKey(const BindStr: TBindingStr): THotkey;
  545. var Cur, LBindings, Terminator: PBinding;
  546. begin
  547.   Result := 0;
  548.   ParseBinding(BindStr, LBindings, Terminator);
  549.   Cur := LBindings;
  550.   while (Cur <> nil) and (Cur^.BindType = btKeyDown) and IsModifierKey(Cur^.BindData) do begin
  551.     if Cur^.BindData = IK_CONTROL  then Result := Result or hmControl;
  552.     if Cur^.BindData = IK_LCONTROL then Result := Result or hmLControl;
  553.     if Cur^.BindData = IK_RCONTROL then Result := Result or hmRControl;
  554.     if Cur^.BindData = IK_ALT  then Result := Result or hmAlt;
  555.     if Cur^.BindData = IK_LALT then Result := Result or hmLAlt;
  556.     if Cur^.BindData = IK_RALT then Result := Result or hmRAlt;
  557.     if Cur^.BindData = IK_SHIFT  then Result := Result or hmShift;
  558.     if Cur^.BindData = IK_LSHIFT then Result := Result or hmLShift;
  559.     if Cur^.BindData = IK_RSHIFT then Result := Result or hmRShift;
  560.     if Cur^.BindData = IK_LWIN then Result := Result or hmLWin;
  561.     if Cur^.BindData = IK_RWIN then Result := Result or hmRWin;
  562.     Cur := Cur^.Next;
  563.   end;
  564. //  if Result := 0 then Exit;                      // No modifiers
  565.   if (Cur <> nil) and not IsModifierKey(Cur^.BindData) and
  566.      ((Cur^.BindType = btKeyDown) or (Cur^.BindType = btKeyClick)) then 
  567.     Result := Result or Cur^.BindData else
  568.       Result := 0;
  569. end;
  570. function TController.GetHotkeyModifiers(HotKey: THotkey): TKeyModifiers;
  571. begin
  572.   Result := [];
  573.   if HotKey and hmControl > 0 then Include(Result, kmControl);
  574.   if HotKey and hmShift   > 0 then Include(Result, kmShift);
  575.   if HotKey and hmAlt     > 0 then Include(Result, kmAlt);
  576.   if HotKey and hmLWin    > 0 then Include(Result, kmLWin);
  577.   if HotKey and hmRWin    > 0 then Include(Result, kmRWin);
  578. end;
  579. procedure TController.BindCommand(const ABinding: string; MsgType: CMessage; const ATimeoutMs: LongWord = DefaultTimeout);
  580. var TB: PBinding;
  581. begin
  582.   Inc(TotalBindings); SetLength(Bindings, TotalBindings);
  583.   with Bindings[TotalBindings - 1] do begin
  584.     ParseBinding(ABinding, First, Terminator);
  585.     Current := First;
  586.     TimeoutMs := ATimeoutMs;
  587.     MessageType := MsgType;
  588.     MembersKind := 0;
  589.   end;
  590.   TB := Bindings[TotalBindings - 1].First;
  591.   while TB <> nil do begin
  592.     case TB^.BindType of
  593.       btKeyDown, btKeyUp, btKeyClick: if (TB^.BindData = IK_MOUSELEFT) or (TB^.BindData = IK_MOUSEMIDDLE) or (TB^.BindData = IK_MOUSERIGHT) then
  594.                                         Bindings[TotalBindings - 1].MembersKind := Bindings[TotalBindings - 1].MembersKind or bmMouseButtons else
  595.                                           Bindings[TotalBindings - 1].MembersKind := Bindings[TotalBindings - 1].MembersKind or bmKeyboard;
  596.       btMouseMove, btMouseHMove, btMouseVMove, btMouseRoll, btStrokeLeft..btStrokeRightDown: Bindings[TotalBindings - 1].MembersKind := Bindings[TotalBindings - 1].MembersKind or bmMouseMotion;
  597.     end;
  598.     TB := TB^.Next;
  599.   end;
  600. end;
  601. procedure TController.BindDelegate(const ABinding: string; Delegate: TInputDelegate; ACustomData: SmallInt; const ATimeoutMs: Longword = 0);
  602. var i, CBIndex: Integer;
  603. begin
  604.   CBIndex := -1;
  605.   for i := 0 to TotalDelegates - 1 do if @Delegates[i] = @Delegate then begin CBIndex := i; Break; end;
  606.   if CBIndex < 0 then begin
  607.     if TotalDelegates >= MaxDelegates then begin
  608.       {$IFDEF LOGGING} Log.Log(ClassName + '.BindDelegate: Too many Delegates', lkError); {$ENDIF}
  609.       Exit;
  610.     end;
  611.     Inc(TotalDelegates); SetLength(Delegates, TotalDelegates);
  612.     CBIndex := TotalDelegates - 1;
  613.     Delegates[CBIndex] := Delegate;
  614.   end;
  615.   Inc(TotalBindings); SetLength(Bindings, TotalBindings);
  616.   with Bindings[TotalBindings - 1] do begin
  617.     ParseBinding(ABinding, First, Terminator);
  618.     Current := First;
  619.     TimeoutMs := ATimeoutMs;
  620.     CustomData := ACustomData;
  621.     DelegateIndex := Word(CBIndex);
  622.     MembersKind := bkDelegate;
  623.   end;
  624. end;
  625. procedure TController.BindPointer(const ABinding: string; const ActionType: Longword; const Data: Pointer; const Value: Word = 0; const ATimeoutMs: LongWord = 0);
  626. var i, PointerIndex: Integer;
  627. begin
  628.   PointerIndex := -1;
  629.   for i := 0 to TotalPointers - 1 do if Pointers[i] = Data then begin PointerIndex := i; Break; end;
  630.   if PointerIndex < 0 then begin
  631.     if TotalPointers >= MaxPointers then begin
  632.       {$IFDEF LOGGING} Log.Log(ClassName + '.BindPointer: Too many pointers', lkError); {$ENDIF}
  633.       Exit;
  634.     end;
  635.     Inc(TotalPointers); SetLength(Pointers, TotalPointers);
  636.     PointerIndex := TotalPointers - 1;
  637.     Pointers[PointerIndex] := Data;
  638.   end;
  639.   Inc(TotalBindings); SetLength(Bindings, TotalBindings);
  640.   with Bindings[TotalBindings - 1] do begin
  641.     ParseBinding(ABinding, First, Terminator);
  642.     Current := First;
  643.     TimeoutMs := ATimeoutMs;
  644.     AType := ActionType;
  645.     Value := Value;
  646.     PTRIndex := Byte(PointerIndex);
  647.     MembersKind := bkPointer;
  648.   end;
  649. end;
  650. procedure TController.UnBind(const Index: Longword);
  651. begin
  652.   Dec(TotalBindings); 
  653.   if Index < TotalBindings then Bindings[Index] := Bindings[TotalBindings];
  654.   SetLength(Bindings, TotalBindings);
  655.   CleanPointers;
  656. end;
  657. procedure TController.CleanPointers;                               //ToFix: Bug here
  658. var i, j: Cardinal; Used: Boolean;
  659. begin
  660.   for j := TotalPointers - 1 downto 0 do begin
  661.     Used := False;
  662.     for i := 0 to TotalBindings - 1 do if (Bindings[i].MembersKind and bkPointer = bkPointer) and (Bindings[i].PTRIndex = j) then begin
  663.       Used := True; Break;
  664.     end;
  665.     if not Used then begin
  666.       Dec(TotalPointers);
  667.       SetLength(Pointers, TotalPointers);
  668.     end;
  669.   end;
  670.   for j := TotalDelegates - 1 downto 0 do begin
  671.     Used := False;
  672.     for i := 0 to TotalBindings - 1 do if (Bindings[i].MembersKind and bkDelegate = bkDelegate) and (Bindings[i].DelegateIndex = j) then begin
  673.       Used := True; Break;
  674.     end;
  675.     if not Used then begin
  676.       Dec(TotalDelegates);
  677.       SetLength(Delegates, TotalDelegates);
  678.     end;
  679.   end;
  680. end;
  681. procedure TController.UnBindAll;
  682. begin
  683.   TotalBindings := 0; TotalDelegates := 0; TotalPointers := 0;
  684.   SetLength(Bindings,  TotalBindings);
  685.   SetLength(Delegates, TotalDelegates);
  686.   SetLength(Pointers,  TotalPointers);
  687. end;
  688. procedure TController.ProcessInput(const EventFilter: TInputFilter);
  689. var
  690.   i: Integer; CurTerm: PBinding;
  691.   EndPass, Terminated: Boolean;
  692.   MouseEvent: TEventType;
  693.   function MatchMouseEvent(Event1, Event2: TEventType): Boolean;
  694.   begin
  695.     Result := False;
  696.     case Event1 of
  697.       btMouseMove:  Result := (Event2 >= btMouseMove) and (Event2 <= btStrokeRightDown);
  698.       btMouseHMove: Result := (Event2  = btMouseHMove) or (Event2  = btStrokeLeft) or (Event2 = btStrokeRight) or (Event2 >= btStrokeLeftUp) and (Event2 <= btStrokeRightDown);
  699.       btMouseVMove: Result := (Event2  = btMouseVMove) or (Event2  = btStrokeUp)   or (Event2 = btStrokeDown)  or (Event2 >= btStrokeLeftUp) and (Event2 <= btStrokeRightDown);
  700.       btStrokeLeft:  Result := (Event2 = btStrokeLeft)  or (Event2 = btStrokeLeftUp)   or (Event2 <= btStrokeLeftDown);
  701.       btStrokeRight: Result := (Event2 = btStrokeRight) or (Event2 = btStrokeRightUp)  or (Event2 <= btStrokeRightDown);
  702.       btStrokeUp:    Result := (Event2 = btStrokeUp)    or (Event2 = btStrokeLeftUp)   or (Event2 <= btStrokeRightUp);
  703.       btStrokeDown:  Result := (Event2 = btStrokeDown)  or (Event2 = btStrokeLeftDown) or (Event2 <= btStrokeRightDown);
  704.       btStrokeLeftUp, btStrokeRightUp, btStrokeLeftDown, btStrokeRightDown, btMouseRoll: Result := (Event2 = Event1);
  705.     end;
  706.   end;
  707.   procedure MatchBinding(BData: Integer);
  708.   var Msg: TMessage;
  709.   begin
  710.     with Bindings[i], Current^ do begin
  711.       if Next = nil then begin
  712.         if MembersKind and bkPointer = bkPointer then case AType of    // Pointer
  713.           atBooleanOn:     Boolean(Pointers[PTRIndex]^) := True;
  714.           atBooleanOff:    Boolean(Pointers[PTRIndex]^) := False;
  715.           atBooleanToggle: Boolean(Pointers[PTRIndex]^) := not Boolean(Pointers[PTRIndex]^);
  716.           atSetByte: Byte(Pointers[PTRIndex]^) := Value;
  717.           atSetWord: Word(Pointers[PTRIndex]^) := Value;
  718.           atSetLongWord: LongWord(Pointers[PTRIndex]^) := Value;
  719.         end else if MembersKind and bkDelegate = bkDelegate then begin   // Delegate
  720.           Delegates[DelegateIndex](BData, CustomData);
  721.         end else begin                                                   // Message
  722.           Msg := MessageType.Create;
  723.           Msg.Flags := Msg.Flags + [mfCore];
  724.           if Msg is TKeyboardMsg then
  725.             TKeyboardMsg(Msg).Create(BData) else
  726.               if Msg is TMouseMsg then
  727.                 TMouseMsg(Msg).Create(MouseX, MouseY);
  728.           if Assigned(MessageHandler) then MessageHandler(Msg);
  729.         end;
  730.         Current := First;
  731.       end else begin
  732.         Current := Next;
  733.         EndPass := False;
  734.       end;
  735.       LastMs := CurrentMs;
  736.     end;
  737.   end;
  738.   function IsKeyWasPressed(Key: Integer): Boolean;
  739.   begin
  740.     Result := (KeyboardState[Key] >= 128) and (LastKeyState[Key] <  128);
  741.   end;
  742.   function IsKeyWasReleased(Key: Integer): Boolean;
  743.   begin
  744.     Result := (KeyboardState[Key] < 128) and (LastKeyState[Key] >= 128);
  745.   end;
  746. begin
  747. //  if not Active then Exit;
  748.   CurrentMs := GetCurrentMs;
  749.   Move(KeyboardState[0], LastKeyState[0], 256);
  750.   GetInputState;
  751.   if ifNotBound in EventFilter then begin                      //
  752.     if iqrMouseMoved in QueryInput then MouseEvent := InputEvents[0].EventType else MouseEvent := btNone;
  753.   end else MouseEvent := GetMouseEvent;
  754. //  if MouseEvent = LastMouseEvent then MouseEvent := btNone;
  755.   if ifBound in EventFilter then for i := 0 to TotalBindings - 1 do with Bindings[i] do if First <> nil then begin
  756.     EndPass := False; Terminated := False;
  757.     while not EndPass do begin
  758.       EndPass := True;
  759.       if Current <> First then begin
  760.         if (TimeoutMs > 0) then begin                                   // Handle timeout
  761.           if CurrentMs - LastMs > TimeoutMs then begin
  762.             Current := First;
  763.           end;
  764.         end;
  765.         CurTerm := Terminator;
  766.         while (CurTerm <> nil) and (Current <> First) do begin
  767.           with CurTerm^ do case BindType of
  768.             btKeyDown: if  IsKeyWasPressed(BindData) then Break;//Current := First;
  769.             btKeyUp:   if IsKeyWasReleased(BindData) then Break;//Current := First;
  770.             btMouseMove..btStrokeRightDown: if MatchMouseEvent(CurTerm^.BindType, MouseEvent) then Break;
  771.           end;
  772.           CurTerm := CurTerm^.Next;
  773.         end;
  774.         Terminated := CurTerm <> nil;
  775.       end;
  776.       if Terminated then begin                                        // Reset sequence if terminated
  777.         Current := First;
  778.       end else with Current^ do begin                                 // Else go on
  779.         case BindType of
  780.           btKeyDown: if IsKeyWasPressed(BindData)  then MatchBinding(BindData);
  781.           btKeyUp:   if IsKeyWasReleased(BindData) then MatchBinding(BindData);
  782.           btMouseMove:  if (MouseState.lX <> 0) or (MouseState.lY <> 0) then MatchBinding((MouseState.lY) shl 16 + (MouseState.lX));
  783.           btMouseHMove: if  MouseState.lX <> 0 then MatchBinding(MouseState.lX);
  784.           btMouseVMove: if  MouseState.lY <> 0 then MatchBinding(MouseState.lY);
  785.           btMouseRoll:  if  MouseState.lZ <> 0 then MatchBinding(MouseState.lZ);
  786.         end;
  787.         if BindType = MouseEvent then MatchBinding(0);
  788.       end;
  789.     end;
  790.   end else begin
  791.     MouseXCounter := 0; MouseYCounter := 0;
  792.   end;
  793. end;
  794. procedure TController.SetMouseWindow(const X1, Y1, X2, Y2: Integer);
  795. begin
  796. //  if not Active then Exit;
  797.   MouseWindow.Left := X1; MouseWindow.Top := Y1;
  798.   MouseWindow.Right := X2; MouseWindow.Bottom := Y2;
  799. end;
  800. procedure TController.InputEventsToMessages;
  801. var i: Integer; CurMs: Cardinal; MouseEvent: Boolean;
  802. begin
  803.   CurMs := GetCurrentMs;
  804.   for i := 0 to TotalEvents-1 do begin
  805.     case InputEvents[i].EventType of
  806.       btKeyDown, btKeyUp: begin
  807.         MouseEvent := (InputEvents[i].EventData = IK_MOUSELEFT) or (InputEvents[i].EventData = IK_MOUSEMIDDLE) or (InputEvents[i].EventData = IK_MOUSERIGHT);
  808.         if (InputEvents[i].EventType = btKeyDown) then begin
  809.           if MouseEvent then
  810.             MessageHandler(TMouseDownMsg.Create(MouseX, MouseY, InputEvents[i].EventData)) else
  811.               MessageHandler(TKeyDownMsg.Create(InputEvents[i].EventData));
  812.           KeyQueryState[InputEvents[i].EventData].State := kqsDown;
  813.         end else begin
  814.           if MouseEvent then
  815.             MessageHandler(TMouseUpMsg.Create(MouseX, MouseY, InputEvents[i].EventData)) else
  816.               MessageHandler(TKeyUpMsg.Create(InputEvents[i].EventData));
  817.           if KeyQueryState[InputEvents[i].EventData].State = kqsDown then begin
  818.             if MouseEvent then
  819.               MessageHandler(TMouseClickMsg.Create(MouseX, MouseY, InputEvents[i].EventData)) else
  820.                 MessageHandler(TKeyClickMsg.Create(InputEvents[i].EventData));
  821.             if (CurMs - KeyQueryState[InputEvents[i].EventData].LastClickedTime < DblClickTimeout) then begin
  822.               if MouseEvent then
  823.                 MessageHandler(TMouseDblClickMsg.Create(MouseX, MouseY, InputEvents[i].EventData)) else
  824.                   MessageHandler(TKeyDblClickMsg.Create(InputEvents[i].EventData));
  825.               KeyQueryState[InputEvents[i].EventData].LastClickedTime := 0;
  826.             end else KeyQueryState[InputEvents[i].EventData].LastClickedTime := CurMs;
  827.           end;
  828.           KeyQueryState[InputEvents[i].EventData].State := kqsUp;
  829.         end;
  830.       end;
  831.       btMouseMove, btMouseHMove, btMouseVMove, btMouseRoll, btStrokeLeft..btStrokeRightDown: begin
  832.         MessageHandler(TMouseMoveMsg.Create(MouseX, MouseY));
  833. {        case InputEvents[i].EventType of
  834.           btMouseMove, btMouseHMove, btMouseVMove,
  835.           btMouseRoll,
  836.           btStrokeLeft, btStrokeRight, btStrokeUp, btStrokeDown,
  837.           btStrokeLeftUp, btStrokeRightUp, btStrokeLeftDown, btStrokeRightDow
  838.         end;}
  839.       end;
  840.     end;
  841.   end;
  842. end;
  843. procedure TController.HandleMessage(const Msg: TMessage);
  844. begin
  845.   if Msg = nil then Exit;
  846.   if (Msg.ClassType = TWindowResizeMsg) or (Msg.ClassType = TWindowMoveMsg) then begin
  847.     RecalcCaptureCoords;
  848.     if FMouseCapture then ApplyMouseCapture(True);
  849.   end else if Msg.ClassType = TWindowActivateMsg then begin
  850.     ProcessInput([]);
  851.     SetSystemCursor(FSystemCursor);
  852.     if FMouseCapture then ApplyMouseCapture(True);
  853.   end else if (Msg.ClassType = TWindowDeactivateMsg) or
  854.               (Msg.ClassType = TWindowMinimizeMsg)   then begin
  855.     if FMouseCapture then ApplyMouseCapture(False);
  856.   end else if (EnableCharactersInput) and (Msg.ClassType = TCharInputMsg) then with TCharInputMsg(Msg) do
  857.     if Character = #8 then if FInputBuffer <> '' then FInputBuffer := Copy(FInputBuffer, 0, Length(FInputBuffer)-1) else
  858.       if Character = ' ' then FInputBuffer := FInputBuffer + Character;
  859. end;
  860. procedure TController.ParseBinding(s: string; out Binding, Terminator: PBinding);
  861. const
  862.   Specifiers = ',+-:^';
  863.   GestureSpecifier = 4;
  864.   ModifiedBindTypes: array[0..3] of TEventType = (btKeyClick, btKeyDown, btKeyUp, btKeyDblClick);
  865.   GestureNames: array[0..11] of string[20] = ('MouseMove', 'MouseMoveH', 'MouseMoveV', 'MouseRoll',
  866.                                               'MouseStrokeLeft', 'MouseStrokeRight', 'MouseStrokeUp', 'MouseStrokeDown',
  867.                                               'MouseStrokeLeftUp', 'MouseStrokeRightUp', 'MouseStrokeLeftDown', 'MouseStrokeRightDown');
  868.   GestureTypes: array[0..11] of TEventType = (btMouseMove, btMouseHMove, btMouseVMove, btMouseRoll,
  869.                                               btStrokeLeft, btStrokeRight, btStrokeUp, btStrokeDown,
  870.                                               btStrokeLeftUp, btStrokeRightUp, btStrokeLeftDown, btStrokeRightDown);
  871. (*  BindElement = (<Key><Specifier>)|<Gesture>"^"
  872.     Key = Key name
  873.     Specifier = ","|"+"|"-"|":" - can be omitted at the end ("," will be assumed)
  874.     Gesture             = "MouseMove"|"MouseMoveH"|"MouseMoveV"|"MouseRoll"|
  875.                           "MouseStrokeLeft"|"MouseStrokeRight"|"MouseStrokeUp"|"MouseStrokeDown"|
  876.                           "MouseStrokeLeftUp"|"MouseStrokeRightUp"|"MouseStrokeLeftDown"|"MouseStrokeRightDown"
  877.     Binding = <BindElement> {<BindElement>}
  878.     "," - key click event
  879.     "+" - key down event
  880.     "-" - key up event
  881.     ":" - key double click *)
  882. var Specifier, CurPos, Len, Key: Integer; Current: PBinding;
  883.   function GetGestureType(const AName: string): TEventType;
  884.   var i: Integer;
  885.   begin
  886.     Result := btNone;
  887.     i := High(GestureNames);
  888.     while (i >= 0) and (AName <> GestureNames[i]) do Dec(i);
  889.     if i >= 0 then Result := GestureTypes[i];
  890.   end;
  891.   function ParseElement: PBinding;
  892.   var Name: TBindingName; BindType: TEventType;
  893.   begin
  894.     Result := nil;
  895.     Name := '';
  896.     while (CurPos <= Len) and (Pos(s[CurPos], Specifiers) = 0) do begin          // Scan name
  897.       Name := Name + s[CurPos];
  898.       Inc(CurPos);
  899.     end;
  900.     
  901.     if CurPos > Len then Specifier := 0 else begin                       // end of string - click specifier, else
  902. {      if (CurPos = Len) and (Pos(s[CurPos], Modifiers) > 0) and (TrimSpaces(Name) = '') then begin   // a specifier at the end is key name if Name is empty
  903.         Name := Name + s[CurPos];
  904.         Modifier := 0
  905.         Inc(CurPos);
  906.       end else}
  907.       if (CurPos+1 <= Len) and (Pos(s[CurPos+1], Specifiers) > 0) then begin     // if doubled modifier add the first one to name
  908.         Name := Name + s[CurPos];
  909.         Inc(CurPos);
  910.       end;
  911.       Specifier := Pos(s[CurPos], Specifiers)-1;
  912.       Inc(CurPos);
  913.     end;
  914.     Name := TrimSpaces(Name);
  915.     if Name = '' then Exit;
  916.     if Specifier = GestureSpecifier then begin
  917.       Key := 0;
  918.       BindType := GetGestureType(Name);
  919.       if BindType = btNone then begin
  920.         Log.Log(ClassName + '.ParseBinding: Invalid gesture name: "' + Name + '"', lkError);
  921.         Exit;
  922.       end;
  923.     end else begin
  924.       Key := StrToKey(Name);
  925.       if Key = -1 then begin
  926.         Log.Log(ClassName + '.ParseBinding: Invalid key name: "' + Name + '"', lkError);
  927.         Exit;
  928.       end;
  929.       BindType := ModifiedBindTypes[Specifier];
  930.     end;
  931.     Result := NewBinding(BindType, Key);
  932.     {$IFDEF DEBUGMODE} {$IFDEF LOGGING}
  933. {    case ModifiedBindTypes[Modifier] of
  934.       btKeyClick:    Log.Log(' "' + Name + '" click');
  935.       btKeyDown:     Log.Log(' "' + Name + '" down');
  936.       btKeyUp:       Log.Log(' "' + Name + '" up');
  937.       btKeyDblClick: Log.Log(' "' + Name + '" double click');
  938.     end;}
  939.     {$ENDIF} {$ENDIF}
  940.   end;
  941. begin
  942.   {$IFDEF DEBUGMODE} {$IFDEF LOGGING}
  943. //  Log.Log('Parsing binding...', lkTitle);
  944.   {$ENDIF} {$ENDIF}
  945.   Binding := nil;
  946.   if s = '' then Exit;
  947.   if Pos(s[Length(s)], Specifiers) = 0 then s := s + ',';
  948.   Len := Length(s);
  949.   CurPos := 1;
  950.   Binding := ParseElement;
  951.   Current := Binding;
  952.   while (CurPos <= Len) and (Current <> nil) do begin
  953.     Current^.Next := ParseElement;
  954.     Current := GetLastBinding(Current^.Next);
  955.   end;
  956. end;
  957. end.