unit VA508ImageListLabeler;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, ImgList, VAClasses, Graphics, ComCtrls,
  Contnrs, Forms, oleacc2, VA508MSAASupport;

type
  TVA508ImageListLabeler = class;
  TVA508ImageListLabels = class;

  TVA508ImageListLabel = class(TCollectionItem)
  private
    FImageIndex: integer;
    FCaption: string;
    FOverlayIndex: integer;
    procedure SetImageIndex(const Value: integer);
    procedure Changed;
    procedure SetCaption(const Value: string);
    procedure SetOverlayIndex(const Value: integer);
  protected
    procedure Refresh;
    function Labeler: TVA508ImageListLabeler;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property Caption: string read FCaption write SetCaption;
    property ImageIndex: integer read FImageIndex write SetImageIndex;
    property OverlayIndex: integer read FOverlayIndex write SetOverlayIndex;
  end;

  TVA508ImageListLabels = class(TCollection)
  private
    FOwner: TVA508ImageListLabeler;
    FColumns: TStringList;
    FImageData: TStrings;
    FOverlayData: TStrings;
    FBuildOverlayData: boolean;
  protected
    function GetAttrCount: Integer; override;
    function GetAttr(Index: Integer): string; override;
    function GetItemAttr(Index, ItemIndex: Integer): string; override;
    function GetItem(Index: Integer): TVA508ImageListLabel;
    procedure SetItem(Index: Integer; Value: TVA508ImageListLabel);
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
    procedure Update(Item: TCollectionItem); override;
    function GetImageData: TStrings;
    function GetOverlayData: TStrings;
    procedure ResetData;    
  public
    constructor Create(Owner: TVA508ImageListLabeler);
    destructor Destroy; override;
    function GetOwner: TPersistent; override;
    function Add: TVA508ImageListLabel;
    property Items[Index: Integer]: TVA508ImageListLabel read GetItem write SetItem; default;
  end;

  TVA508ImageListComponent = class(TCollectionItem)
  private
    FComponent: TWinControl;
    FComponentNotifier: TVANotificationEventComponent;
    procedure ComponentNotifyEvent(AComponent: TComponent; Operation: TOperation);
    procedure SetComponent(const Value: TWinControl);
    function Labeler: TVA508ImageListLabeler;
  protected
    function GetDisplayName: string; override;
  public
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    function ImageListTypes: TVA508ImageListTypes;
  published
    property Component: TWinControl read FComponent write SetComponent;
  end;

  TVA508ImageListComponents = class(TCollection)
  private
    FOwner: TVA508ImageListLabeler;
  protected
    function GetItem(Index: Integer): TVA508ImageListComponent;
    procedure SetItem(Index: Integer; Value: TVA508ImageListComponent);
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
  public
    constructor Create(Owner: TVA508ImageListLabeler);
    destructor Destroy; override;
    function GetOwner: TPersistent; override;
    function Add: TVA508ImageListComponent;
    property Items[Index: Integer]: TVA508ImageListComponent read GetItem write SetItem; default;
  end;

  TVA508ImageListLabeler = class(TComponent)
  private
    FStartup: boolean;
    FOldComponentList: TList;
    FImageListChanging: boolean;
    FImageListChanged: boolean;
    FItemChange: boolean;
    FOnChange: TNotifyEvent;
    FChangeLink: TChangeLink;
    FImageList: TCustomImageList;
    FComponents: TVA508ImageListComponents;
    FItems: TVA508ImageListLabels;
    FRemoteLabeler: TVA508ImageListLabeler;
    procedure SetImageList(const Value: TCustomImageList);
    procedure SetRemoteLabeler(const Value: TVA508ImageListLabeler);
  protected
    procedure ImageIndexQuery(Sender: TObject; ImageIndex: integer;
                              ImageType: TVA508ImageListType; var ImageText: string);
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure ImageListChange(Sender: TObject);
    procedure ItemChanged;
    procedure SaveChanges(Reregister: boolean);
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ComponentImageListChanged;
  published
    property Components: TVA508ImageListComponents read FComponents write FComponents;
    property ImageList: TCustomImageList read FImageList write SetImageList;
    property Labels: TVA508ImageListLabels read FItems write FItems;
    property RemoteLabeler: TVA508ImageListLabeler read FRemoteLabeler write SetRemoteLabeler;
  end;

