{*************************************************************************}
{ TAdvTreeNodes                                                           }
{ for Delphi & C++Builder                                                 }
{                                                                         }
{ written by TMS Software                                                 }
{           copyright © 2010                                              }
{           Email : info@tmssoftware.com                                  }
{           Website : http://www.tmssoftware.com/                         }
{                                                                         }
{ The source code is given as is. The author is not responsible           }
{ for any possible damage done due to the use of this code.               }
{ The component can be freely used in any application. The complete       }
{ source code remains property of the author and may not be distributed,  }
{ published, given or sold in any form as such. No parts of the source    }
{ code can be included in any other component or application without      }
{ written authorization of the author.                                    }
{*************************************************************************}

unit AdvTreeNodes;

{$I TMSDEFS.INC}

interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, ImgList
{$IFDEF DELPHIXE3_LVL}
  , System.UITypes
{$ENDIF}
  ;


type
  TAdvTreeNode = class;
  TAdvTreeNodes = class;
  
  TDbgList = class(TList)
  private
    function GetItemsEx(Index: Integer): TAdvTreeNode;
    procedure SetItemsEx(Index: Integer; const Value: TAdvTreeNode);
  public
    procedure AssignList(ListA: TList);
    property Items[Index: Integer]: TAdvTreeNode read GetItemsEx write SetItemsEx; default;
  end;

{ TAdvTreeNode }

  TNodeType = (ntText, ntCheckbox, ntRadioButton);


  PNodeInfo = ^TNodeInfo;
  TNodeInfo = packed record
    SelectedIndex: Integer;
    StateIndex: Integer;
    OverlayIndex: Integer;
    Data: Pointer;
    Count: Integer;
    Text: string[255];
    NodeType: TNodeType;
    Checked: Boolean;
    ImageURL: string[255];
    ImageCollapsedURL: string[255];
    URL: string[255];
    Tag: Integer;
    Hint: string[255];
    IsExpanded: Boolean;
  end;

  TNodeDataInfo = packed record
    SelectedIndex: Integer;
    StateIndex: Integer;
    OverlayIndex: Integer;
    Data: Pointer;
    Count: Integer;
    NodeType: TNodeType;
    Checked: Boolean;
    Tag: Integer;
    IsExpanded: Boolean;
    // WideString Text of TextLen chars follows
    TextLen: Byte;
    HintLen: Byte;
    URLLen: Byte;
    ImageURLLen: Byte;
    ImageCollapsedURLLen: Byte;
  end;

  TAdvTreeNodeClass = class of TAdvTreeNode;

  TAdvTreeNode = class(TPersistent)
  private
    FOwner: TAdvTreeNodes;
    FText: string;
    FData: TObject;
    FImageIndex: TImageIndex;
    FDeleting: Boolean;
    FIsExpanded: Boolean;
    FFirstChild: TAdvTreeNode;
    FNextSibling: TAdvTreeNode;
    FPrevSibling: TAdvTreeNode;
    FParentNode: TAdvTreeNode;
    FShowText: Boolean;
    FImageCollapsedURL: string;
    FImageURL: string;
    FNodeType: TNodeType;
    FURL: string;
    FChecked: boolean;
    FTag: Integer;
    FHint: string;
    function GetLevel: Integer;
    function GetParent: TAdvTreeNode;
    function GetIndex: Integer;
    function GetItem(Index: Integer): TAdvTreeNode;
    function GetCount: Integer;
    function IsEqual(Node: TAdvTreeNode): Boolean;
    procedure SetData(Value: TObject);
    procedure SetItem(Index: Integer; Value: TAdvTreeNode);
    procedure SetText(const S: string);
    function GetHasChildren: Boolean;
    procedure SetShowText(const Value: Boolean);
    procedure ReadData(Stream: TStream; var Info: TNodeDataInfo);
    procedure WriteData(Stream: TStream; var Info: TNodeDataInfo);
    procedure SetChecked(const Value: boolean);
    procedure SetImageCollapsedURL(const Value: string);
    procedure SetImageURL(const Value: string);
    procedure SetNodeType(const Value: TNodeType);
    procedure SetURL(const Value: string);
    procedure SetTag(const Value: Integer);
    procedure SetHint(const Value: string);
  protected
    function CompareCount(CompareMe: Integer): Boolean;

    property Owner: TAdvTreeNodes read FOwner;
  public

    property Item[Index: Integer]: TAdvTreeNode read GetItem write SetItem; default;

    function IndexOf(Value: TAdvTreeNode): Integer;
    property Count: Integer read GetCount;
    property Index: Integer read GetIndex;

    constructor Create(AOwner: TAdvTreeNodes);
    destructor Destroy; override;

    function AddFirstChild(const S: string): TAdvTreeNode; overload;
    function AddFirstChild(Data: TObject; const S: string): TAdvTreeNode; overload;
    function AddChild(const S: string): TAdvTreeNode; overload;
    function AddChild(Data: TObject; const S: string): TAdvTreeNode; overload;
    procedure Assign(Source: TPersistent); override;

    procedure Delete;
    procedure DeleteChildren;

    function getFirstChild: TAdvTreeNode;
    function GetLastChild: TAdvTreeNode;
    function GetNextChild(Value: TAdvTreeNode): TAdvTreeNode;
    function getNextSibling: TAdvTreeNode;
    function GetPrevChild(Value: TAdvTreeNode): TAdvTreeNode;
    function getPrevSibling: TAdvTreeNode;
    function HasAsParent(Value: TAdvTreeNode): Boolean;
    function IsFirstNode: Boolean;
    function IsFirstChild: Boolean;

    property Deleting: Boolean read FDeleting;
    property IsExpanded: Boolean read FIsExpanded write FIsExpanded;
    property HasChildren: Boolean read GetHasChildren;
    property Level: Integer read GetLevel;
    property Parent: TAdvTreeNode read GetParent;
    property Text: string read FText write SetText;
    property Data: TObject read FData write SetData;
    property ShowText: Boolean read FShowText write SetShowText default true;
    property NodeType: TNodeType read FNodeType write SetNodeType default ntText;
    property Checked: boolean read FChecked write SetChecked default False;
    property URL: string read FURL write SetURL;

    property ImageCollapsedURL: string read FImageCollapsedURL write SetImageCollapsedURL;
    property ImageURL: string read FImageURL write SetImageURL;
    property Tag: Integer read FTag write SetTag default 0;
    property Hint: string read FHint write SetHint;
  end;

{ TAdvTreeNodes }

  TAdvTreeNodes = class(TPersistent)
  private
    FOwner: TComponent;
    FFirstNode: TAdvTreeNode;
    FNodeList: TDbgList;
    FDeleting: Boolean;
    FReading: Boolean;
    FOnChange: TNotifyEvent;
    function GetNodeFromIndex(Index: Integer): TAdvTreeNode;
    procedure ReadData(Stream: TStream);
    procedure WriteData(Stream: TStream);
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure SetItem(Index: Integer; Value: TAdvTreeNode);
    function GetCount: Integer;
    function InsertNode(ParentNode: TAdvTreeNode; DesNode: TAdvTreeNode; InsertBefore: Boolean): TAdvTreeNode;
    function IsVeryFirstNode(Node: TAdvTreeNode): Boolean;
    function CreateNode: TAdvTreeNode;
    property Reading: Boolean read FReading;
  public
    constructor Create(AOwner: TComponent);
    destructor Destroy; override;
    function AddFirst(const S: string): TAdvTreeNode; overload;
    function AddFirst(Data: TObject; const S: string): TAdvTreeNode; overload;
    function Add(const S: string): TAdvTreeNode; overload;
    function Add(Data: TObject; const S: string): TAdvTreeNode; overload;

    procedure Assign(Source: TPersistent); override;
    procedure Clear;
    procedure Delete(Node: TAdvTreeNode);
    function GetFirstNode: TAdvTreeNode;
    function GetLastNode: TAdvTreeNode;
    function Insert(Sibling: TAdvTreeNode; const S: string): TAdvTreeNode;
    function InsertObject(Sibling: TAdvTreeNode; const S: string; Data: TObject): TAdvTreeNode;
    property Count: Integer read GetCount;
    property Item[Index: Integer]: TAdvTreeNode read GetNodeFromIndex; default;
    property Owner: TComponent read FOwner;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    procedure Changed;
  end;

implementation

uses
  Math;
//------------------------------------------------------------------------------

{ TDbgList }

procedure TDbgList.AssignList(ListA: TList);
var
  I: Integer;
begin
  Clear;
  for I := 0 to ListA.Count - 1 do
    Add(ListA[I]);
end;

//------------------------------------------------------------------------------

function TDbgList.GetItemsEx(Index: Integer): TAdvTreeNode;
begin
  if (Index >= Count) then
  begin
    raise Exception.Create('Index out of bounds in list read access');
  end;

  if Index < Count then
    Result := inherited Items[Index]
  else
    Result := nil;
end;

//------------------------------------------------------------------------------

procedure TDbgList.SetItemsEx(Index: Integer; const Value: TAdvTreeNode);
begin
  if (Index >= Count) then
  begin
    raise Exception.Create('Index out of bounds in list write access');
  end;
  if Index < Count then
    inherited Items[Index] := value;
end;

//------------------------------------------------------------------------------

{ TAdvTreeNode }

constructor TAdvTreeNode.Create(AOwner: TAdvTreeNodes);
begin
  inherited Create;
  FOwner := AOwner;
  FShowText := true;
  FImageIndex := -1;
  FNodeType := ntText;
  FChecked := False;
  FIsExpanded := True;
  FTag := 0;
end;

//------------------------------------------------------------------------------

destructor TAdvTreeNode.Destroy;
var
  N, PN, NN: TAdvTreeNode;
  i: Integer;
begin
  FDeleting := True;

  DeleteChildren;

  N := Parent;
  if (((N <> nil) and (not N.Deleting)) or (N = nil)) and (not Owner.FDeleting) and Assigned(Owner) and Assigned(Owner.Owner) and not (csDestroying in Owner.Owner.ComponentState) then
  begin
    PN := getPrevSibling;
    NN := getNextSibling;

    if not Assigned(NN) then
    begin
      if IsFirstNode then
        Owner.FFirstNode := nil
      else if Assigned(Parent) and IsFirstChild then
        Parent.FFirstChild := nil
      else
      begin
        if Assigned(PN) then
          PN.FNextSibling := nil;
      end;  
    end
    else if not Assigned(PN) then
    begin
      if IsFirstNode then
        Owner.FFirstNode := NN
      else if Assigned(Parent) and IsFirstChild then
        Parent.FFirstChild := NN;
    end
    else     
    begin
      PN.FNextSibling := NN;
      NN.FPrevSibling := PN;
    end;
  end;

  if not Assigned(Parent) then
  begin
    i := Owner.FNodeList.IndexOf(Self);
    if (i >= 0) then
      Owner.FNodeList.Delete(i);
  end;

  Data := nil;
  inherited Destroy;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.HasAsParent(Value: TAdvTreeNode): Boolean;
begin
  if Value <> Nil then
  begin
    if Parent = nil then Result := False
    else if Parent = Value then Result := True
    else Result := Parent.HasAsParent(Value);
  end
  else Result := True;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetTag(const Value: Integer);
begin
  FTag := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetText(const S: string);
begin
  if not Deleting and (S <> Text) then
  begin
    FText := S;
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetURL(const Value: string);
begin
  FURL := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetChecked(const Value: boolean);
begin
  FChecked := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetData(Value: TObject);
begin
  if not Deleting and (Value <> Data) then
  begin
    FData := Value;
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetHint(const Value: string);
begin
  FHint := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetImageCollapsedURL(const Value: string);
begin
  FImageCollapsedURL := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetImageURL(const Value: string);
begin
  FImageURL := Value;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.CompareCount(CompareMe: Integer): Boolean;
var
  Count: integer;
  Node: TAdvTreeNode;
Begin
  Count := 0;
  Result := False;
  Node := GetFirstChild;
  while Node <> nil do
  begin
    Inc(Count);
    Node := Node.GetNextChild(Node);
    if Count > CompareMe then
      Exit;
  end;
  if Count = CompareMe then
    Result := True;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetHasChildren: Boolean;
begin
  Result := Assigned(FFirstChild);
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetParent: TAdvTreeNode;
begin
  Result := FParentNode;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetNextSibling: TAdvTreeNode;
begin
  Result := FNextSibling;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetPrevSibling: TAdvTreeNode;
begin
  Result := FPrevSibling;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetNextChild(Value: TAdvTreeNode): TAdvTreeNode;
begin
  if Value <> nil then Result := Value.GetNextSibling
  else Result := nil;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetPrevChild(Value: TAdvTreeNode): TAdvTreeNode;
begin
  if Value <> nil then Result := Value.GetPrevSibling
  else Result := nil;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetFirstChild: TAdvTreeNode;
begin
  Result := FFirstChild;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetLastChild: TAdvTreeNode;
var
  Node: TAdvTreeNode;
begin
  Result := GetFirstChild;
  if Result <> nil then
  begin
    Node := Result;
    repeat
      Result := Node;
      Node := Result.GetNextSibling;
    until Node = nil;
  end;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetIndex: Integer;
var
  Node: TAdvTreeNode;
begin
  Result := -1;
  Node := Self;
  while Node <> nil do
  begin
    Inc(Result);
    Node := Node.GetPrevSibling;
  end;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetItem(Index: Integer): TAdvTreeNode;
begin
  Result := GetFirstChild;
  while (Result <> nil) and (Index > 0) do
  begin
    Result := GetNextChild(Result);
    Dec(Index);
  end;
  if Result = nil then raise Exception.Create('Invalid index');
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetItem(Index: Integer; Value: TAdvTreeNode);
begin
  item[Index].Assign(Value);
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetNodeType(const Value: TNodeType);
begin
  FNodeType := Value;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.SetShowText(const Value: Boolean);
begin
  FShowText := Value;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.IndexOf(Value: TAdvTreeNode): Integer;
var
  Node: TAdvTreeNode;
begin
  Result := -1;
  Node := GetFirstChild;
  while (Node <> nil) do
  begin
    Inc(Result);
    if Node = Value then Break;
    Node := GetNextChild(Node);
  end;
  if Node = nil then Result := -1;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetCount: Integer;
var
  Node: TAdvTreeNode;
begin
  Result := 0;
  Node := GetFirstChild;
  while Node <> nil do
  begin
    Inc(Result);
    Node := Node.GetNextChild(Node);
  end;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.IsFirstNode: Boolean;
begin
  Result := Self = Owner.FFirstNode; // not Deleting and (Parent = nil) and (GetPrevSibling = nil);
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.IsFirstChild: Boolean;
begin
  Result := IsFirstNode or (Assigned(Parent) and (Parent.getFirstChild = Self));
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.GetLevel: Integer;
var
  Node: TAdvTreeNode;
begin
  Result := 0;
  Node := Parent;
  while Node <> nil do
  begin
    Inc(Result);
    Node := Node.Parent;
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.Delete;
begin
  if not Deleting then
    Free;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.DeleteChildren;
var
  ChildList: TDbgList;
  N: TAdvTreeNode;
  i: Integer;
begin
  ChildList := TDbgList.Create;
  N := GetFirstChild;
  if N <> nil then
  begin
    repeat
      ChildList.Add(N);
      N := N.GetNextSibling;
    until N = nil;
  end;

  for i := ChildList.Count - 1 downto 0 do
  begin
    ChildList[i].Delete;
  end;
  ChildList.Free;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.AddChild(const S: string): TAdvTreeNode;
begin
  Result := AddChild(nil, S);
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.AddChild(Data: TObject; const S: string): TAdvTreeNode;
var
  N: TAdvTreeNode;
begin
  N := GetLastChild;
  if Assigned(N) then
    Result := Owner.InsertNode(nil, N, False)
  else
    Result := Owner.InsertNode(Self, nil, False);
  Result.Text := S;
  Result.Data := Data;
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.AddFirstChild(const S: string): TAdvTreeNode;
begin
  Result := AddFirstChild(nil, S);
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.AddFirstChild(Data: TObject; const S: string): TAdvTreeNode;
begin
  if not Assigned(FFirstChild) then
    Result := Owner.InsertNode(Self, nil, True)
  else
    Result := Owner.InsertNode(nil, FFirstChild, True);
  Result.Text := S;
  Result.Data := Data;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.Assign(Source: TPersistent);
var
  Node: TAdvTreeNode;
begin
  if not Deleting and (Source is TAdvTreeNode) then
  begin
    Node := TAdvTreeNode(Source);
    Text := Node.Text;
    Data := Node.Data;
    ShowText := Node.ShowText;
    NodeType := Node.NodeType;
    Checked := Node.Checked;
    URL := Node.URL;
    Hint := Node.Hint;
    ImageURL := Node.ImageURL;
    ImageCollapsedURL := Node.ImageCollapsedURL;
    Tag := Node.Tag;
    IsExpanded := Node.IsExpanded;
  end
  else
    inherited Assign(Source);
end;

//------------------------------------------------------------------------------

function TAdvTreeNode.IsEqual(Node: TAdvTreeNode): Boolean;
begin
  Result := (Text = Node.Text) and (Data = Node.Data);
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.ReadData(Stream: TStream; var Info: TNodeDataInfo);
var
  I, Size, ItemCount, SzM: Integer;
  LNode: TAdvTreeNode;
  s: string;
begin
  Stream.ReadBuffer(Size, SizeOf(Size));
  Stream.ReadBuffer(Info, SizeOf(TNodeDataInfo));

  {$IFDEF DELPHI_UNICODE}
  SzM := 2;
  {$ENDIF}

  {$IFNDEF DELPHI_UNICODE}
  SzM := 1;
  {$ENDIF}

  SetLength(S, Info.TextLen);
  Stream.ReadBuffer(S[1], SzM * Info.TextLen);
  Text := S;

  SetLength(S, Info.HintLen);
  Stream.ReadBuffer(S[1], SzM * Info.HintLen);
  Hint := S;

  SetLength(S, Info.URLLen);
  Stream.ReadBuffer(S[1], SzM * Info.URLLen);
  URL := S;

  SetLength(S, Info.ImageURLLen);
  Stream.ReadBuffer(S[1], SzM * Info.ImageURLLen);
  ImageURL := S;

  SetLength(S, Info.ImageCollapsedURLLen);
  Stream.ReadBuffer(S[1], SzM * Info.ImageCollapsedURLLen);
  ImageCollapsedURL := S;

  NodeType := Info.NodeType;
  Checked := Info.Checked;
  Tag := Info.Tag;
  IsExpanded := Info.IsExpanded;

  ItemCount := Info.Count;
  for I := 0 to ItemCount - 1 do
  begin
    LNode := AddChild(''); //Owner.Add(Self, '');
    LNode.FParentNode := Self;
    LNode.ReadData(Stream, Info);
    //Owner.Owner.Added(LNode);
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNode.WriteData(Stream: TStream; var Info: TNodeDataInfo);
var
  I, Size, ItemCount, SzM: Integer;
begin
  //L := Length(Text);
  //if L > 255 then L := 255;
  //Size := SizeOf(TNodeInfo) + L - 255;

  //L := Min(255, Length(Text)) + Min(255, Length(URL)) + Min(255, Length(Hint)) + Min(255, Length(ImageURL)) + Min(255, Length(ImageCollapsedURL));
  //Size := SizeOf(TNodeInfo) + L - 255 * 5;

  {$IFDEF DELPHI_UNICODE}
  SzM := 2;
  {$ENDIF}

  {$IFNDEF DELPHI_UNICODE}
  SzM := 1;
  {$ENDIF}

  Info.TextLen := Min(255, Length(Text));
  Info.HintLen := Min(255, Length(Hint));
  Info.URLLen := Min(255, Length(URL));
  Info.ImageURLLen := Min(255, Length(ImageURL));
  Info.ImageCollapsedURLLen := Min(255, Length(ImageCollapsedURL));

  Info.NodeType := NodeType;
  Info.Checked := Checked;
  Info.Tag := Tag;
  Info.IsExpanded := IsExpanded;

  ItemCount := Count;
  Info.Count := ItemCount;

  Size := SizeOf(TNodeDataInfo) + (Info.TextLen + Info.HintLen + Info.URLLen + Info.ImageURLLen + Info.ImageCollapsedURLLen);

  Stream.WriteBuffer(Size, SizeOf(Size));
  Stream.WriteBuffer(Info, SizeOf(TNodeDataInfo));
  Stream.WriteBuffer(Text[1], SzM * Info.TextLen);
  Stream.WriteBuffer(Hint[1], SzM * Info.HintLen);
  Stream.WriteBuffer(URL[1], SzM * Info.URLLen);
  Stream.WriteBuffer(ImageURL[1], SzM * Info.ImageURLLen);
  Stream.WriteBuffer(ImageCollapsedURL[1], SzM * Info.ImageCollapsedURLLen);

  for I := 0 to ItemCount - 1 do
    Item[I].WriteData(Stream, Info);
end;

//------------------------------------------------------------------------------

{ TAdvTreeNodes }

constructor TAdvTreeNodes.Create(AOwner: TComponent);
begin
  inherited Create;
  FOwner := AOwner;
  FFirstNode := nil;
  FNodeList :=  TDbgList.Create;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.CreateNode: TAdvTreeNode;
var
  LClass: TAdvTreeNodeClass;
begin
  LClass := TAdvTreeNode;
  Result := LClass.Create(Self);
end;

//------------------------------------------------------------------------------

destructor TAdvTreeNodes.Destroy;
begin
  FDeleting := True;
  Clear;
  FNodeList.Free;
  inherited Destroy;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.Delete(Node: TAdvTreeNode);
begin
  Node.Delete;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.Changed;
begin
  if Assigned(OnChange) then
    OnChange(Self);
end;

procedure TAdvTreeNodes.Clear;
var
  i: Integer;
begin
  for i := FNodeList.Count-1 downto 0 do
    FNodeList[i].Delete;

  FNodeList.Clear;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.GetCount: Integer;
begin
  Result := FNodeList.Count;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.GetNodeFromIndex(Index: Integer): TAdvTreeNode;
begin
  Result := nil;
  if (Index >= 0) and (Index < FNodeList.Count) then
    Result := FNodeList[Index];
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.SetItem(Index: Integer; Value: TAdvTreeNode);
begin
  if (Index >= 0) and (Index < FNodeList.Count) then
    GetNodeFromIndex(Index).Assign(Value);
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.Insert(Sibling: TAdvTreeNode; const S: string): TAdvTreeNode;
begin
  Result := InsertObject(Sibling, S, nil);
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.InsertObject(Sibling: TAdvTreeNode; const S: string;
  Data: TObject): TAdvTreeNode;
begin
  Result := InsertNode(nil, Sibling, True);
  if Assigned(Result) then
  begin
    Result.Text := S;
    Result.Data := Data;
  end;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.IsVeryFirstNode(Node: TAdvTreeNode): Boolean;
begin
  Result := Assigned(Node) and (Node = FFirstNode);  
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.GetFirstNode: TAdvTreeNode;
begin
  Result := FFirstNode;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.GetLastNode: TAdvTreeNode;
begin
  Result := FFirstNode;
  if Assigned(Result) then
    while (Result.getNextSibling <> nil) do
      Result := Result.getNextSibling;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.Add(const S: string): TAdvTreeNode;
begin
  Result := Add(nil, S);
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.Add(Data: TObject; const S: string): TAdvTreeNode;
begin
  Result := InsertNode(nil, GetLastNode, False);
  Result.Text := S;
  Result.Data := Data;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.AddFirst(const S: string): TAdvTreeNode;
begin
  Result := AddFirst(nil, S);
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.AddFirst(Data: TObject; const S: string): TAdvTreeNode;
begin
  if not Assigned(FFirstNode) then
  begin
    //Result := CreateNode;
    //FFirstNode := Result
    Result := InsertNode(nil, nil, True);
  end
  else
  begin
    Result := InsertNode(nil, FFirstNode, True);
  end;
  Result.Text := S;
  Result.Data := Data;
end;

//------------------------------------------------------------------------------

function TAdvTreeNodes.InsertNode(ParentNode: TAdvTreeNode; DesNode: TAdvTreeNode; InsertBefore: Boolean): TAdvTreeNode;
var
  N: TAdvTreeNode;
begin
  Result := nil;
  if not Assigned(DesNode) then
  begin
    if Assigned(ParentNode) then
    begin
      N := ParentNode.GetLastChild;
      if Assigned(N) then
      begin
        InsertNode(nil, N, False);
        Exit;
      end
      else  // First Child Node
      begin
        Result := CreateNode;
        ParentNode.FFirstChild := Result;
        Result.FParentNode := ParentNode;
      end;
    end
    else
    begin
      if not Assigned(FFirstNode) then
      begin
        Result := CreateNode;
        FFirstNode := Result;  // very first Node
        FNodeList.Add(Result);
      end;
    end;
  end
  else
  begin
    Result := CreateNode;
    if InsertBefore then
    begin
      if IsVeryFirstNode(DesNode) then
      begin
        FFirstNode := Result;
        Result.FNextSibling := DesNode;
        DesNode.FPrevSibling := Result;
        FNodeList.Add(Result);
      end
      else
      begin
//fix: if DesNode is not the first node in a treeview, but a first child in a parent node (with DesNode.FPrevSibling = nil),
//then execution continues on "else" branch and DesNode.Parent.FFirstChild does not change
//- it remains pointing to DesNode, not on newly inserted node
//        if DesNode.IsFirstNode and Assigned(DesNode.Parent) then
        if DesNode.IsFirstChild and Assigned(DesNode.Parent) then
        begin
          DesNode.Parent.FFirstChild := Result;
          Result.FNextSibling := DesNode;
          DesNode.FPrevSibling := Result;
          Result.FParentNode := DesNode.Parent;
        end
        else
        begin
          Result.FPrevSibling := DesNode.FPrevSibling;
          Result.FNextSibling := DesNode;
          DesNode.FPrevSibling := Result;
          Result.FParentNode := DesNode.Parent;
          if Assigned(Result.FPrevSibling) then
            Result.FPrevSibling.FNextSibling := Result;
        end;
      end;
    end
    else // Insert after DesNode
    begin
      Result.FNextSibling := DesNode.FNextSibling;
      Result.FPrevSibling := DesNode;
      DesNode.FNextSibling := Result;
      if Assigned(Result.FNextSibling) then
        Result.FNextSibling.FPrevSibling := Result;
      Result.FParentNode := DesNode.Parent;
      if not Assigned(DesNode.Parent) then  // Root Node
        FNodeList.Add(Result);
    end;
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.Assign(Source: TPersistent);
var
  TreeNodes: TAdvTreeNodes;
  MemStream: TMemoryStream;
begin
  if Source is TAdvTreeNodes then
  begin
    TreeNodes := TAdvTreeNodes(Source);
    Clear;
    MemStream := TMemoryStream.Create;
    try
      TreeNodes.WriteData(MemStream);
      MemStream.Position := 0;
      ReadData(MemStream);
    finally
      MemStream.Free;
    end;
  end
  else inherited Assign(Source);
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.DefineProperties(Filer: TFiler);

  function WriteNodes: Boolean;
  var
    I: Integer;
    Nodes: TAdvTreeNodes;
  begin
    Nodes := TAdvTreeNodes(Filer.Ancestor);
    if Nodes = nil then
      Result := Count > 0
    else if Nodes.Count <> Count then
      Result := True
    else
    begin
      Result := False;
      for I := 0 to Count - 1 do
      begin
        Result := not Item[I].IsEqual(Nodes[I]);
        if Result then
          Break;
      end
    end;
  end;

begin
  inherited DefineProperties(Filer);
  Filer.DefineBinaryProperty('Data', ReadData, WriteData, WriteNodes);
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.ReadData(Stream: TStream);
var
  I, Count: Integer;
  NodeInfo: TNodeDataInfo;
  LNode: TAdvTreeNode;
begin
  FReading := True;
  try
    Clear;
    Stream.ReadBuffer(Count, SizeOf(Count));
    for I := 0 to Count - 1 do
    begin
      LNode := Add('');
      LNode.ReadData(Stream, NodeInfo);
    end;
  finally
    FReading := False;
  end;
end;

//------------------------------------------------------------------------------

procedure TAdvTreeNodes.WriteData(Stream: TStream);
var
  I: Integer;
  Node: TAdvTreeNode;
  NodeInfo: TNodeDataInfo;
begin
  I := 0;
  Node := GetFirstNode;
  while Node <> nil do
  begin
    Inc(I);
    Node := Node.GetNextSibling;
  end;
  Stream.WriteBuffer(I, SizeOf(I));
  Node := GetFirstNode;
  while Node <> nil do
  begin
    Node.WriteData(Stream, NodeInfo);
    Node := Node.GetNextSibling;
  end;
end;

//------------------------------------------------------------------------------

end.
