unit mdhTranslate;

{
[MDHTranslate] [1.5]
Delphi 2005
January 2009

LICENSE

The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
"http://www.mozilla.org/MPL/"

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.

The Original Code is "[mdhTranslate.pas]".

The Initial Developer of the Original Code is Martin Holmes (Victoria,
BC, Canada, "http://www.mholmes.com/"). Copyright (C) 2005 Martin Holmes 
and the University of Victoria Computing and Media Centre. The code was 
co-developed for university and personal projects, and rights are shared
by Martin Holmes and the University of Victoria. All Rights Reserved.
}

{
Written by Martin Holmes, between October 2005 and January 2009.

The objective of this unit is to provide a mechanism for translating the relevant UI
strings (actually WideStrings) in an application and saving and reloading the results.

Dependendies:

TVirtualStringTree (Mike Lischke)
TntUnicodeControls (Troy Wolbrink)
XDOM 3.2 (Dieter Köhler)
JEDI Component Library (JCL) jclUnicode

The unit uses undocumented reflection functions (RTTI) in the Delphi 2005 TypInfo unit to
get access to properties of GUI objects without having to know exhaustive details of their
actual class hierarchy. This is the basic idea:

The basic unit is a TComponentReflection object, which interfaces with a TComponent in the
application. The first of these is instantiated by the framework object, a TAppReflection,
with the TApplication object (which is a TComponent descendant). Instantiating one of
these objects causes it to interrogate any child components recursively, and instantiate
objects reflecting them if they (or their descendants) are:

  a) components we're interested in (for example, we're not interested in components
     which have a TAction property assigned, because their properties will be
     controlled by (and reflected through) the TAction.
  b) components which have the property or properties we care about (mostly WideString
     captions and hints, but there may be others).

Thus instantiating one TComponentReflection linked to the Application object should result
in a tree representing the application as a whole, consisting of all objects whose widestrings
we need to translate. This object is only ever created linked to the application instance.
It represents a selective model of the GUI of the application.

The resulting model should be able to:

-write itself to a Virtual String Tree to enable translation

-read itself from a VST to get translated data

-write itself to XML

-read itself from XML

-write itself to the app GUI

Typical usage patterns would be:

-Read from the GUI
-Write resulting data over to a VST, where the user can translate strings
-Read back the data from the VST
-Save that data to an XML file, thus creating a translation file.

-Read from the GUI to create the initial structure
-Read strings from a translation file (XML)
-Write the strings back to the GUI, to change the language of the GUI

-Read from the GUI to create the initial structure
-Read strings from a translation file (XML)
-Write resulting data to a VST to act as the basis for another translation
-Read resulting translation back from the VST
-Save the result to file (thus someone could translate a German GUI to a French
 one without actually changing the app GUI from English).

 The interface ITranslationOperations is designed to allow special processing
 by any form after an interface file is loaded. For example, the form may have
 a TTntComboBox whose drop-down options are not translated by these functions.
 They could be stored as TTntLabel components, invisibly, on the form, so that
 they would be translated, but after loading the translated strings, the
 label captions would have to be re-copied into the combo box to finish the
 update. Any form which needs to do this kind of processing can implement this
 interface and write the function; then the update code checks whether each
 form does implement the interface, and if so, it calls the function.


}

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, TntForms, StdCtrls, TntStdCtrls, ComCtrls, TntComCtrls, Menus,
  TntMenus, TypInfo, Contnrs, XMLRoutines, XDOM_4_1, VirtualTrees, jclUnicode,
  TntDialogs, ImgList;

type
	ITranslationOperations = interface
		['{05AC9298-DC56-4B3A-8170-F4559898D0A1}']
		procedure AfterLoadingTranslation;
	end;

type
  TComponentReflection = class

  private
    FName: string;
    FID: WideString;
    FCaption: WideString;
    FHint: WideString;
    FText: WideString;
    FTitle: WideString;
    FComp: TComponent;
    FClassName: WideString;
    FParentCompRef: TComponentReflection; //keep a pointer to the parent -- very useful
    function GetShouldBeReflected: Boolean;
    function GetShouldBeExcluded: Boolean;
    function GetHasComponents: Boolean;
    function GetColumnCount: integer;  //list views have column headers
    function GetHasCaption: Boolean;
    function GetHasHint: Boolean;
    function GetHasText: Boolean;
    function GetHasTitle: Boolean;
    function GetHasOldStringHint: Boolean;
  public
    Children: TObjectList;