procedure Register;

implementation

uses VA508Classes, VA508AccessibilityRouter;

procedure Register;
begin
  RegisterComponents('VA 508', [TVA508ImageListLabeler]);
end;

{ TVA508ImageListLabeler }

procedure TVA508ImageListLabeler.ComponentImageListChanged;
begin
  SaveChanges(TRUE);
end;

constructor TVA508ImageListLabeler.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FStartup := TRUE;
  FOldComponentList := TList.Create;
  FItems := TVA508ImageListLabels.Create(Self);
  FComponents := TVA508ImageListComponents.Create(Self);
  FChangeLink := TChangeLink.Create;
  FChangeLink.OnChange := ImageListChange;
  VA508ComponentCreationCheck(Self, AOwner, TRUE, TRUE);
end;

destructor TVA508ImageListLabeler.Destroy;
begin
  FItems.Clear;
  FComponents.Clear;
  SaveChanges(FALSE);
  SetImageList(nil);
  FreeAndNil(FItems);
  FreeAndNil(FComponents);
  FChangeLink.Free;
  FreeAndNil(FOldComponentList);
  inherited;
end;

procedure TVA508ImageListLabeler.ImageIndexQuery(Sender: TObject; ImageIndex: integer; 
                                  ImageType: TVA508ImageListType; var ImageText: string);
var
  list: TStrings;
begin
  if ImageIndex < 0 then exit;
  if ImageType = iltOverlayImages then
  begin
    if assigned(RemoteLabeler) then
      list := RemoteLabeler.FItems.GetOverlayData
    else
      list := FItems.GetOverlayData;
  end
  else
  begin
    if assigned(RemoteLabeler) then
      list := RemoteLabeler.FItems.GetImageData
    else
      list := FItems.GetImageData;
  end;
  if ImageIndex < list.Count then
    ImageText := list[ImageIndex]
  else
    ImageText := '';
end;

procedure TVA508ImageListLabeler.ImageListChange(Sender: TObject);
var
  i: integer;
begin
  if assigned(FOnChange) then
  begin
    FItemChange := FALSE;
    FImageListChanged := TRUE;
    try
      for I := 0 to FItems.Count - 1 do
      begin
        FItems.Items[i].Refresh;
        if FItemChange then
          break;
      end;
      if FItemChange then
        FOnChange(Self);
    finally
      FImageListChanged := FALSE;
    end;                         
  end;
end;

procedure TVA508ImageListLabeler.ItemChanged;
begin
  if FImageListChanged then
    FItemChange := TRUE
  else if assigned(FOnChange) then
    FOnChange(Self);
end;

procedure TVA508ImageListLabeler.Loaded;
begin
  inherited;
  FStartup := FALSE;
  Application.ProcessMessages;
  SaveChanges(FALSE);
end;

procedure TVA508ImageListLabeler.SaveChanges(Reregister: boolean);
var
  i, idx: integer;
  Item: TVA508ImageListComponent;
  Comp: TWinControl;
  NewList: TList;
  reg: boolean;
begin
  if FStartup or (csDesigning in ComponentState) or (not ScreenReaderSystemActive) then exit;
  if (FComponents.Count = 0) and (FOldComponentList.Count = 0) then exit;
  NewList := TList.Create;
  try
    for i := 0 to FComponents.Count - 1 do
    begin
      Item := FComponents.Items[i];
      if assigned(Item.Component) then
      begin
        NewList.Add(Item.Component);
        idx := FOldComponentList.IndexOf(Item.Component);
        if idx < 0 then
          reg := TRUE
        else
        begin
          FOldComponentList.Delete(idx);
          reg := Reregister;
        end;
        if reg then
          RegisterComponentImageListQueryEvent(Item.Component, Item.ImageListTypes, ImageIndexQuery);
      end;
    end;
    for i := 0 to FOldComponentList.Count-1 do
    begin
      Comp := TWinControl(FOldComponentList[i]);
      UnregisterComponentImageListQueryEvent(Comp, ImageIndexQuery);
    end;
  finally
    FOldComponentList.Free;
    FOldComponentList := NewList;
  end;
