{                                                                              }
{  IB_KeyNavigator}
{                                                                              }

{***************************************************************}
{                                                               }
{          IB Objects Components and Resources Library          }
{                                                               }
{          Copyright (C) 1996-2002 Jason Wharton                }
{          and Computer Programming Solutions, Mesa, AZ         }
{                                                               }
{  This source code unit is released under the terms of the     }
{  CPS Trustware License.                                       }
{  It may be distributed or deployed as source code or in       }
{  compiled form only in compliance with the terms and          }
{  conditions of the CPS Trustware License which may be         )
{  examined at http://www.ibobjects.com/ibo_trustware.html      )
{                                                               }
{***************************************************************}

{******************************************************************************}
{  CONTRIBUTED MODIFICATIONS                                                   }
{  Additions or modifications listed below using format:                       }
{                                                                              }
{  Joe Citizen <joe@joecitizen.com>                                            }
{  mm/dd/yyyy                                                                  }
{     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                 }
{     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                 }
{     xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx                 }
{            Copyright (C) 2001 Joe Citizen                                    }
{                                                                              }
{------------------------------------------------------------------------------}
{  Place your credits and information in the top of the section                }
{  below with any other descriptions or explanations desired.                  }
{------------------------------------------------------------------------------}
{                                                                              }
{  Carlos Panizza <capanizza@ig.com.br>                                        }
{  10-Jul-2002                                                                 }
{     This component permits:                                                  }
{     1.  the use of [ENTER] key (and/or Up/Down and/or Left/Right) almost     }
{         like [TAB] key in data entry forms or grids;                         }
{     2.  the navigation on the rows of a table using the PageUp and PageDown  }
{         keys;                                                                }
{     3.  the insertion and deletion of rows on a table using the keys         }
{         Ctrl[Insert] and Ctrl[Delete] keys;                                  }
{                                                                              }
{     4.   the cancel of edits in a row of a table with the Ctrl[Break] key.   }
{                                                                              }
{******************************************************************************}

{$INCLUDE IB_Directives.inc}

unit IB_KeyNav;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
  IB_Grid, IB_Controls, IB_Components, Dialogs;

type
  EIB_KeyNavigator = class(Exception);

  TIB_KeyNavigator = class(TComponent)
  private
    FActive: Boolean;
    FHandle: HHook;
    FLeftRightClasses: string;
    FUpDownClasses: string;
    FEnterClasses: string;
    FGridClasses: string;
    FDatasetClasses: string;
    procedure SetActive(const Value: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Handle: HHook read FHandle;
  published
    property Active: Boolean read FActive write SetActive default True;
    property DatasetClasses: string read FDatasetClasses write FDatasetClasses;
    property EnterClasses: string read FEnterClasses write FEnterClasses;
    property GridClasses: string read FGridClasses write FGridClasses;
    property LeftRightClasses: string read FLeftRightClasses
      write FLeftRightClasses;
    property UpDownClasses: string read FUpDownClasses write FUpDownClasses;
  end;

procedure FocusNextControl(GoForward: Boolean);
procedure ProcessGrid(IB_Grid: TIB_Grid);
procedure ProcessDataset(Key: Word);
function IsOfClasses(AObject: TObject; const AClasses: string): Boolean;

procedure Register;

implementation

//{$R KeyNav.res}

procedure Register;
begin
  RegisterComponents('Bufi', [TIB_KeyNavigator]);
end;

{ TIB_KeyNavigator }

var
  IB_KeyNavigator: TIB_KeyNavigator;

constructor TIB_KeyNavigator.Create(AOwner: TComponent);
begin
  if Assigned(IB_KeyNavigator) then
    raise EIB_KeyNavigator.Create('Only one instance of TIB_KeyNavigator per application allowed');
  inherited Create(AOwner);
  IB_KeyNavigator := Self;
  Active := True;
  FEnterClasses := 'TIB_CustomEdit';
  FGridClasses := 'TIB_Grid';
  FDatasetClasses := 'TIB_CustomEdit';
end;

destructor TIB_KeyNavigator.Destroy;
begin
  Active := False;
  IB_KeyNavigator := nil;
  inherited Destroy;
end;

procedure FocusNextControl(GoForward: Boolean);
var
  Form: TCustomForm;
  Keyboard: TKeyboardState;
  OldShift: Byte;
begin
  Form := Screen.ActiveCustomForm;
  if Assigned(Form) then
  begin
    GetKeyboardState(Keyboard);
    OldShift := Keyboard[VK_SHIFT];
    if not GoForward then
      Keyboard[VK_SHIFT] := not Keyboard[VK_SHIFT];
    SetKeyboardState(Keyboard);
    SendMessage(Form.Handle, CM_DIALOGKEY, VK_TAB, 0);
    Keyboard[VK_SHIFT] := OldShift;
    SetKeyboardState(Keyboard);
  end;
end;

{
procedure ProcessGrid(IB_Grid: TIB_Grid);
begin
  if Screen.ActiveControl is TIB_Grid then
  begin
    IB_Grid := Screen.ActiveControl as TIB_Grid;
    if HiWord(GetKeyState(VK_SHIFT)) <> 0 then
    begin
      if IB_Grid.Col = 1 then
      begin
        IB_Grid.MoveCol(IB_Grid.LastTabCol, 0);
        IB_Grid.Datasource.Dataset.Prior;
        if IB_Grid.Datasource.Dataset.Bof then
          IB_Grid.Datasource.Dataset.First;
      end
      else
        IB_Grid.MoveCol(IB_Grid.Col-1,-1);
    end
    else
    begin
      if IB_Grid.Col = IB_Grid.LastTabCol then
      begin
        IB_Grid.MoveCol(0, 0);
        IB_Grid.Datasource.Dataset.Next;
        if IB_Grid.Datasource.Dataset.Eof then
          IB_Grid.Datasource.Dataset.Append;//Last;
      end
      else
        IB_Grid.MoveCol(IB_Grid.Col+1,1);
    end;
  end;
end;
}

procedure ProcessGrid(IB_Grid: TIB_Grid);
begin
  if HiWord(GetKeyState(VK_SHIFT)) <> 0 then
  begin
    if IB_Grid.Col = 1 then
    begin
      IB_Grid.MoveCol(IB_Grid.LastTabCol, 0);
      IB_Grid.Datasource.Dataset.Prior;
      if IB_Grid.Datasource.Dataset.Bof then
        IB_Grid.Datasource.Dataset.First;
    end
    else
      IB_Grid.MoveCol(IB_Grid.Col-1,-1);
  end
  else
  begin
    if IB_Grid.Col = IB_Grid.LastTabCol then
    begin
      IB_Grid.MoveCol(0, 0);
      if (IB_Grid.Datasource.Dataset.RecNo = IB_Grid.Datasource.Dataset.RecordCount) then
      begin
        if IB_Grid.Datasource.AutoInsert then
          IB_Grid.Datasource.Dataset.Next
        else
          IB_Grid.Datasource.Dataset.Last;
      end
      else
        IB_Grid.Datasource.Dataset.Next;

      //We should cosider id Datasource.Autoinsert alows inserting directly in grid

      if IB_Grid.Datasource.Dataset.Eof then
      begin
        if IB_Grid.Datasource.Dataset.CanInsert then
          IB_Grid.Datasource.Dataset.Insert
        else
          IB_Grid.Datasource.Dataset.Last;
      end
    end
    else
      IB_Grid.MoveCol(IB_Grid.Col+1,1);
  end;
end;

procedure ProcessDataset(Key: Word);
var Ctrl:       Boolean;
    IB_Dataset: TIB_Dataset;
begin
  Ctrl  := (HiWord(GetKeyState(VK_CONTROL)) <> 0);
  if Screen.ActiveControl is TIB_CustomEdit then
  begin
    IB_Dataset := TIB_CustomEdit(Screen.ActiveControl).Datasource.Dataset;
    if IB_Dataset <> nil then
      case Key of
        VK_NEXT   : if Ctrl then
                      IB_DataSet.Last
                    else
                    begin
                      IB_DataSet.Next;
                      if IB_DataSet.Eof then
                        IB_DataSet.Last;
                    end;
        VK_PRIOR  : if Ctrl then
                      IB_DataSet.First
                    else
                    begin
                      IB_DataSet.Prior;
                      if IB_DataSet.Bof then
                        IB_DataSet.First;
                    end;
        VK_INSERT : if Ctrl then
                      IB_DataSet.Append;
        VK_DELETE : if Ctrl then
                      IB_DataSet.Delete;
        VK_CANCEL : IB_DataSet.Cancel;
      end;
  end;
end;

function IsOfClasses(AObject: TObject; const AClasses: string): Boolean;
var
  AClassName: string;

  function IsOfClass: Boolean;
  var
    AClass: TClass;
  begin
    AClass := AObject.ClassType;
    repeat
      Result := AClass.ClassName = AClassName;
      AClass := AClass.ClassParent;
    until Result or (AClass = nil);
  end;

var
  StartPos, EndPos, Len: Integer;
begin
  Len := Length(AClasses);
  StartPos := 1;
  EndPos := 1;
  repeat
    while (EndPos <= Len) and (AClasses[EndPos] <> ';') do
      Inc(EndPos);
    AClassName := Trim(Copy(AClasses, StartPos, EndPos - StartPos));
    if IsOfClass then
    begin
      Result := True;
      Exit;
    end;
    if (EndPos <= Len) and (AClasses[EndPos] = ';') then
      Inc(EndPos);
    StartPos := EndPos;
  until (StartPos > Len);
  Result := False;
end;

function KeyboardHook(code: Integer; wParam: WPARAM;
  lParam: LPARAM): LRESULT; stdcall;
var
  Control: TWinControl;
  s:string;
begin
  if (code < 0) then
    Result := CallNextHookEx(IB_KeyNavigator.Handle, code, wParam, lParam)
  else
  begin
    Control := Screen.ActiveCustomForm.ActiveControl;
    s := Screen.ActiveCustomForm.Caption;
    if (lParam and $80000000 = 0) and Assigned(Control) then
    begin
      //to here

      if (IsOfClasses(Control, IB_KeyNavigator.FLeftRightClasses) and
         ((wParam = VK_LEFT) or (wParam = VK_RIGHT))) or
         (IsOfClasses(Control, IB_KeyNavigator.FUpDownClasses) and
         ((wParam = VK_UP) or (wParam = VK_DOWN))) or
         (IsOfClasses(Control, IB_KeyNavigator.FEnterClasses) and
         (wParam = VK_RETURN) and (HiWord(GetKeyState(VK_CONTROL)) = 0)) then
      begin
      if (Control is TIB_LookupCombo) and (Control.Parent is TIB_Grid) then
//        if (Control.Parent is TIB_Grid) then
          ProcessGrid(TIB_Grid(Control.Parent))
        else
          PostMessage( Screen.ActiveCustomForm.Handle, WM_NEXTDLGCTL, 0, 0 );
          //FocusNextControl((wParam = VK_DOWN) or (wParam = VK_RIGHT) or
          //                 (wParam = VK_RETURN));
        Result := 1;
      end
      else
        Result := 0;
      //From here
      if (IsOfClasses(Control, IB_KeyNavigator.FGridClasses) and
         ((wParam = VK_RETURN))) then
      begin
        ProcessGrid(TIB_Grid(Control));
        Result := 1;
      end;

      if (IsOfClasses(Control, IB_KeyNavigator.FDatasetClasses) and
         ((wParam = VK_NEXT)   or (wParam = VK_PRIOR) or
          (wParam = VK_INSERT) or (wParam = VK_DELETE) or
          (wParam = VK_CANCEL))) then
      begin
        ProcessDataset(wParam);
        Result := 1;
      end;
    end
    else
      Result := 0;
  end;
end;

procedure TIB_KeyNavigator.SetActive(const Value: Boolean);
begin
  if (Value <> FActive) then
  begin
    FActive := Value;
    if not (csDesigning in ComponentState) then
      if FActive then
      begin
        FHandle := SetWindowsHookEx(WH_KEYBOARD, KeyboardHook, 0,
          GetCurrentThreadID);
        if (FHandle = 0) then
          raise EIB_KeyNavigator.Create('Keyboard navigator cannot be activated.');
      end
      else
        if UnhookWindowsHookEx(FHandle) then
          FHandle := 0
        else
          raise EIB_KeyNavigator.Create('Keyboard navigator cannot be deactivated.');
  end;
end;

end.