//    ColumnCaptions: TWideStringList;  Abandoned attempt to handle TTntListView columns.
    PVSTNode: PVirtualNode;
    constructor Create(SourceComp: TComponent;  Parent: TComponentReflection);
    destructor Destroy; override;
    function ToDomElement(Doc: TDomDocument): TDomElement;
    procedure ReadFromDomDoc(Doc: TDomDocument);
    procedure ReadFromDomElement(El: TDomElement);
    procedure WriteToComponent;
    function ContainsText(InText: WideString): Boolean;
  published
    property ID: WideString read FID write FID;
    property Name: string read FName write FName;
    property Caption: WideString read FCaption write FCaption;
    property Hint: WideString read FHint write FHint;
    property Text: WideString read FText write FText;
    property Title: WideString read FTitle write FTitle;
    property Comp: TComponent read FComp write FComp;
    property CompClassName: WideString read FClassName write FClassName;
    property HasComponents: Boolean read GetHasComponents;
    property ColumnCount: integer read GetColumnCount;
    property HasCaption: Boolean read GetHasCaption;
    property HasHint: Boolean read GetHasHint;
    property HasOldStringHint: Boolean read GetHasOldStringHint;
    property HasText: Boolean read GetHasText;
    property HasTitle: Boolean read GetHasTitle;
    property ShouldBeReflected: Boolean read GetShouldBeReflected default False;
    property ShouldBeExcluded: Boolean read GetShouldBeExcluded default True;
    property ParentCompRef: TComponentReflection read FParentCompRef write FParentCompRef;
  end;

type
  TAppReflection = class

  private
    FRoot: TComponentReflection;
    DomImpl: TDomImplementation;
    Doc: TDomDocument;
    DomToXMLParser: TDomToXMLParser;
    XMLToDomParser: TXMLToDomParser;
    FIndex: TWideStringList;
    procedure BuildIndex;
  public
    VST: TVirtualStringTree;
    LastFindHit: integer;
    LastFindText: WideString;
    constructor Create;
    destructor Destroy; override;
    procedure WriteToVST;
    procedure WriteToGUI;
    procedure WriteToXMLFile(FilePath: WideString);
    procedure ReadFromXMLFile(FilePath: WideString);
    function GetElementById(ID: WideString): TComponentReflection;
    function Find(InText: WideString): TComponentReflection;
    function FindNext(InText: WideString): TComponentReflection;
  published
  end;

type
  rComponentReflection = Record
    CompRef: TComponentReflection;
  end;

implementation

constructor TComponentReflection.Create(SourceComp: TComponent; Parent: TComponentReflection);
var
i: integer;
ParentID: WideString;
ChildComp: TComponentReflection;
ColCount: integer;

begin
//Instantiate this object
  Comp := SourceComp;
  ParentCompRef := Parent;
  PVSTNode := nil; //initialize to nil; may later be pointed at a tree node
  //ColumnCaptions := TWideStringList.Create;   Abandoned attempt to handle TTntListView columns.
  Children := TObjectList.Create;
  Name := TComponent(Comp).Name;
  ParentID := ''; //default
  if Parent <> nil then
    if Length(TComponentReflection(Parent).ID) > 0 then
      ParentID := TComponentReflection(Parent).ID + '_';
  ID := ParentID + Name;
  CompClassName := string(TComponent(Comp).ClassName);
  if IsPublishedProp(TObject(SourceComp), 'Caption') then
    if PropIsType(TObject(SourceComp), 'Caption', tkWString) then
      Caption := GetWideStrProp(TObject(SourceComp), 'Caption');
  if IsPublishedProp(TObject(SourceComp), 'Hint') then
    if PropIsType(TObject(SourceComp), 'Hint', tkWString) then
      Hint := GetWideStrProp(TObject(SourceComp), 'Hint');

//Now, we may have to handle non-wide-string hints for some old
//components that are not covered in the TntUnicodeControls:
  if IsPublishedProp(TObject(SourceComp), 'Hint') then
    if PropIsType(TObject(SourceComp), 'Hint', tkString) then
      Hint := GetStrProp(TObject(SourceComp), 'Hint');

  if IsPublishedProp(TObject(SourceComp), 'Text') then
    if PropIsType(TObject(SourceComp), 'Text', tkWString) then
      Caption := GetWideStrProp(TObject(SourceComp), 'Text');
  if IsPublishedProp(TObject(SourceComp), 'Title') then
    if PropIsType(TObject(SourceComp), 'Title', tkWString) then
      Title := GetWideStrProp(TObject(SourceComp), 'Title');