end;

procedure TVA508ImageListLabeler.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (AComponent = FImageList) and (Operation = opRemove) then
    SetImageList(nil);
end;

procedure TVA508ImageListLabeler.SetImageList(const Value: TCustomImageList);
var
  i,idx: integer;
  list: string;
begin
  if FImageListChanging then exit;
  if assigned(FRemoteLabeler) then 
  begin
    FImageList := nil;
    exit;
  end;
  if FImageList <> Value then
  begin
    FImageListChanging := TRUE;
    try
      if assigned(FImageList) then
      begin
        FImageList.UnRegisterChanges(FChangeLink);
        FImageList.RemoveFreeNotification(Self);
      end;
      FImageList := Value;
      if assigned(FImageList) then
      begin
        FImageList.FreeNotification(Self);
        FImageList.RegisterChanges(FChangeLink);
        if FImageList.count > 0 then
        begin
          list := StringOfChar('x',FImageList.Count);
          for i := 0 to FItems.Count - 1 do
          begin
            idx := FItems[i].ImageIndex + 1;
            if idx > 0 then
              list[idx] := ' ';
          end;
          for i := 0 to FImageList.Count - 1 do
          begin
            if list[i+1] = 'x' then
              FItems.Add.ImageIndex := i;
          end;
        end;
      end;
      if assigned(FOnChange) then
        FOnChange(Self);
    finally
      FImageListChanging := FALSE;
    end;
  end;
end;

procedure TVA508ImageListLabeler.SetRemoteLabeler(const Value: TVA508ImageListLabeler);
begin
  if (FRemoteLabeler <> Value) then
  begin
    if assigned(Value) then
    begin
      FItems.Clear;
      SetImageList(nil);
    end;
    FRemoteLabeler := Value;
  end;
end;

{ TVA508ImageListItems }

function TVA508ImageListLabels.Add: TVA508ImageListLabel;
begin
  Result := TVA508ImageListLabel(inherited Add);
end;

constructor TVA508ImageListLabels.Create(Owner: TVA508ImageListLabeler);
begin
  inherited Create(TVA508ImageListLabel);
  FImageData := TStringList.Create;
  FOverlayData := TStringList.Create;
  FOwner := Owner;
  FColumns := TStringList.Create;
  FColumns.Add('Image');
  FColumns.Add('ImageIndex');
  FColumns.Add('OverlayIndex');  
  FColumns.Add('Caption');
end;

destructor TVA508ImageListLabels.Destroy;
begin
  Clear;
  FreeAndNil(FColumns);
  FreeAndNil(FImageData);
  FreeAndNil(FOverlayData);
  inherited;
end;

function TVA508ImageListLabels.GetAttr(Index: Integer): string;
begin
  Result := FColumns[Index];
end;

function TVA508ImageListLabels.GetAttrCount: Integer;
begin
  Result := FColumns.Count;
end;

function TVA508ImageListLabels.GetImageData: TStrings;
var
  i: integer;
  item: TVA508ImageListLabel;
begin
  if (FImageData.Count = 0) and (Count > 0) then
  begin
    for i := 0 to Count-1 do
    begin
      item := Items[i];
      while FImageData.Count <= item.ImageIndex do
        FImageData.Add('');
      FImageData[item.ImageIndex] := item.Caption;
    end;
  end;
  Result := FImageData;
end;

function TVA508ImageListLabels.GetItem(Index: Integer): TVA508ImageListLabel;
begin
  Result := TVA508ImageListLabel(inherited GetItem(Index));
end;

