unit uAccessibleStringGrid;

interface

uses
  ComObj, ActiveX, AxCtrls, Classes, CPRSChart_TLB, StdVcl, Accessibility_TLB,
  ORCtrls, Variants;

type
  TChildType = (ctInvalid, ctNoChild, ctChild);

  TAccessibleStringGrid = class(TAutoObject, IAccessibleStringGrid, IAccessible)
  private
    FDefaultObject: IAccessible;
    FDefaultObjectLoaded: boolean;
    FControl: TCaptionStringGrid;
    function GetDefaultObject: IAccessible;
  protected {IAccessible}
    function accHitTest(xLeft, yTop: Integer): OleVariant; safecall;
    function accNavigate(navDir: Integer; varStart: OleVariant): OleVariant;
      safecall;
    function Get_accChild(varChild: OleVariant): IDispatch; safecall;
    function Get_accChildCount: Integer; safecall;
    function Get_accDefaultAction(varChild: OleVariant): WideString; safecall;
    function Get_accDescription(varChild: OleVariant): WideString; safecall;
    function Get_accFocus: OleVariant; safecall;
    function Get_accHelp(varChild: OleVariant): WideString; safecall;
    function Get_accHelpTopic(out pszHelpFile: WideString;
      varChild: OleVariant): Integer; safecall;
    function Get_accKeyboardShortcut(varChild: OleVariant): WideString;
      safecall;
    function Get_accName(varChild: OleVariant): WideString; safecall;
    function Get_accParent: IDispatch; safecall;
    function Get_accRole(varChild: OleVariant): OleVariant; safecall;
    function Get_accSelection: OleVariant; safecall;
    function Get_accState(varChild: OleVariant): OleVariant; safecall;
    function Get_accValue(varChild: OleVariant): WideString; safecall;
    procedure accDoDefaultAction(varChild: OleVariant); safecall;
    procedure accLocation(out pxLeft, pyTop, pcxWidth, pcyHeight: Integer;
      varChild: OleVariant); safecall;
    procedure accSelect(flagsSelect: Integer; varChild: OleVariant); safecall;
    procedure Set_accName(varChild: OleVariant; const pszName: WideString);
      safecall;
    procedure Set_accValue(varChild: OleVariant; const pszValue: WideString);
      safecall;
  protected
    property DefaultObject: IAccessible read GetDefaultObject write FDefaultObject;
  public
    property Control: TCaptionStringGrid read FControl write FControl;
    function ChildType( varChild: OleVariant): TChildType;
    class procedure WrapControl( Control: TCaptionStringGrid);
    class procedure UnwrapControl( Control: TCaptionStringGrid);
  public {but it wouldn't be in a perfect world}
    function SafeCallException(ExceptObject: TObject; ExceptAddr: Pointer): HResult; override;
  end;

implementation

uses uComServ, SysUtils, uAccessAPI, Windows, Controls;

var
  UserIsRestricted: boolean = False;

function TAccessibleStringGrid.accHitTest(xLeft,
  yTop: Integer): OleVariant;
var
  ACol: integer;
  ARow: integer;
  P: TPoint;
begin
  result := Null;
  if Assigned(FControl) then
  begin
    P.X := xLeft;
    P.Y := yTop;
    P := FControl.ScreenToClient(P);
    FControl.MouseToCell(P.X, P.Y, ACol, ARow);
    if (ACol = -1) or (ARow = -1) then
      result := NULL
    else
      result := FControl.ColRowToIndex( ACol, ARow);
  end
  else
    result := CHILDID_SELF;
end;

function TAccessibleStringGrid.accNavigate(navDir: Integer;
  varStart: OleVariant): OleVariant;
begin
  result := Null;
  if Assigned(FControl) then
  begin
  case ChildType(varStart) of
    ctNoChild:
      case navDir of
        NAVDIR_FIRSTCHILD:
          result := 1;
        NAVDIR_LASTCHILD:
          result := Get_AccChildCount;
        NAVDIR_DOWN,
        NAVDIR_LEFT,
        NAVDIR_NEXT,
        NAVDIR_PREVIOUS,
        NAVDIR_RIGHT,
        NAVDIR_UP:
          result := varStart;
      end;
    ctChild:
      case NavDir of
        NAVDIR_FIRSTCHILD,
        NAVDIR_LASTCHILD:
          result := varStart;
        NAVDIR_DOWN:
          result := varStart + (FControl.ColCount - FControl.FixedCols);
        NAVDIR_LEFT,
        NAVDIR_NEXT:
          result := varStart + 1;
        NAVDIR_PREVIOUS,
        NAVDIR_RIGHT:
          result := varStart - 1;
        NAVDIR_UP:
          result := varStart - (FControl.ColCount - FControl.FixedCols);
      end;
    end;
    //revert if index is invalid
    if ChildType(result) = ctChild then
    begin
      if (result > Get_AccChildCount) or (result < 1) then
        result := varStart;
    end;
  end;
end;

function TAccessibleStringGrid.Get_accChild(
  varChild: OleVariant): IDispatch;
begin
  result := nil;
  OleError(S_FALSE);
end;

function TAccessibleStringGrid.Get_accChildCount: Integer;
begin
  if Assigned(FControl) then
    result := (FControl.RowCount - FControl.FixedRows) * (FControl.ColCount - FControl.FixedCols)
  else
    result := 0;
end;

function TAccessibleStringGrid.Get_accDefaultAction(
  varChild: OleVariant): WideString;
begin
  result := '';
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accDefaultAction(varChild);
end;

function TAccessibleStringGrid.Get_accDescription(
  varChild: OleVariant): WideString;
begin
  result := '';
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accDescription(varChild);
end;

function TAccessibleStringGrid.Get_accFocus: OleVariant;
begin
  if Assigned(FControl) and FControl.Focused then
    result := FControl.ColRowToIndex(FControl.Col, FControl.Row)
  else
    result := NULL;
end;

function TAccessibleStringGrid.Get_accHelp(
  varChild: OleVariant): WideString;
begin
  result := '';
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accHelp(varChild);
end;

function TAccessibleStringGrid.Get_accHelpTopic(
  out pszHelpFile: WideString; varChild: OleVariant): Integer;
begin
  result := 0;
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accHelpTopic(pszHelpFile, varChild);
end;

function TAccessibleStringGrid.Get_accKeyboardShortcut(
  varChild: OleVariant): WideString;
begin
  result := '';
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accKeyboardShortcut(varChild);
end;

function TAccessibleStringGrid.Get_accName(
  varChild: OleVariant): WideString;
var
  Row,Col: integer;
  ColumnName: string;
  RowName: string;
begin
  case ChildType(varChild) of
    ctNoChild:
      result := FControl.Caption;
    ctChild:
    begin
      if Assigned(FControl) then
      begin
        FControl.IndexToColRow(varChild,Col,Row);
        if (FControl.FixedRows = 1) and (FControl.Cells[Col,0] <> '') then
          ColumnName := FControl.Cells[Col,0]
        else
          ColumnName := IntToStr(Col-FControl.FixedCols+1);
        if (FControl.FixedCols = 1) and ((FControl.Cells[0,Row] <> '')) then
          RowName := FControl.Cells[0,Row]
        else
          RowName := IntToStr(Row-FControl.FixedRows+1) + ' of ' +
            IntToStr(FControl.RowCount - FControl.FixedRows);
        result := 'Column ' + ColumnName + ', Row ' + RowName;
      end
      else
        result := 'Unknown Property';
    end;
    else
      result := 'Unknown Property';
  end;
end;

function TAccessibleStringGrid.Get_accParent: IDispatch;
begin
  result := nil;
  if Assigned(DefaultObject) then
    result := DefaultObject.Get_accParent;
end;

function TAccessibleStringGrid.Get_accRole(
  varChild: OleVariant): OleVariant;
begin
  case ChildType(varChild) of
    ctNoChild:
      result := ROLE_SYSTEM_LIST;
    ctChild:
      result := ROLE_SYSTEM_LISTITEM;
    else
      result := ROLE_SYSTEM_CLIENT;
  end;
end;

function TAccessibleStringGrid.Get_accSelection: OleVariant;
begin
  //We are assuming single-selection for this control
  if Assigned(FControl) then
    result := FControl.ColRowToIndex(FControl.Col, FControl.Row)
  else
    result := NULL;
end;

function TAccessibleStringGrid.Get_accState(
  varChild: OleVariant): OleVariant;
begin
  if Assigned(FControl) then
  begin
    result := STATE_SYSTEM_FOCUSABLE  or STATE_SYSTEM_READONLY or STATE_SYSTEM_SELECTABLE;
    case ChildType(varChild) of
      ctNoChild:
        if FControl.Focused then
          result := result or STATE_SYSTEM_FOCUSED;
      ctChild:
      begin
        if FControl.ColRowToIndex(FControl.Col, FControl.Row) = varChild then
        begin
          result := result or STATE_SYSTEM_SELECTED;
          if FControl.Focused then
            result := result or STATE_SYSTEM_FOCUSED;
        end;
      end;
    end;
    if ([csCreating,csDestroyingHandle] * FControl.ControlState <> []) or
      ([csDestroying,csFreeNotification,csLoading,csWriting] * FControl.ComponentState	<> []) then
      result := result or STATE_SYSTEM_UNAVAILABLE;
  end
  else
    result := STATE_SYSTEM_UNAVAILABLE;
end;

function TAccessibleStringGrid.Get_accValue(
  varChild: OleVariant): WideString;
var
  Row,Col: integer;
begin
  case ChildType(varChild) of
    ctNoChild:
      result := '';
    ctChild:
    begin
      if Assigned(FControl) then
      begin
        FControl.IndexToColRow(varChild,Col,Row);
        result := FControl.Cells[Col,Row];
        if FControl.JustToTab then
          result := Copy(result, 1, pos(#9{Tab},Result) -1);
      end
      else
        result := 'Unknown Property';
    end;
    else
      result := 'Unknown Property';
  end;
end;

procedure TAccessibleStringGrid.accDoDefaultAction(varChild: OleVariant);
begin
  if Assigned(DefaultObject) then
    DefaultObject.accDoDefaultAction(varChild);
end;

procedure TAccessibleStringGrid.accLocation(out pxLeft, pyTop, pcxWidth,
  pcyHeight: Integer; varChild: OleVariant);
var
  P: TPoint;
  R: TRect;
begin
  if Assigned(FControl) then
  begin
    case ChildType(varChild) of
      ctNoChild:
      begin
        P.X := 0;
        P.Y := 0;
        with FControl.ClientToScreen(P) do begin
          pxLeft := X;
          pyTop := Y;
        end;
        pcxWidth := FControl.Width;
        pcyHeight := FControl.Height;
      end;
      ctChild:
      begin
        R := FControl.CellRect(FControl.Col,FControl.Row);
        with FControl.ClientToScreen(R.TopLeft) do begin
          pxLeft := X;
          pyTop := Y;
        end;
        pcxWidth := R.Right - R.Left;
        pcyHeight := R.Bottom - R.Top;
      end;
      else
      begin
        pxLeft := 0;
        pyTop := 0;
        pcxWidth := 0;
        pcyHeight := 0;
      end;
    end;
  end;
end;

procedure TAccessibleStringGrid.accSelect(flagsSelect: Integer;
  varChild: OleVariant);
begin
  if Assigned(DefaultObject) then
    DefaultObject.accSelect(flagsSelect, varChild);
end;

procedure TAccessibleStringGrid.Set_accName(varChild: OleVariant;
  const pszName: WideString);
begin
  if Assigned(DefaultObject) then
    DefaultObject.Set_accName(varChild, pszName);
end;

procedure TAccessibleStringGrid.Set_accValue(varChild: OleVariant;
  const pszValue: WideString);
var
  Row,Col: integer;
begin
  case ChildType(varChild) of
    ctChild:
    begin
      if Assigned(FControl) then
      begin
        FControl.IndexToColRow(varChild,Col,Row);
        FControl.Cells[Col,Row] := pszValue;
      end;
    end;
  end;
end;

function TAccessibleStringGrid.ChildType(varChild: OleVariant): TChildType;
begin
  if VarType(varChild) <> varInteger then
    result := ctInvalid
  else if varChild = CHILDID_SELF then
    result := ctNoChild
  else
    result := ctChild;
end;

function TAccessibleStringGrid.GetDefaultObject: IAccessible;
begin
  if Assigned(FControl) and not FDefaultObjectLoaded then begin
    FDefaultObject := uAccessAPI.GetDefaultObject(FControl);
    FDefaultObjectLoaded := True;
  end;
  Result := FDefaultObject;
end;

function TAccessibleStringGrid.SafeCallException(ExceptObject: TObject;
  ExceptAddr: Pointer): HResult;
begin
  if (ExceptObject is EOleSysError) then
    result := EOleSysError(ExceptObject).ErrorCode
  else
    result := inherited SafeCallException(ExceptObject, ExceptAddr);
end;

class procedure TAccessibleStringGrid.UnwrapControl(
  Control: TCaptionStringGrid);
begin
  if not UserIsRestricted then
    Control.MakeAccessible(nil);
end;

class procedure TAccessibleStringGrid.WrapControl(
  Control: TCaptionStringGrid);
var
  AccessibleStringGrid: TAccessibleStringGrid;
  {Using Accessible here is probably just interface reference count paranoia}
  Accessible: IAccessible;
begin
  if not UserIsRestricted then
  begin
    AccessibleStringGrid := TAccessibleStringGrid.Create;
    Accessible := AccessibleStringGrid;
    AccessibleStringGrid.Control := Control;
    Control.MakeAccessible(Accessible);
  end;
end;

initialization
  try
    TAutoObjectFactory.Create(ComServer, TAccessibleStringGrid, Class_AccessibleStringGrid,
      ciMultiInstance, tmApartment);
  except
    {Let the poor restricted users pass!}
    UserIsRestricted := True;
  end;
end.