{Abandoned attempt to handle TTntListView columns.}
  {ColCount := ColumnCount;
  ColumnCaptions.Clear;
  if FClassName = 'TTntListView' then
    if ColCount > 0 then
      for i := 0 to ColCount-1 do
        ColumnCaptions.Add(TTntListView(Comp).Columns[i].Caption);   }

//Now, if necessary, instantiate child objects
  if HasComponents then
    begin
      for i := 0 to TComponent(Comp).ComponentCount-1 do
        begin
          ChildComp := TComponentReflection.Create(TComponent(Comp).Components[i], Self);
          if ChildComp.ShouldBeReflected then
            Children.Add(ChildComp);
        end;
    end;
end;

destructor TComponentReflection.Destroy;
begin
  Children.Free;
  //ColumnCaptions.Free; Abandoned attempt to handle TTntListView columns.
  inherited;
end;

function TComponentReflection.GetHasComponents: Boolean;
begin
  Result := False; //default
  if Comp <> nil then
    if TComponent(Comp).ComponentCount > 0 then
      Result := True;
end;

function TComponentReflection.GetColumnCount: integer;
var
PropInfo: PPropInfo;

begin
  Result := 0;
  PropInfo := GetPropInfo(TObject(Comp), 'ColumnCount');
  if Assigned(PropInfo) and
       (PropInfo^.PropType^.Kind = tkInteger) then
    Result := GetOrdProp(TObject(Comp), 'ColumnCount');
end;

function TComponentReflection.GetHasCaption: Boolean;
begin
  Result := (IsPublishedProp(TObject(Comp), 'Caption'));
end;

function TComponentReflection.GetHasHint: Boolean;
begin
  Result := (IsPublishedProp(TObject(Comp), 'Hint'));
end;

function TComponentReflection.GetHasOldStringHint: Boolean;
begin
  Result := False;
  if IsPublishedProp(TObject(Comp), 'Hint') then
    if PropIsType(TObject(Comp), 'Hint', tkString) then
      Result := True;
end;

function TComponentReflection.GetHasText: Boolean;
begin
  Result := (IsPublishedProp(TObject(Comp), 'Text'));
end;

function TComponentReflection.GetHasTitle: Boolean;
begin
  Result := (IsPublishedProp(TObject(Comp), 'Title'));
end;

function TComponentReflection.GetShouldBeReflected: Boolean;
var
i: integer;

begin
  Result := False; //default
//If it has no name, then it shouldn't be reflected (and can't contain components we care about)
  if (IsPublishedProp(TObject(Comp), 'Name')) then
    if Length(GetStrProp(TObject(Comp), 'Name')) < 1 then
      Exit;
  if ShouldBeExcluded then
    Exit;
  if (IsPublishedProp(TObject(Comp), 'Caption')) or
      ((IsPublishedProp (TObject(Comp), 'Hint')) or
        (IsPublishedProp (TObject(Comp), 'Title'))) then
    Result := True
  else
    begin
      if Children.Count > 0 then
        for i := 0 to Children.Count - 1 do
          begin
            Result := Result and TComponentReflection(Children[i]).ShouldBeReflected;
            if Result = True then
              Break;
          end;
    end;
end;

function TComponentReflection.GetShouldBeExcluded: Boolean;
begin
  Result := True; //Default
//Check for an overriding TAction
  if IsPublishedProp(TObject(Comp), 'Action') then
    begin
      if Comp is TTntMenuItem then
        if TTntMenuItem(Comp).Action <> nil then
          Exit;
      if Comp is TTntToolButton then
        if TTntToolButton(Comp).Action <> nil then
          Exit;
    end;

//Check whether it's a toolbar separator
  if Comp is TTntToolButton then
    if (TTntToolButton(Comp).Style = tbsSeparator) or
       (TTntToolButton(Comp).Style = tbsDivider)   then
      Exit;

//Check whether it's a menu separator (having a hyphen for a caption)
  if Comp is TTntMenuItem then
    if TTntMenuItem(Comp).Caption = '-' then
      Exit;

//Add any further tests here, as other component configurations show up that
//should be hidden from the translation mechanism.

//Now return false for any that have not been caught so far
  Result := False;
end;