function TVA508ImageListLabels.GetItemAttr(Index, ItemIndex: Integer): string;
begin
  case Index of
    0:  Result := ' '; // needs something on index 0 or it doesn't display anything on entire line
    1:  if GetItem(ItemIndex).ImageIndex < 0 then
          Result := ' '
        else
          Result := IntToStr(GetItem(ItemIndex).ImageIndex);
    2:  begin
          if (GetItem(ItemIndex).OverlayIndex < 0) then
            Result := ' '
          else
            Result := IntToStr(GetItem(ItemIndex).OverlayIndex);
        end;
    3:  Result := GetItem(ItemIndex).Caption;
    else  Result := '';
  end;
end;

function TVA508ImageListLabels.GetOverlayData: TStrings;
var
  i: integer;
  item: TVA508ImageListLabel;
begin
  if FBuildOverlayData then
  begin
    FBuildOverlayData := FALSE;
    if (Count > 0) then
    begin
      for i := 0 to Count-1 do
      begin
        item := Items[i];
        if item.OverlayIndex >= 0 then
        begin
          while FOverlayData.Count <= item.OverlayIndex do
            FOverlayData.Add('');
          FOverlayData[item.OverlayIndex] := item.Caption;
        end;
      end;
    end;
  end;
  Result := FOverlayData;
end;

function TVA508ImageListLabels.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure TVA508ImageListLabels.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited;
  ResetData;
end;

procedure TVA508ImageListLabels.ResetData;
begin
  FImageData.Clear;
  FOverlayData.Clear;
  FBuildOverlayData := TRUE;
end;

procedure TVA508ImageListLabels.SetItem(Index: Integer; Value: TVA508ImageListLabel);
begin
  inherited SetItem(Index, Value);
end;

procedure TVA508ImageListLabels.Update(Item: TCollectionItem);
begin
  inherited;
  ResetData;
end;

{ TVA508GraphicLabel }

procedure TVA508ImageListLabel.Assign(Source: TPersistent);
var
  item: TVA508ImageListLabel;
begin
  if Source is TVA508ImageListLabel then
  begin
    item := TVA508ImageListLabel(Source);
    SetImageIndex(item.ImageIndex);
    FCaption := item.Caption;
  end
  else
    inherited Assign(Source);
end;

procedure TVA508ImageListLabel.Changed;
begin
  labeler.ItemChanged;
end;

constructor TVA508ImageListLabel.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FImageIndex := -1;
  FOverlayIndex := -1;
end;

destructor TVA508ImageListLabel.Destroy;
begin
  inherited;
end;

function TVA508ImageListLabel.Labeler: TVA508ImageListLabeler;
begin
  Result := TVA508ImageListLabeler(TVA508ImageListLabels(GetOwner).GetOwner);
end;

procedure TVA508ImageListLabel.Refresh;
begin
  SetImageIndex(FImageIndex);
end;

procedure TVA508ImageListLabel.SetCaption(const Value: string);
begin
  if (FCaption <> Value) then
  begin
    FCaption := Value;
    TVA508ImageListLabels(GetOwner).Update(Self);
  end;
end;

procedure TVA508ImageListLabel.SetImageIndex(const Value: integer);
var
  before: integer;
begin
  if csReading in labeler.ComponentState then
    FImageIndex := Value
  else
  begin
    before := FImageIndex;
    if not assigned(labeler.ImageList) then
      FImageIndex := -1
    else
    if (Value >= 0) and (Value < labeler.ImageList.Count) then
      FImageIndex := Value
    else
      FImageIndex := -1;
    if FImageIndex <> before then
    begin
      Changed;
      TVA508ImageListLabels(GetOwner).Update(Self);
    end;
  end;

end;

procedure TVA508ImageListLabel.SetOverlayIndex(const Value: integer);
begin
  if (FOverlayIndex <> Value) and (Value >= 0) and (Value < 16) then
  begin
    FOverlayIndex := Value;
  end;
end;

{ TVA508ImageListComponents }

function TVA508ImageListComponents.Add: TVA508ImageListComponent;
begin
  Result := TVA508ImageListComponent(inherited Add);
end;

constructor TVA508ImageListComponents.Create(Owner: TVA508ImageListLabeler);
begin
  inherited Create(TVA508ImageListComponent);
  FOwner := Owner;
end;

destructor TVA508ImageListComponents.Destroy;
begin
  Clear;
  inherited;
end;

function TVA508ImageListComponents.GetItem(
  Index: Integer): TVA508ImageListComponent;
begin
  Result := TVA508ImageListComponent(inherited GetItem(Index));
end;

function TVA508ImageListComponents.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure TVA508ImageListComponents.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited;
  FOwner.SaveChanges(FALSE);
end;

procedure TVA508ImageListComponents.SetItem(Index: Integer;
  Value: TVA508ImageListComponent);
begin
  inherited SetItem(Index, Value);
end;

{ TVA508ImageListComponent }

procedure TVA508ImageListComponent.Assign(Source: TPersistent);
var
  comp: TVA508ImageListComponent;
begin
  if Source is TVA508ImageListComponent then
  begin
    comp := TVA508ImageListComponent(Source);
    comp.Component := FComponent;
  end
  else
    inherited Assign(Source);
end;

procedure TVA508ImageListComponent.ComponentNotifyEvent(AComponent: TComponent;
  Operation: TOperation);
begin
  if (Operation = opRemove) and assigned(AComponent) and (AComponent = FComponent) then
    SetComponent(nil);
end;

destructor TVA508ImageListComponent.Destroy;
begin
  SetComponent(nil);
  if assigned(FComponentNotifier) then
    FreeAndNil(FComponentNotifier);
  inherited;
end;

function TVA508ImageListComponent.GetDisplayName: string;
begin
  if assigned(FComponent) and (length(FComponent.Name) > 0) then
    Result := FComponent.Name + ' (' + FComponent.ClassName + ')'
  else
    Result := inherited GetDisplayName;
end;

type
  TExposedTreeView = class(TCustomTreeView);
  TExposedListView = class(TCustomListView);

function TVA508ImageListComponent.ImageListTypes: TVA508ImageListTypes;
var
  list: TCustomImageList;
begin
  Result := [];
  list := Labeler.ImageList;
  if (not assigned(list)) and assigned(Labeler.FRemoteLabeler) then
    list := Labeler.FRemoteLabeler.ImageList;
  if (not assigned(list)) then exit;
  if FComponent is TCustomTreeView then
  begin
    with TExposedTreeView(FComponent) do
    begin
      if list = Images then
        Result := Result + [iltImages, iltOverlayImages];
      if list = StateImages then
        Include(Result, iltStateImages);
    end;
  end
  else if FComponent is TCustomListView then
  begin
    with TExposedListView(FComponent) do
    begin
      if list = LargeImages then
        Result := Result + [iltLargeImages, iltOverlayImages];
      if list = SmallImages then
        Result := Result + [iltSmallImages, iltOverlayImages];
      if list = StateImages then
        Include(Result, iltStateImages);
    end;
  end;
end;

function TVA508ImageListComponent.Labeler: TVA508ImageListLabeler;
begin
  Result := TVA508ImageListLabeler(TVA508ImageListLabels(GetOwner).GetOwner);
end;

procedure TVA508ImageListComponent.SetComponent(const Value: TWinControl);
var
  i: integer;
  found: boolean;
begin
  if FComponent <> Value then
  begin
    if assigned(Value) then
    begin
      Found := false;
      for i := low(VA508ImageListLabelerClasses) to high(VA508ImageListLabelerClasses) do
      begin
        if Value is VA508ImageListLabelerClasses[i] then
        begin
          Found := true;
          break;
        end;
      end;
      if not found then
        raise EVA508AccessibilityException.Create('Invalid component class used in ' + TVA508ImageListComponent.ClassName);
    end;
    if assigned(FComponentNotifier) and assigned(FComponent) then
      FComponentNotifier.RemoveFreeNotification(FComponent);
    if assigned(Value) then
    begin
      if not assigned(FComponentNotifier) then
        FComponentNotifier := TVANotificationEventComponent.NotifyCreate(nil, ComponentNotifyEvent);
      FComponentNotifier.FreeNotification(Value);
    end;
    FComponent := Value;
    Labeler.SaveChanges(FALSE);
  end;
end;

end.