function TComponentReflection.ToDomElement(Doc: TDomDocument): TDomElement;
var
El: TDomElement;
ColsEl: TDomElement;
TempEl: TDomElement;
TextNode: TDomText;
i: integer;

  procedure AddElement(ElName, ElValue: WideString);
  begin
    TempEl := TDomElement.Create(Doc, ElName);
    TextNode := TDomText.Create(Doc);

//BUGFIX 28/04/08: AppendData seems to have been eliminating angle brackets instead
//of escaping them. Trying NodeValue instead.
//    TextNode.AppendData(ElValue);
    TextNode.NodeValue := ElValue;
    TempEl.AppendChild(TextNode);
    El.AppendChild(TempEl);
  end;


begin
  Result := nil; //default
  try
    El := TDomElement.Create(Doc, 'component');
    El.SetAttribute('id', ID);

    AddElement('name', Name);
    AddElement('caption', Caption);
    AddElement('hint', Hint);
    AddElement('text', Text);
    AddElement('title', Title);
    AddElement('classname', CompClassName);
    
{Abandoned attempt to handle TTntListView columns.}
    {if ColumnCaptions.Count > 0 then
      begin
        ColsEl := TDomElement.Create(Doc, 'columns');
          for i := 0 to ColumnCaptions.Count -1 do
            begin
              TempEl := TDomElement.Create(Doc, 'column-caption');
              TextNode := TDomText.Create(Doc);
              TextNode.AppendData(ColumnCaptions[1]);
              TempEl.AppendChild(TextNode);
              ColsEl.AppendChild(TempEl);
            end;
        El.AppendChild(ColsEl);
      end;   }

    if Children.Count > 0 then
      begin
        TempEl := TDomElement.Create(Doc, 'children');
        for i := 0 to Children.Count-1 do
          TempEl.AppendChild(TComponentReflection(Children[i]).ToDomElement(Doc));
        El.AppendChild(TempEl);
      end;

  except
    Exit;
  end;
  Doc.AppendChild(El);
  Result := El;
end;

procedure TComponentReflection.ReadFromDomDoc(Doc: TDomDocument);
var
BaseEl: TDomElement;
i: integer;

begin
//There are two possible approaches to this, one of which iterates through the
//dom tree manually, and the other of which just uses ids. The latter is
//simpler. We can't use getElementById on the DOM structure unless the file
//is validated first, which is complicated, so this function is redundant at the
//moment.
  BaseEl := Doc.GetElementById(ID);
  if BaseEl <> nil then
    begin
      if BaseEl.GetElementsByTagName('caption').Length > 0 then
        Caption := BaseEl.GetElementsByTagName('caption').Item(0).textContent;
      if BaseEl.GetElementsByTagName('hint').Length > 0 then
        Hint := BaseEl.GetElementsByTagName('hint').Item(0).textContent;
      if BaseEl.GetElementsByTagName('text').Length > 0 then
        Text := BaseEl.GetElementsByTagName('text').Item(0).textContent;
      if BaseEl.GetElementsByTagName('title').Length > 0 then
        Title := BaseEl.GetElementsByTagName('title').Item(0).textContent;
    end;
  if Children.Count > 0 then
    for i := 0 to Children.Count-1 do
      TComponentReflection(Children[i]).ReadFromDomDoc(Doc);
end;

procedure TComponentReflection.ReadFromDomElement(El: TDomElement);
var
i: integer;

begin
  if El <> nil then
    begin
      if El.GetElementsByTagName('caption').Length > 0 then
        Caption := El.GetElementsByTagName('caption').Item(0).textContent;
      if El.GetElementsByTagName('hint').Length > 0 then
        Hint := El.GetElementsByTagName('hint').Item(0).textContent;
      if El.GetElementsByTagName('text').Length > 0 then
        Text := El.GetElementsByTagName('text').Item(0).textContent;
      if El.GetElementsByTagName('title').Length > 0 then
        Title := El.GetElementsByTagName('title').Item(0).textContent;

      {(New for 1.4): Handling TTntListView column captions.}
      if El.GetElementsByTagName('columns').Length > 0 then
        begin

        end;
    end;
end;

procedure TComponentReflection.WriteToComponent;
var
i: integer;
iTransOp: ITranslationOperations;
wsTemp: WideString;
ImgList: TCustomImageList;

begin
//Only write strings to the GUI if they have text, otherwise we risk
//components losing all displayable captions.
{First, check if it's a main menu. If so, stash a reference to its image list,
then set the image list to 0 (otherwise a Windows bug will screw up
the menu background colour -- see
http://www.tsilang.com/delphiglobalizationtool_faq.html#20).}
  ImgList := nil;
  if (Comp is TTntForm) then
    if TTntForm(Comp).Menu <> nil then
      begin
        ImgList := TTntMainMenu(TTntForm(Comp).Menu).Images;
        TTntMainMenu(TTntForm(Comp).Menu).Images := nil;
        Application.ProcessMessages;
      end;

  if HasCaption then
    if Length(Caption) > 0 then
      SetWideStrProp(TObject(Comp), GetPropInfo(TObject(Comp), 'Caption'), Caption);

  if HasHint then
    if Length(Hint) > 0 then
      SetWideStrProp(TObject(Comp), GetPropInfo(TObject(Comp), 'Hint'), Hint);

  if HasOldStringHint then
    if Length(Hint) > 0 then
      SetStrProp(TObject(Comp), 'Hint', Hint);

{TODO: we may have to handle non-wide-string hints for some old
components that are not covered in the TntUnicodeControls. Right now,
string hints are casting to widestrings where necessary; this may cause
some problems in future.}

//Not bothering with the Text element -- probably never needed.
  if HasTitle then
    if Length(Title) > 0 then
      SetWideStrProp(TObject(Comp), GetPropInfo(TObject(Comp), 'Title'), Title);
  if Children.Count > 0 then
    for i := 0 to Children.Count-1 do
      TComponentReflection(Children[i]).WriteToComponent;

{Now reset the image list if it was set to nil.}
  if (Comp is TTntForm) then
    if TTntForm(Comp).Menu <> nil then
    begin
      if ImgList <> nil then
        TTntMainMenu(TTntForm(Comp).Menu).Images := ImgList;
      Application.ProcessMessages;
    end;

//Now check if it's a component that implements the update interface, and
//may need to do some special processing:
  if Supports(Comp, ITranslationOperations, iTransOp) then
    iTransOp.AfterLoadingTranslation;

//Invalidate any TControls to trigger repaints.
//We need a way to force repaints of TComponents!
{  if Comp is TControl then
    TControl(Comp).Invalidate
  else
    if Comp is TTntMenuItem then
      begin
        wsTemp := TTntMenuItem(Comp).Caption;
        TTntMenuItem(Comp).Caption := wsTemp;
      end; }
end;

function TComponentReflection.ContainsText(InText: WideString): Boolean;
begin
  Result := False; //default
  if Pos(InText, Name) > 0 then
    begin
      Result := True;
      Exit;
    end;
  if HasCaption then
    if Pos(InText, Caption) > 0 then
      begin
        Result := True;
        Exit;
      end;
  if HasHint then
    if Pos(InText, Hint) > 0 then
      begin
        Result := True;
        Exit;
      end;
  if HasTitle then
    if Pos(InText, Title) > 0 then
      begin
        Result := True;
        Exit;
      end;
end;

{ TAppReflection }

constructor TAppReflection.Create;
begin
  DomImpl := TDomImplementation.Create(nil);
  Doc := TDomDocument.Create(DomImpl);
  DomToXMLParser := TDomToXMLParser.Create(nil);
  DomToXMLParser.DOMImpl := DomImpl;
  XMLToDomParser := TXMLToDomParser.Create(nil);
  XMLToDomParser.DOMImpl := DomImpl;
//Must expand entity references automatically, otherwise ampersands in captions
//will be lost.
  XMLToDomParser.KeepEntityRefs := False;

//Initialize the VST to nil; it will be connected to a tree later if required.
  VST := nil;

//Create the indexing list
  FIndex := TWideStringList.Create;

//Instantiate the tree
  FRoot := TComponentReflection.Create(Application, nil);

//Now set special parameters in the root element
  FRoot.Name := Application.Title;
  FRoot.ID := 'appRoot';

//Create the index for getElementById lookups.
  BuildIndex;

  LastFindHit := -1;
end;

destructor TAppReflection.Destroy;
begin
  FIndex.Free;
  FRoot.Free;
  XMLToDomParser.Free;
  DomToXMLParser.Free;
  Doc.Free;
  DomImpl.Free;
  inherited;
end;

procedure TAppReflection.WriteToVST;
  procedure CreateNode(CR: TComponentReflection);
  var
  ParentPVTNode: PVirtualNode;
  Node: PVirtualNode;
  NodeRec: ^rComponentReflection;
  i: integer;
  begin
  //Find the parent if there is one
    ParentPVTNode := nil;
    if CR.ParentCompRef <> nil then
      ParentPVTNode := CR.ParentCompRef.PVSTNode;
  //Create the node
    Node := TVirtualStringTree(VST).AddChild(ParentPVTNode);
  //Link the node rec to the component reflection object
    NodeRec := TVirtualStringTree(VST).GetNodeData(Node);
    NodeRec.CompRef := CR;
  //Link the component reflection object back to the node
    CR.PVSTNode := Node;
  //Call the function recursively for any children of this CR
    if CR.Children.Count > 0 then
      for i := 0 to CR.Children.Count-1 do
        CreateNode(TComponentReflection(CR.Children[i]));
  end;

begin
  if VST = nil then
    Exit;
  TVirtualStringTree(VST).Clear;
  CreateNode(FRoot);
end;

procedure TAppReflection.WriteToXMLFile(FilePath: WideString);
var
Path: TFileName;
Stream: TFileStream;

begin
  Path := TFileName(FilePath);
  Doc.Clear;
  FRoot.ToDomElement(Doc);
  Stream := TFileStream.Create(Path, fmCreate);
  try
    DomToXMLParser.WriteToStream(Doc, 'UTF-8', Stream);
  finally
    Stream.Free;
  end;
end;

procedure TAppReflection.ReadFromXMLFile(FilePath: WideString);
var
Path: TFileName;
InDoc: TDomDocument;
NodeIterator: TDomNodeIterator;
Node: TDomNode;
CurrId: WideString;
CompRef: TComponentReflection;

begin
  Path := TFileName(FilePath);
  InDoc := XMLToDomParser.ParseFile(Path, False);
  try
    try
//The following will not work because it uses getElementById when reading the file,
//and that fails unless the file is validated. Instead, we can reverse the process
//and implement our own GetElementById indexing system for the model.
//      FRoot.ReadFromDomDoc(InDoc);
      NodeIterator := InDoc.CreateNodeIterator(InDoc.DocumentElement, [ntElement_Node], nil, True);
      Node := NodeIterator.NextNode;
      while Node <> nil do
        begin
          CurrId := TDomElement(Node).GetAttributeNormalizedValue('id');
          CompRef := GetElementById(CurrId);
          if CompRef <> nil then
            CompRef.ReadFromDomElement(TDomElement(Node));
          Node := NodeIterator.NextNode;
        end;
    except
      raise;
    end;
  finally
    InDoc.Free;
  end;
end;

procedure TAppReflection.WriteToGUI;
begin
  FRoot.WriteToComponent;
end;

procedure TAppReflection.BuildIndex;
  procedure AddToIndex(CompRef: TComponentReflection);
  var
  i: integer;

  begin
    FIndex.AddObject(CompRef.ID, CompRef);
    if CompRef.Children.Count > 0 then
      for i := 0 to CompRef.Children.Count - 1 do
        AddToIndex(TComponentReflection(CompRef.Children[i]));
  end;

begin
//This function iterates through all the elements of the tree and
//builds a linear list of ids with pointers to them, enabling getElementById.
  FIndex.Clear;
  AddToIndex(FRoot);
end;

function TAppReflection.GetElementById(ID: WideString): TComponentReflection;
var
Hit: integer;

begin
  Result := nil;
  Hit := FIndex.IndexOf(ID);
  if Hit > -1 then
    Result := TComponentReflection(FIndex.Objects[Hit]);
end;

function TAppReflection.FindNext(InText: WideString): TComponentReflection;
var
i: integer;

begin
  if (InText <> LastFindText) or (LastFindHit < 0) then
    begin
      Result := Find(InText);
    end
  else
    begin
      i := LastFindHit+1;
      while (i<FIndex.Count) and
            (TComponentReflection(FIndex.Objects[i]).ContainsText(InText) = False) do
        inc(i);
      if i < FIndex.Count then
        begin
          LastFindHit := i;
          Result := TComponentReflection(FIndex.Objects[i]);
        end
      else
        begin
          Result := Find(InText);
        end;
    end;
end;

function TAppReflection.Find(InText: WideString): TComponentReflection;
var
i: integer;

begin
  Result := nil;
  LastFindHit := -1;
  LastFindText := InText;
  i := 0;
  while (i<FIndex.Count) and
        (TComponentReflection(FIndex.Objects[i]).ContainsText(InText) = False) do
    inc(i);
  if i < FIndex.Count then
    begin
      LastFindHit := i;
      Result := TComponentReflection(FIndex.Objects[i]);
    end;
end;



end.
