1{%MainUnit ../comctrls.pp}
2
3{******************************************************************************
4                                  TTreeView
5 ******************************************************************************
6
7  Author: Mattias Gaertner
8
9 *****************************************************************************
10  This file is part of the Lazarus Component Library (LCL)
11
12  See the file COPYING.modifiedLGPL.txt, included in this distribution,
13  for details about the license.
14 *****************************************************************************
15
16  Abstract:
17    TTreeView for LCL
18}
19
20
21{ $DEFINE TREEVIEW_DEBUG}
22
23const
24  TTreeNodeWithPointerStreamVersion : word = 1;
25  TTreeNodeStreamVersion : word = 2;
26  TVAutoHeightString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789jgpq|\()^';
27  MinNodeCapacity = 10;
28
29function CompareExpandedNodes(Data1, Data2: Pointer): integer;
30var
31  Node1: TTreeNodeExpandedState;
32  Node2: TTreeNodeExpandedState;
33begin
34  Node1:=TTreeNodeExpandedState(Data1);
35  Node2:=TTreeNodeExpandedState(Data2);
36  Result:=AnsiCompareText(Node1.NodeText,Node2.NodeText);
37end;
38
39function CompareTextWithExpandedNode(Key, Data: Pointer): integer;
40var
41  NodeText: String;
42  Node: TTreeNodeExpandedState;
43begin
44  NodeText:=String(Key);
45  Node:=TTreeNodeExpandedState(Data);
46  Result:=AnsiCompareText(NodeText,Node.NodeText);
47end;
48
49procedure TreeViewError(const Msg: string);
50begin
51  raise ETreeViewError.Create(Msg);
52end;
53
54procedure TreeNodeError(const Msg: string);
55begin
56  raise ETreeNodeError.Create(Msg);
57end;
58
59procedure TreeNodeErrorFmt(const Msg: string; Format: array of const);
60begin
61  raise ETreeNodeError.CreateFmt(Msg, Format);
62end;
63
64function IndexOfNodeAtTop(NodeArray: TTreeNodeArray; Count, y: integer): integer;
65// NodeArray must be sorted via Top
66// returns index of Node with Node.Top <= y < Node[+1].Top
67var
68  l, m, r, VisibleCount: integer;
69  VisibleNodesAlloc: Boolean;
70  VisibleNodes: TTreeNodeArray;
71begin
72  if (Count = 0) or (NodeArray = nil) then
73    Exit(-1);
74  // Count the visible nodes
75  VisibleCount := 0;
76  VisibleNodesAlloc := False;
77  for l := 0 to Count-1 do
78    if NodeArray[l].Visible then
79      Inc(VisibleCount);
80  try
81    // Make a temporary array of visible nodes if there are hidden nodes
82    if VisibleCount < Count then begin
83      GetMem(VisibleNodes,SizeOf(Pointer)*VisibleCount);
84      m := 0;
85      for l := 0 to Count-1 do
86        if NodeArray[l].Visible then begin
87          VisibleNodes[m] := NodeArray[l];
88          Inc(m);
89        end;
90      Count := VisibleCount;
91      VisibleNodesAlloc := True;
92    end
93    else
94      VisibleNodes := NodeArray;
95    // Binary search for the Y coordinate
96    l := 0;
97    r := Count - 1;
98    while (l <= r) do
99    begin
100      m := (l + r) shr 1;
101      //DebugLn(':0 [IndexOfNodeAtTop] m=',m,' y=',y,' ',NodeArray[m].Text,' NodeArray[m].Top=',NodeArray[m].Top,' NodeArray[m].BottomExpanded=',NodeArray[m].BottomExpanded);
102      if VisibleNodes[m].Top > y then
103        r := m - 1
104      else if VisibleNodes[m].BottomExpanded <= y then
105        l := m + 1
106      else
107        Exit(VisibleNodes[m].Index);
108    end;
109    Result := -1;
110  finally
111    if VisibleNodesAlloc then
112      Freemem(VisibleNodes);
113  end;
114end;
115
116// procedure for sorting a TTreeNodeArray
117procedure Sort(Nodes: TTreeNodeArray; Count: integer;
118  SortProc: TTreeNodeCompare; UpdateIndex: Boolean);
119// Sorts the nodes using merge sort and updates the sibling links
120var
121  Buffer: TTreeNodeArray;
122  i: Integer;
123
124  procedure MergeNodeArrays(Pos1, Pos2, Pos3: integer);
125  // merge two sorted arrays (result is in Src)
126  // the first array ranges Pos1..Pos2-1, the second ranges Pos2..Pos3
127  var Src1Pos,Src2Pos,DestPos,cmp,a:integer;
128  begin
129    if (Pos1>=Pos2) or (Pos2>Pos3) then exit;
130    Src1Pos:=Pos2-1;
131    Src2Pos:=Pos3;
132    DestPos:=Pos3;
133    while (Src2Pos>=Pos2) and (Src1Pos>=Pos1) do begin
134      cmp:=SortProc(Nodes[Src1Pos],Nodes[Src2Pos]);
135      if cmp>0 then begin
136        Buffer[DestPos]:=Nodes[Src1Pos];
137        dec(Src1Pos);
138      end else begin
139        Buffer[DestPos]:=Nodes[Src2Pos];
140        dec(Src2Pos);
141      end;
142      dec(DestPos);
143    end;
144    while Src2Pos>=Pos2 do begin
145      Buffer[DestPos]:=Nodes[Src2Pos];
146      dec(Src2Pos);
147      dec(DestPos);
148    end;
149    for a:=DestPos+1 to Pos3 do
150      Nodes[a]:=Buffer[a];
151  end;
152
153  procedure MergeSort(StartPos, EndPos: integer);
154  // sort Src from Position StartPos to EndPos (both included)
155  var cmp,mid:integer;
156  begin
157    if StartPos>=EndPos then begin
158      // sort one element -> very easy :)
159    end else if StartPos+1=EndPos then begin
160      // sort two elements -> quite easy :)
161      cmp:=SortProc(Nodes[StartPos],Nodes[EndPos]);
162      if cmp>0 then begin
163        Buffer[StartPos]:=Nodes[StartPos];
164        Nodes[StartPos]:=Nodes[EndPos];
165        Nodes[EndPos]:=Buffer[StartPos];
166      end;
167    end else begin
168      // sort more than two elements -> Mergesort
169      mid:=(StartPos+EndPos) shr 1;
170      MergeSort(StartPos,mid);
171      MergeSort(mid+1,EndPos);
172      MergeNodeArrays(StartPos,mid+1,EndPos);
173    end;
174  end;
175
176begin
177  if Count>0 then begin
178    Buffer := GetMem(SizeOf(Pointer)*Count);
179    MergeSort(0,Count-1);
180    FreeMem(Buffer);
181    // update sibling links
182    Nodes[0].FPrevBrother := nil;
183    Nodes[Count-1].FNextBrother := nil;
184    if UpdateIndex then Nodes[0].FIndex:=0;
185    for i:= 1 to Count-1 do begin
186      Nodes[i].FPrevBrother := Nodes[i-1];
187      Nodes[i-1].FNextBrother := Nodes[i];
188      if UpdateIndex then Nodes[i].FIndex:=i;
189    end;
190  end;
191end;
192
193{ TTreeNodeExpandedState }
194
195function TTreeNodeExpandedState.DefaultGetNodeText(Node: TTreeNode): string;
196begin
197  Result:=Node.Text;
198end;
199
200constructor TTreeNodeExpandedState.Create(FirstTreeNode: TTreeNode;
201  const GetNodeTextEvent: TTVGetNodeText);
202begin
203  if GetNodeTextEvent<>nil then
204    FOnGetNodeText:=GetNodeTextEvent
205  else
206    FOnGetNodeText:=@DefaultGetNodeText;
207  CreateChildNodes(FirstTreeNode);
208end;
209
210constructor TTreeNodeExpandedState.Create(TreeView: TCustomTreeView;
211  const GetNodeTextEvent: TTVGetNodeText);
212begin
213  if GetNodeTextEvent<>nil then
214    FOnGetNodeText:=GetNodeTextEvent
215  else
216    FOnGetNodeText:=@DefaultGetNodeText;
217  CreateChildNodes(TreeView.Items.GetFirstNode);
218end;
219
220destructor TTreeNodeExpandedState.Destroy;
221begin
222  Clear;
223  inherited Destroy;
224end;
225
226procedure TTreeNodeExpandedState.Clear;
227begin
228  if Children<>nil then begin
229    Children.FreeAndClear;
230    FreeThenNil(Children);
231  end;
232end;
233
234procedure TTreeNodeExpandedState.CreateChildNodes(FirstTreeNode: TTreeNode);
235var
236  ChildNode: TTreeNode;
237  NewExpandedNode: TTreeNodeExpandedState;
238begin
239  Clear;
240  if (FirstTreeNode=nil) then exit;
241  if (FirstTreeNode.Parent<>nil) then
242    NodeText:=OnGetNodeText(FirstTreeNode.Parent)
243  else
244    NodeText:='';
245  ChildNode:=FirstTreeNode;
246  while ChildNode<>nil do begin
247    if ChildNode.Expanded then begin
248      if Children=nil then Children:=TAvlTree.Create(@CompareExpandedNodes);
249      NewExpandedNode:=TTreeNodeExpandedState.Create(ChildNode.GetFirstChild,OnGetNodeText);
250      if ChildNode.GetFirstChild=nil then
251        NewExpandedNode.NodeText:=OnGetNodeText(ChildNode);
252      Children.Add(NewExpandedNode);
253    end;
254    ChildNode:=ChildNode.GetNextSibling;
255  end;
256end;
257
258procedure TTreeNodeExpandedState.Apply(FirstTreeNode: TTreeNode; CollapseToo: boolean);
259var
260  ChildNode: TTreeNode;
261  ANode: TAvlTreeNode;
262  ChildNodeText: String;
263begin
264  if Children=nil then exit;
265  ChildNode:=FirstTreeNode;
266  while ChildNode<>nil do begin
267    ChildNodeText:=OnGetNodeText(ChildNode);
268    ANode:=Children.FindKey(PChar(ChildNodeText),@CompareTextWithExpandedNode);
269    if ChildNodeText='' then
270      debugln(['TTreeNodeExpandedState.Apply ',ChildNode.GetTextPath,' ChildNodeText="',ChildNodeText,'"']);
271    if ANode<>nil then
272      ChildNode.Expanded:=true
273    else if CollapseToo then
274      ChildNode.Expanded:=false;
275    if ANode<>nil then
276      TTreeNodeExpandedState(ANode.Data).Apply(ChildNode.GetFirstChild,CollapseToo);
277    ChildNode:=ChildNode.GetNextSibling;
278  end;
279end;
280
281procedure TTreeNodeExpandedState.Apply(TreeView: TCustomTreeView; CollapseToo: boolean);
282begin
283  Apply(TreeView.Items.GetFirstNode,CollapseToo);
284end;
285
286{ TTreeNode }
287
288function TTreeNode.DefaultTreeViewSort(Node1, Node2: TTreeNode): Integer;
289begin
290  if (Node1.TreeView<>nil) and  Assigned(Node1.TreeView.OnCompare) then begin
291    Result:=0;
292    Node1.TreeView.OnCompare(Node1.TreeView,Node1, Node2, Result);
293  end else
294    {$IFDEF UNIX}
295    Result := CompareStr(Node1.Text, Node2.Text);
296    {$ELSE}
297    Result := AnsiCompareStr(Node1.Text,Node2.Text);
298    {$ENDIF}
299end;
300
301constructor TTreeNode.Create(AnOwner: TTreeNodes);
302begin
303  inherited Create;
304  FNodeEffect := gdeNormal;
305  FImageIndex := -1;
306  FOverlayIndex := -1;
307  FSelectedIndex := -1;
308  FStateIndex := -1;
309  FStates := [nsVisible];
310  FOwner := AnOwner;
311  FSubTreeCount := 1;
312  FIndex := -1;
313end;
314
315destructor TTreeNode.Destroy;
316var
317  lOwnerAccessibleObject, lAccessibleObject: TLazAccessibleObject;
318begin
319  {$IFDEF TREEVIEW_DEBUG}
320  DebugLn('[TTreeNode.Destroy] Self=',DbgS(Self),' Self.Text=',Text);
321  {$ENDIF}
322  Include(FStates,nsDeleting);
323
324  // we must trigger TCustomTreeView.OnDeletion event before
325  // unbinding. See issue #17832.
326  if Assigned(Owner) and Assigned(Owner.Owner) then
327  begin
328    Owner.Owner.Delete(Self);
329    Include(Owner.Owner.FStates, tvsScrollbarChanged);;
330    Owner.Owner.UpdateScrollbars;
331  end;
332
333  // Remove the accessibility object too
334  if Assigned(Owner) and Assigned(Owner.Owner) then
335  begin
336    lOwnerAccessibleObject := Owner.Owner.GetAccessibleObject();
337    if lOwnerAccessibleObject<>nil then
338    begin
339      lAccessibleObject := lOwnerAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
340      if lAccessibleObject <> nil then
341        lOwnerAccessibleObject.RemoveChildAccessibleObject(lAccessibleObject);
342    end;
343  end;
344
345  // delete children
346  HasChildren := false;
347  // unbind all references
348  Unbind;
349  if Assigned(Owner) then begin
350    if FStates * [nsSelected, nsMultiSelected] <> [] then
351      Owner.FSelection.Remove(Self);
352  end;
353
354  Data := nil;
355  // free data
356  if Assigned(FItems) then
357  begin
358    FreeMem(FItems);
359    FItems := nil;
360  end;
361  inherited Destroy;
362end;
363
364function TTreeNode.GetHandle: THandle;
365begin
366  if TreeView<>nil then
367    Result := TreeView.Handle
368  else
369    Result := 0;
370end;
371
372function TTreeNode.GetParentNodeOfAbsoluteLevel(
373  TheAbsoluteLevel: integer): TTreeNode;
374var
375  i: integer;
376  l: LongInt;
377begin
378  l:=Level;
379  if (TheAbsoluteLevel > l) or (TheAbsoluteLevel < 0) then
380    Result := nil
381  else
382  begin
383    Result := Self;
384    for i := TheAbsoluteLevel to l-1 do
385      Result := Result.Parent;
386  end;
387end;
388
389function TTreeNode.GetTreeNodes: TTreeNodes;
390begin
391  if Owner is TTreeNodes then
392    Result:=TTreeNodes(Owner)
393  else
394    Result:=nil;
395end;
396
397function TTreeNode.GetTreeView: TCustomTreeView;
398begin
399  if Owner <> nil then
400    Result := Owner.Owner
401  else
402    Result := nil;
403end;
404
405function TTreeNode.GetTop: integer;
406begin
407  if TreeView <> nil then
408    TreeView.UpdateAllTops;
409  Result := FTop;
410end;
411
412function TTreeNode.GetVisible: Boolean;
413begin
414  Result:=nsVisible in FStates;
415end;
416
417function TTreeNode.HasAsParent(AValue: TTreeNode): Boolean;
418begin
419  if AValue<>nil then begin
420    if Parent=nil then Result := False
421    else if Parent=AValue then Result := True
422    else Result := Parent.HasAsParent(AValue);
423  end
424  else Result := True;
425end;
426
427procedure TTreeNode.SetText(const S: string);
428var
429  lSelfAX: TLazAccessibleObject;
430begin
431  if S=FText then exit;
432  FText := S;
433  if TreeView=nil then exit;
434  Include(TreeView.FStates,tvsMaxRightNeedsUpdate);
435  if (TreeView.SortType in [stText, stBoth])
436  and (nsBound in FStates) then begin
437    if (Parent <> nil) then Parent.AlphaSort
438    else TreeView.AlphaSort;
439  end;
440  Update;
441  Changed(ncTextChanged);
442  // Update accessibility information
443  lSelfAX := TreeView.GetAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
444  if lSelfAX <> nil then
445    lSelfAX.AccessibleValue := S;
446end;
447
448procedure TTreeNode.SetData(AValue: Pointer);
449begin
450  if FData=AValue then exit;
451  FData := AValue;
452  if (TreeView<>nil)
453  and (TreeView.SortType in [stData, stBoth]) and Assigned(TreeView.OnCompare)
454  and (not Deleting)
455  and (nsBound in FStates) then
456  begin
457    if Parent <> nil then
458      Parent.AlphaSort
459    else
460      TreeView.AlphaSort;
461  end;
462  Changed(ncDataChanged);
463end;
464
465function TTreeNode.GetState(NodeState: TNodeState): Boolean;
466begin
467  Result:=NodeState in FStates;
468end;
469
470procedure TTreeNode.SetHeight(AValue: integer);
471begin
472  if AValue<0 then AValue:=0;
473  if AValue=FHeight then exit;
474  FHeight:=AValue;
475  if TreeView<>nil then
476    TreeView.FStates:=TreeView.FStates+[tvsScrollbarChanged,tvsTopsNeedsUpdate];
477  Update;
478  Changed(ncHeightChanged);
479end;
480
481procedure TTreeNode.SetImageEffect(AValue: TGraphicsDrawEffect);
482begin
483  if FNodeEffect=AValue then exit;
484  FNodeEffect := AValue;
485  Update;
486  Changed(ncImageEffect);
487end;
488
489procedure TTreeNode.SetImageIndex(AValue: TImageIndex);
490begin
491  if FImageIndex=AValue then exit;
492  FImageIndex := AValue;
493  Update;
494  Changed(ncImageIndex);
495end;
496
497procedure TTreeNode.SetIndex(const AValue: Integer);
498
499  procedure RaiseMissingTreeNodes;
500  begin
501    TreeViewError('TTreeNode.SetIndex missing Owner');
502  end;
503
504var
505  OldIndex: LongInt;
506begin
507  OldIndex:=Index;
508  if OldIndex=AValue then exit;
509  if Parent=nil then begin
510    // moving a top level node
511    if Owner=nil then RaiseMissingTreeNodes;
512    if AValue=0 then
513      MoveTo(Owner.GetFirstNode,naInsert)
514    else if AValue<OldIndex then
515      MoveTo(Owner.TopLvlItems[AValue-1],naInsertBehind)
516    else
517      MoveTo(Owner.TopLvlItems[AValue],naInsertBehind);
518  end else begin
519    // moving a normal node
520    if AValue=0 then
521      MoveTo(Parent.GetFirstChild,naInsert)
522    else if AValue<OldIndex then
523      MoveTo(Parent.Items[AValue-1],naInsertBehind)
524    else
525      MoveTo(Parent.Items[AValue],naInsertBehind);
526  end;
527end;
528
529procedure TTreeNode.SetSelectedIndex(AValue: Integer);
530begin
531  if FSelectedIndex = AValue then exit;
532  FSelectedIndex := AValue;
533  Update;
534  Changed(ncSelectedIndex);
535end;
536
537procedure TTreeNode.SetVisible(const AValue: Boolean);
538begin
539  if Visible = AValue then exit;
540  if AValue then
541    Include(FStates,nsVisible)
542  else
543    Exclude(FStates,nsVisible);
544  Selected := False;
545  if TreeView<>nil then
546    TreeView.FStates:=TreeView.FStates+[tvsScrollbarChanged,tvsTopsNeedsUpdate,
547                                        tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
548                                        tvsMaxLvlNeedsUpdate,tvsMaxRightNeedsUpdate];
549  Update;
550  Changed(ncVisibility);
551end;
552
553procedure TTreeNode.SetOverlayIndex(AValue: Integer);
554begin
555  if FOverlayIndex = AValue then exit;
556  FOverlayIndex := AValue;
557  Update;
558  Changed(ncOverlayIndex)
559end;
560
561procedure TTreeNode.SetStateIndex(AValue: Integer);
562begin
563  if FStateIndex = AValue then exit;
564  FStateIndex := AValue;
565  Update;
566  Changed(ncStateIndex);
567end;
568
569function TTreeNode.AreParentsExpandedAndVisible: Boolean;
570var
571  ANode: TTreeNode;
572begin
573  Result:=false;
574  ANode:=Parent;
575  while ANode<>nil do begin
576    if not (ANode.Expanded and ANode.Visible) then exit;
577    ANode:=ANode.Parent;
578  end;
579  Result:=true;
580end;
581
582procedure TTreeNode.BindToMultiSelected;
583var
584  TheTreeNodes: TTreeNodes;
585  CurNode: TTreeNode;
586begin
587  TheTreeNodes:=TreeNodes;
588  if TheTreeNodes=nil then exit;
589
590  // Get the first selected node of the tree
591  CurNode := TheTreeNodes.FFirstMultiSelected;
592
593  // Initialize self unbinded
594  Self.FPrevMultiSelected := nil;
595  Self.FNextMultiSelected := nil;
596
597  // If there isn't any selected node, set self as first
598  if CurNode = nil then
599  begin
600    TheTreeNodes.FFirstMultiSelected := Self;
601    TheTreeNodes.FStartMultiSelected := Self;
602  end
603  else
604  begin
605
606    // if last selected node was the previous one
607    if (TheTreeNodes.FLastMultiSelected.AbsoluteIndex+1=Self.AbsoluteIndex) and (TheTreeNodes.FLastMultiSelected.FNextMultiSelected=nil) then
608    begin
609      TheTreeNodes.FLastMultiSelected.FNextMultiSelected := Self;
610      Self.FPrevMultiSelected := TheTreeNodes.FLastMultiSelected;
611    end
612    else
613    begin
614      // if last selected node was the next one
615      if (TheTreeNodes.FLastMultiSelected.AbsoluteIndex=Self.AbsoluteIndex+1) and (TheTreeNodes.FLastMultiSelected.FPrevMultiSelected=nil) then
616      begin
617        TheTreeNodes.FLastMultiSelected.FPrevMultiSelected := Self;
618        Self.FNextMultiSelected := TheTreeNodes.FLastMultiSelected;
619        TheTreeNodes.FFirstMultiSelected := Self
620      end
621      else
622      begin
623
624        // Scan linked list of selected nodes until one has a lower absolute index or we reach the end
625        While (CurNode.GetNextMultiSelected<>Nil) and (CurNode.AbsoluteIndex<Self.AbsoluteIndex) do
626          CurNode := CurNode.GetNextMultiSelected;
627
628        // last of the list
629        if CurNode.AbsoluteIndex < Self.AbsoluteIndex then
630        begin
631           CurNode.FNextMultiSelected := Self;
632           Self.FPrevMultiSelected    := CurNode;
633        end
634        else
635        // insert between two nodes
636        begin
637           Self.FPrevMultiSelected    := CurNode.FPrevMultiSelected;
638           Self.FNextMultiSelected    := CurNode;
639           if CurNode.FPrevMultiSelected <> nil then
640              CurNode.FPrevMultiSelected.FNextMultiSelected := Self;
641           CurNode.FPrevMultiSelected := Self;
642        end;
643        // Set self as head of the list if needed
644        if Self.FPrevMultiSelected = nil then
645          TheTreeNodes.FFirstMultiSelected := Self;
646      end;
647
648    end;
649
650  end;
651
652  // Set self as last selected node
653  TheTreeNodes.FLastMultiSelected := Self;
654end;
655
656function TTreeNode.CompareCount(CompareMe: Integer): Boolean;
657Begin
658  Result:=(CompareMe=Count);
659end;
660
661function TTreeNode.DoCanExpand(ExpandIt: Boolean): Boolean;
662begin
663  Result := False;
664  if (TreeView<>nil) and HasChildren then begin
665    if ExpandIt then
666      Result := TreeView.CanExpand(Self)
667    else
668      Result := TreeView.CanCollapse(Self);
669  end;
670end;
671
672procedure TTreeNode.DoExpand(ExpandIt: Boolean);
673begin
674//DebugLn('[TTreeNode.DoExpand] Self=',DbgS(Self),' Text=',Text,
675//' HasChildren=',HasChildren,' ExpandIt=',ExpandIt,' Expanded=',Expanded);
676  if HasChildren and (Expanded<>ExpandIt) then begin
677    if ExpandIt then
678      Include(FStates,nsExpanded)
679    else begin
680      Exclude(FStates,nsExpanded);
681      if (not Owner.KeepCollapsedNodes) then begin
682        while GetLastChild<>nil do
683          GetLastChild.Free;
684      end;
685    end;
686    if TreeView<>nil then begin
687      TreeView.FStates:=(TreeView.FStates+[tvsTopsNeedsUpdate,
688                tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
689                tvsScrollbarChanged,tvsMaxRightNeedsUpdate]);
690      Update;
691      if ExpandIt then
692        TreeView.Expand(Self)
693      else
694        TreeView.Collapse(Self);
695    end;
696  end;
697end;
698
699procedure TTreeNode.ExpandItem(ExpandIt: Boolean; Recurse: Boolean);
700var
701  ANode: TTreeNode;
702begin
703  if Recurse then begin
704    ExpandItem(ExpandIt, False);
705    ANode := GetFirstChild;
706    while ANode<>nil do begin
707      ANode.ExpandItem(ExpandIt, true);
708      ANode := ANode.FNextBrother;
709    end;
710  end
711  else begin
712    if TreeView<>nil then
713      Include(TreeView.FStates,tvsManualNotify);
714    try
715      if DoCanExpand(ExpandIt) then
716        DoExpand(ExpandIt);
717    finally
718      if TreeView<>nil then
719        Exclude(TreeView.FStates,tvsManualNotify);
720    end;
721  end;
722end;
723
724procedure TTreeNode.Expand(Recurse: Boolean);
725begin
726  ExpandItem(True, Recurse);
727end;
728
729procedure TTreeNode.ExpandParents;
730var ANode: TTreeNode;
731begin
732  ANode:=Parent;
733  while ANode<>nil do begin
734    ANode.Expanded:=true;
735    ANode:=ANode.Parent;
736  end;
737end;
738
739procedure TTreeNode.FreeAllNodeData;
740var
741  i: Integer;
742begin
743  FreeAndNil(FData);
744  for i:=0 to Count-1 do
745    Items[i].FreeAllNodeData;
746end;
747
748procedure TTreeNode.Collapse(Recurse: Boolean);
749begin
750  ExpandItem(False, Recurse);
751end;
752
753function TTreeNode.GetExpanded: Boolean;
754begin
755  Result := GetState(nsExpanded);
756end;
757
758procedure TTreeNode.SetExpanded(AValue: Boolean);
759begin
760  if AValue=Expanded then exit;
761  if AValue then
762    Expand(False)
763  else
764    Collapse(False);
765end;
766
767function TTreeNode.GetSelected: Boolean;
768begin
769  Result := GetState(nsSelected);
770  if (not Result) and (tvoAllowMultiSelect in TreeView.Options) then
771    Result := GetState(nsMultiSelected);
772end;
773
774procedure TTreeNode.SetSelected(AValue: Boolean);
775var
776  MultiSelect: Boolean;
777  TV: TCustomTreeView;
778begin
779  if AValue=GetSelected then exit;
780  TV:=TreeView;
781  if AValue then
782  begin
783    Include(FStates,nsSelected);
784    if (TV<>nil) then
785    begin
786      TV.EndEditing(true);
787      MultiSelect:=tvoAllowMultiselect in TV.Options;
788
789      if not MultiSelect and Assigned(FOwner) then
790        FOwner.SelectionsChanged(Self, True);
791
792      if MultiSelect then TV.LockSelectionChangeEvent;
793      try
794        TV.Selected:=Self;
795        if TV.Selected<>Self then
796          Exclude(FStates,nsSelected);
797        if (nsSelected in FStates) and MultiSelect then
798          MultiSelected:=true;
799      finally
800        if MultiSelect then TV.UnlockSelectionChangeEvent;
801      end;
802    end;
803  end else
804  begin
805    if not MultiSelected and Assigned(FOwner) then
806      FOwner.SelectionsChanged(Self, False);
807    Exclude(FStates,nsSelected);
808    if (TV<>nil) and (TV.Selected=Self) then
809    begin
810      {$IFDEF TREEVIEW_DEBUG}
811      DebugLn('TTreeNode.SetSelected: Removing selection from Node (but it does not work): ', Text);
812      {$ENDIF}
813//      TV.EndEditing(true);    // Done in TV.SetSelection
814      TV.Selected:=nil;
815      Assert(TV.Selected<>Self, 'Should not happen');
816      //if TV.Selected=Self then
817      //  Include(FStates,nsSelected);
818    end;
819  end;
820  Update;
821end;
822
823function TTreeNode.GetCut: boolean;
824begin
825  Result := GetState(nsCut);
826end;
827
828procedure TTreeNode.SetCut(AValue: Boolean);
829begin
830  if AValue=Cut then exit;
831  if AValue then
832    Include(FStates,nsCut)
833  else
834    Exclude(FStates,nsCut);
835end;
836
837function TTreeNode.GetDropTarget: Boolean;
838begin
839  Result := GetState(nsDropHilited);
840end;
841
842procedure TTreeNode.SetDropTarget(AValue: Boolean);
843begin
844  if AValue then begin
845    Include(FStates,nsDropHilited);
846    if TreeView<>nil then
847      TreeView.FLastDropTarget:=Self;
848  end else begin
849    Exclude(FStates,nsDropHilited);
850    if TreeView<>nil then
851      TreeView.FLastDropTarget:=nil;
852  end;
853end;
854
855function TTreeNode.GetHasChildren: Boolean;
856begin
857  if not GetState(nsValidHasChildren) then
858  begin
859    if Owner.Owner.NodeHasChildren(Self) then
860      Include(FStates, nsHasChildren)
861    else
862      Exclude(FStates, nsHasChildren);
863    Include(FStates, nsValidHasChildren);
864  end;
865  Result := GetState(nsHasChildren);
866end;
867
868procedure TTreeNode.SetFocused(AValue: Boolean);
869begin
870  if AValue=GetFocused then exit;
871  if AValue then
872    Include(FStates,nsFocused)
873  else
874    Exclude(FStates,nsFocused);
875  Update;
876end;
877
878function TTreeNode.Bottom: integer;
879begin
880  Result := Top + Height;
881end;
882
883function TTreeNode.BottomExpanded: integer;
884var
885  Node: TTreeNode;
886begin
887  Node := GetNextVisibleSibling;
888  if Node <> nil then
889    Result := Node.Top
890  else begin
891    Node := GetLastVisibleChild;
892    if Expanded and (Node <> nil) then
893      Result := Node.BottomExpanded
894    else
895      Result := Bottom;
896  end;
897end;
898
899function TTreeNode.GetFocused: Boolean;
900begin
901  Result := GetState(nsFocused);
902end;
903
904procedure TTreeNode.SetHasChildren(AValue: Boolean);
905begin
906  if GetState(nsValidHasChildren) and (AValue=HasChildren) then exit;
907  //DebugLn('[TTreeNode.SetHasChildren] Self=',DbgS(Self),' Self.Text=',Text,' AValue=',AValue);
908  if AValue then
909    Include(FStates,nsHasChildren)
910  else begin
911    while GetLastChild<>nil do
912      GetLastChild.Free;
913    Exclude(FStates,nsHasChildren)
914  end;
915  Include(FStates, nsValidHasChildren);
916  Update;
917end;
918
919function TTreeNode.GetNextSibling: TTreeNode;
920begin
921  Result := FNextBrother;
922end;
923
924function TTreeNode.GetPrevSibling: TTreeNode;
925begin
926  Result := FPrevBrother;
927end;
928
929function TTreeNode.GetNextVisible: TTreeNode;
930begin
931  if Visible and Expanded and (GetFirstVisibleChild<>nil) then
932    Result:=GetFirstVisibleChild
933  else begin
934    Result:=Self;
935    while (Result<>nil) and (Result.GetNextVisibleSibling=nil) do
936      Result:=Result.Parent;
937    if Result<>nil then
938      Result:=Result.GetNextVisibleSibling;
939  end;
940  if (Result<>nil) and ( (not Result.Visible) or (not AreParentsExpandedAndVisible) ) then
941    Result:=nil;
942end;
943
944function TTreeNode.GetNextVisibleSibling: TTreeNode;
945begin
946  Result := Self;
947  repeat
948    Result := Result.GetNextSibling;
949  until ((Result=nil) or (Result.Visible));
950  if (Result<>nil) and (not Result.Visible) then // Result := nil ... will be removed
951    Assert(False,'TTreeNode.GetNextVisibleSibling: (Result<>nil) and (not Result.Visible)');
952end;
953
954function TTreeNode.GetPrevVisible: TTreeNode;
955var
956  ANode: TTreeNode;
957begin
958  Result:=GetPrevVisibleSibling;
959  if Result <> nil then begin
960    while Result.Visible and Result.Expanded do begin
961      ANode:=Result.GetLastVisibleChild;
962      if ANode=nil then break;
963      Result:=ANode;
964    end;
965  end
966  else
967    Result := Parent;
968  if (Result<>nil) and ( (not Result.Visible) or (not AreParentsExpandedAndVisible) ) then
969    Result:=nil;
970end;
971
972function TTreeNode.GetPrevVisibleSibling: TTreeNode;
973begin
974  Result := Self;
975  repeat
976    Result := Result.GetPrevSibling;
977  until ((Result=nil) or (Result.Visible));
978  if (Result<>nil) and (not Result.Visible) then  // Result := nil ... will be removed
979    Assert(False,'TTreeNode.GetPrevVisibleSibling: (Result<>nil) and (not Result.Visible)');
980end;
981
982function TTreeNode.GetPrevExpanded: TTreeNode;
983var
984  ANode: TTreeNode;
985begin
986  Result:=GetPrevVisibleSibling;
987  if Result <> nil then begin
988    while Result.Visible and Result.Expanded do begin
989      ANode:=Result.GetLastVisibleChild;
990      if ANode=nil then break;
991      Result:=ANode;
992    end;
993  end
994  else
995    Result:=Parent;
996end;
997
998function TTreeNode.GetNextChild(AValue: TTreeNode): TTreeNode;
999begin
1000  if AValue <> nil then
1001    Result := AValue.GetNextSibling
1002  else
1003    Result := nil;
1004end;
1005
1006function TTreeNode.GetNextExpanded: TTreeNode;
1007var
1008  ANode: TTreeNode;
1009begin
1010  ANode := GetFirstVisibleChild;
1011  if Expanded and (ANode<>nil) then
1012    Result:=ANode
1013  else begin
1014    Result:=Self;
1015    while (Result<>nil) and (Result.GetNextVisibleSibling=nil) do
1016      Result:=Result.Parent;
1017    if Result<>nil then
1018      Result:=Result.GetNextVisibleSibling;
1019  end;
1020end;
1021
1022function TTreeNode.GetNextMultiSelected: TTreeNode;
1023begin
1024  Result:=FNextMultiSelected;
1025end;
1026
1027function TTreeNode.GetPrevChild(AValue: TTreeNode): TTreeNode;
1028begin
1029  if AValue <> nil then
1030    Result := AValue.GetPrevSibling
1031  else
1032    Result := nil;
1033end;
1034
1035function TTreeNode.GetPrevMultiSelected: TTreeNode;
1036begin
1037  Result:=FPrevMultiSelected;
1038end;
1039
1040function TTreeNode.GetFirstChild: TTreeNode;
1041begin
1042  if Count>0 then
1043    Result:=FItems[0]
1044  else
1045    Result:=nil;
1046end;
1047
1048function TTreeNode.GetFirstSibling: TTreeNode;
1049begin
1050  if Parent<>nil then
1051    Result:=Parent.GetFirstChild
1052  else begin
1053    Result:=Self;
1054    while Result.FPrevBrother<>nil do
1055      Result:=Result.FPrevBrother;
1056  end;
1057end;
1058
1059function TTreeNode.GetFirstVisibleChild: TTreeNode;
1060begin
1061  Result := GetFirstChild;
1062  if (Result<>nil) and (not Result.Visible) then
1063    Result := Result.GetNextVisibleSibling;
1064end;
1065
1066function TTreeNode.GetLastSibling: TTreeNode;
1067begin
1068  if Parent<>nil then
1069    Result:=Parent.GetLastChild
1070  else begin
1071    Result:=Self;
1072    while Result.FNextBrother<>nil do
1073      Result:=Result.FNextBrother;
1074  end;
1075end;
1076
1077function TTreeNode.GetLastChild: TTreeNode;
1078begin
1079  if Count > 0 then
1080    Result := FItems[Count - 1]
1081  else
1082    Result := nil;
1083end;
1084
1085function TTreeNode.GetLastVisibleChild: TTreeNode;
1086begin
1087  Result := GetLastChild;
1088  if Assigned(Result) and not Result.Visible then begin
1089    Result := Result.GetPrevVisible;
1090    if Result = Self then begin                     // No visible nodes found.
1091      Assert(Visible, 'TTreeNode.GetLastVisibleChild: Node is not Visible');
1092      Result := Nil;
1093    end;
1094  end;
1095end;
1096
1097function TTreeNode.GetLastSubChild: TTreeNode;
1098var
1099  Node: TTreeNode;
1100begin
1101  Result:=GetLastChild;
1102  if Result<>nil then begin
1103    Node:=Result.GetLastSubChild;
1104    if Node<>nil then
1105      Result:=Node;
1106  end;
1107end;
1108
1109function TTreeNode.GetNext: TTreeNode;
1110begin
1111  Result:=GetFirstChild;
1112  if Result=nil then
1113    Result:=GetNextSkipChildren;
1114end;
1115
1116function TTreeNode.GetNextSkipChildren: TTreeNode;
1117begin
1118  Result:=Self;
1119  while (Result<>nil) and (Result.FNextBrother=nil) do
1120    Result:=Result.Parent;
1121  if Result<>nil then
1122    Result:=Result.FNextBrother;
1123end;
1124
1125function TTreeNode.GetPrev: TTreeNode;
1126var
1127  ANode: TTreeNode;
1128begin
1129  Result := GetPrevSibling;
1130  if Result <> nil then begin
1131    ANode := Result;
1132    repeat
1133      Result := ANode;
1134      ANode := Result.GetLastChild;
1135    until ANode = nil;
1136  end else
1137    Result := Parent;
1138end;
1139
1140function TTreeNode.GetAbsoluteIndex: Integer;
1141// - first node has index 0
1142// - the first child of a node has an index one bigger than its parent
1143// - a node without children has an index one bigger than its previous brother
1144var
1145  ANode: TTreeNode;
1146begin
1147  Result:=-1;
1148  ANode:=Self;
1149  repeat
1150    inc(Result);
1151    while ANode.FPrevBrother<>nil do begin
1152      ANode:=ANode.FPrevBrother;
1153      inc(Result,ANode.FSubTreeCount);
1154    end;
1155    ANode:=ANode.Parent;
1156  until ANode=nil;
1157end;
1158
1159function TTreeNode.GetDeleting: Boolean;
1160begin
1161  Result := nsDeleting in FStates;
1162end;
1163
1164function TTreeNode.GetHeight: integer;
1165begin
1166  if FHeight <= 0 then
1167  begin
1168    if TreeView <> nil then
1169      Result := TreeView.FDefItemHeight
1170    else
1171      Result := DefaultTreeNodeHeight;
1172  end
1173  else
1174    Result := FHeight;
1175end;
1176
1177function TTreeNode.GetIndex: Integer;
1178// returns number of previous siblings (nodes on same lvl with same parent)
1179var
1180  ANode: TTreeNode;
1181begin
1182  if self = nil then
1183    exit(-1);
1184  if FIndex>=0 then
1185    exit(FIndex);
1186  // many algorithms uses the last sibling, so we check that first for speed
1187  if (Parent<>nil) and (Parent[Parent.Count-1]=Self) then begin
1188    Result:=Parent.Count-1;
1189    FIndex:=Result;
1190    exit;
1191  end;
1192  // count previous siblings
1193  Result := -1;
1194  ANode := Self;
1195  while ANode <> nil do begin
1196    Inc(Result);
1197    if ANode.FIndex>=0 then begin
1198      inc(Result,ANode.FIndex);
1199      break;
1200    end;
1201    ANode := ANode.GetPrevSibling;
1202  end;
1203  FIndex:=Result;
1204end;
1205
1206function TTreeNode.GetItems(AnIndex: Integer): TTreeNode;
1207begin
1208  if (AnIndex<0) or (AnIndex>=Count) then
1209    TreeNodeErrorFmt(rsIndexOutOfBounds,[ClassName, AnIndex, Count-1]);
1210  Result:=FItems[AnIndex];
1211end;
1212
1213procedure TTreeNode.SetItems(AnIndex: Integer; AValue: TTreeNode);
1214begin
1215  if (AnIndex<0) or (AnIndex>=Count) then
1216    TreeNodeErrorFmt(rsIndexOutOfBounds, [ClassName, AnIndex, Count-1]);
1217  Items[AnIndex].Assign(AValue);
1218end;
1219
1220procedure TTreeNode.SetMultiSelected(const AValue: Boolean);
1221begin
1222  if AValue=GetMultiSelected then exit;
1223  if AValue then begin
1224    if (Treeview<>nil) and (not (tvoAllowMultiselect in TreeView.Options)) then
1225      exit;
1226    if Assigned(FOwner) then
1227      FOwner.SelectionsChanged(Self, True);
1228    Include(FStates,nsMultiSelected);
1229    if TreeNodes<>nil then BindToMultiSelected;
1230  end else begin
1231    if Assigned(FOwner) then
1232      FOwner.SelectionsChanged(Self, False);
1233    Exclude(FStates,nsMultiSelected);
1234    if TreeNodes<>nil then UnbindFromMultiSelected;
1235  end;
1236  if TreeView<>nil then TreeView.InternalSelectionChanged;
1237  Update;
1238end;
1239
1240function TTreeNode.IndexOf(AValue: TTreeNode): Integer;
1241begin
1242  if (AValue = nil) or (AValue.FParent <> Self) then
1243  begin
1244    Result:=-1;
1245    exit;
1246  end;
1247  Result := AValue.GetIndex;
1248end;
1249
1250function TTreeNode.IndexOfText(const NodeText: string): Integer;
1251begin
1252  Result := Count - 1;
1253  while Result >= 0 do
1254  begin
1255    if FItems[Result].Text = NodeText then exit;
1256    dec(Result);
1257  end;
1258end;
1259
1260function TTreeNode.FindNode(const NodeText: string): TTreeNode;
1261begin
1262  Result:=GetFirstChild;
1263  while (Result<>nil) and (Result.Text<>NodeText) do
1264    Result:=Result.GetNextSibling;
1265end;
1266
1267function TTreeNode.GetTextPath: string;
1268var
1269  Node: TTreeNode;
1270begin
1271  Result := '';
1272  Node := Self;
1273  while Assigned(Node) do
1274  begin
1275    if Result <> '' then
1276      Result := '/' + Result;
1277    Result := Node.Text + Result;
1278    Node := Node.Parent;
1279  end;
1280end;
1281
1282function TTreeNode.GetCount: Integer;
1283begin
1284  Result := FCount;
1285end;
1286
1287procedure TTreeNode.EndEdit(Cancel: Boolean);
1288var
1289  TV: TCustomTreeView;
1290begin
1291  TV:=TreeView;
1292  if TV=nil then exit;
1293  TV.EndEditing(Cancel);
1294end;
1295
1296procedure TTreeNode.Unbind;
1297// unbind from parent and neighbor siblings, but not from owner
1298var
1299  OldIndex, i: integer;
1300  HigherNode: TTreeNode;
1301  TheTreeView: TCustomTreeView;
1302begin
1303  {$IFDEF TREEVIEW_DEBUG}
1304  DebugLn('[TTreeNode.Unbind] Self=',DbgS(Self),' Self.Text=',Text);
1305  {$ENDIF}
1306  if not (nsBound in FStates) then exit;
1307  Exclude(FStates,nsBound);
1308  if Owner<>nil then dec(Owner.FCount);
1309
1310  // remove single select
1311  Selected:=false;
1312  TheTreeView:=Nil;
1313  // invalidate caches of TreeView and if root item, remove from TreeView.Items
1314  if Owner<>nil then begin
1315    Owner.ClearCache;
1316    if FParent=nil then
1317      Owner.MoveTopLvlNode(Owner.IndexOfTopLvlItem(Self),-1,Self);
1318    TheTreeView:=Owner.Owner;
1319    if TheTreeView<>nil then begin
1320      TheTreeView.FStates:=TheTreeView.FStates+[tvsMaxRightNeedsUpdate,
1321        tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
1322      if TheTreeView.FLastDropTarget=Self then
1323        TheTreeView.FLastDropTarget:=nil;
1324      if TheTreeView.FInsertMarkNode=Self then
1325        TheTreeView.FInsertMarkNode:=nil;
1326    end;
1327  end;
1328  // unmultiselect (keeping MultiSelected, but leaving multiselection list)
1329  UnbindFromMultiSelected;
1330  // remove from sibling list
1331  if FPrevBrother<>nil then FPrevBrother.FNextBrother:=FNextBrother;
1332  if FNextBrother<>nil then FNextBrother.FPrevBrother:=FPrevBrother;
1333  FPrevBrother:=nil;
1334  FNextBrother:=nil;
1335  // remove from parent
1336  if FParent<>nil then begin
1337    // update all FSubTreeCount
1338    HigherNode:=FParent;
1339    while HigherNode<>nil do begin
1340      dec(HigherNode.FSubTreeCount,FSubTreeCount);
1341      HigherNode:=HigherNode.Parent;
1342    end;
1343    //if TreeNodes<>nil then Dec(TreeNodes.FCount,FSubTreeCount);
1344    // remove from parents list
1345    OldIndex:=FIndex;
1346    if OldIndex<0 then
1347      RaiseGDBException('');
1348    for i:=OldIndex to FParent.FCount-2 do begin
1349      FParent.FItems[i]:=FParent.FItems[i+1];
1350      FParent.FItems[i].FIndex:=i;
1351    end;
1352    dec(FParent.FCount);
1353    if (FParent.FCapacity>15) and (FParent.FCount<(FParent.FCapacity shr 2))
1354    then begin
1355      // shrink FParent.FItems
1356      FParent.FCapacity:=FParent.FCapacity shr 1;
1357      ReAllocMem(FParent.FItems,SizeOf(Pointer)*FParent.FCapacity);
1358    end;
1359    if FParent.Count=0 then begin
1360      Assert(Assigned(TheTreeView), 'TTreeNode.Unbind: TheTreeView=Nil.');
1361      TheTreeView.BeginUpdate;
1362      try
1363        FParent.Expanded:=false;
1364        FParent.HasChildren:=false;
1365      finally
1366        TheTreeView.EndUpdate;
1367      end;
1368    end;
1369    FParent:=nil;
1370  end;
1371end;
1372
1373procedure TTreeNode.UnbindFromMultiSelected;
1374var
1375  TheTreeNodes: TTreeNodes;
1376begin
1377  TheTreeNodes:=TreeNodes;
1378  if TheTreeNodes=nil then exit;
1379  if TheTreeNodes.FFirstMultiSelected=Self then
1380    TheTreeNodes.FFirstMultiSelected:=FNextMultiSelected;
1381  // Reset last multiselected node
1382  if TheTreeNodes.FLastMultiSelected=Self then
1383  begin
1384    if Self.FNextMultiSelected <> nil then
1385      TheTreeNodes.FLastMultiSelected := Self.FNextMultiSelected
1386    else
1387      if Self.FPrevMultiSelected <> nil then
1388        TheTreeNodes.FLastMultiSelected := Self.FPrevMultiSelected
1389      else
1390        TheTreeNodes.FLastMultiSelected := nil;
1391  end;
1392  if FNextMultiSelected<>nil then
1393    FNextMultiSelected.FPrevMultiSelected:=FPrevMultiSelected;
1394  if FPrevMultiSelected<>nil then
1395    FPrevMultiSelected.FNextMultiSelected:=FNextMultiSelected;
1396  FNextMultiSelected:=nil;
1397  FPrevMultiSelected:=nil;
1398end;
1399
1400function AddModeStr(AddMode: TAddMode): string;
1401begin
1402  WriteStr(Result, AddMode);
1403end;
1404
1405procedure TTreeNode.InternalMove(ANode: TTreeNode; AddMode: TAddMode);
1406{
1407  TAddMode = (taAddFirst, taAdd, taInsert);
1408
1409  taAdd:      add Self as last child of ANode
1410  taAddFirst: add Self as first child of ANode
1411  taInsert:   add Self in front of ANode
1412}
1413var
1414  HigherNode: TTreeNode;
1415  NewIndex, NewParentItemSize, i: integer;
1416  WasSelected: Boolean;
1417begin
1418  {$IFDEF TREEVIEW_DEBUG}
1419  DbgOut('[TTreeNode.InternalMove]  Self=',DbgS(Self),' Self.Text=',Text
1420         ,' ANode=',ANode<>nil,' AddMode=', AddModeStr(AddMode));
1421  if ANode<>nil then
1422    DbgOut(' ANode.Text=',ANode.Text);
1423  DebugLn('');
1424  {$ENDIF}
1425  if TreeView<>nil then
1426    TreeView.BeginUpdate;
1427  try
1428    WasSelected:=Selected;
1429    Unbind;
1430    if Owner<>nil then
1431      Owner.ClearCache;
1432    Include(FStates,nsBound);
1433    if Owner<>nil then inc(Owner.FCount);
1434    // set parent
1435    if AddMode in [taAdd, taAddFirst] then
1436      FParent:=ANode
1437    else begin // taInsert
1438      if (ANode=nil) then
1439        TreeNodeError('TTreeNode.InternalMove AddMode=taInsert but ANode=nil');
1440      FParent:=ANode.Parent;
1441      FPrevBrother:=ANode.FPrevBrother;
1442      FNextBrother:=ANode;
1443    end;
1444    if FParent<>nil then begin
1445      FParent.HasChildren:=true;
1446      if (FParent.FCount=FParent.FCapacity) then begin
1447        // grow FParent.FItems
1448        if FParent.FCapacity=0 then
1449          FParent.FCapacity:=5
1450        else
1451          FParent.FCapacity:=FParent.FCapacity shl 1;
1452        NewParentItemSize:=SizeOf(Pointer)*FParent.FCapacity;
1453        if FParent.FItems=nil then
1454          GetMem(FParent.FItems,NewParentItemSize)
1455        else
1456          ReAllocMem(FParent.FItems,NewParentItemSize);
1457      end;
1458      inc(FParent.FCount);
1459      // calculate new Index
1460      case AddMode of
1461      taAdd: NewIndex:=FParent.Count-1;
1462      taAddFirst: NewIndex:=0;
1463      else
1464        // taInsert
1465        NewIndex:=ANode.Index;
1466      end;
1467      // move next siblings
1468      for i:=FParent.FCount-1 downto NewIndex+1 do begin
1469        FParent.FItems[i]:=FParent.FItems[i-1];
1470        FParent.FItems[i].FIndex:=i;
1471      end;
1472      // insert this node to parent's items
1473      FParent.FItems[NewIndex]:=Self;
1474      FIndex:=NewIndex;
1475      // set Next and Prev sibling
1476      if NewIndex>0 then
1477        FPrevBrother:=FParent.FItems[NewIndex-1]
1478      else
1479        FPrevBrother:=nil;
1480      if NewIndex<FParent.Count-1 then
1481        FNextBrother:=FParent.FItems[NewIndex+1]
1482      else
1483        FNextBrother:=nil;
1484      // update total node count of all parents
1485      HigherNode:=FParent;
1486      while HigherNode<>nil do begin
1487        inc(HigherNode.FSubTreeCount,FSubTreeCount);
1488        HigherNode:=HigherNode.Parent;
1489      end;
1490      //if TreeNodes<>nil then inc(TreeNodes.FCount,FSubTreeCount);
1491    end else begin
1492      // add as top level node
1493      case AddMode of
1494      taAdd:
1495        begin
1496          // add as last top level node
1497          if Owner<>nil then begin
1498            FPrevBrother:=Owner.GetLastNode;
1499            Owner.MoveTopLvlNode(-1,Owner.FTopLvlCount,Self);
1500          end;
1501        end;
1502      taAddFirst:
1503        begin
1504          // add as first top level node = root node
1505          if Owner<>nil then begin
1506            FNextBrother:=Owner.GetFirstNode;
1507            Owner.MoveTopLvlNode(-1,0,Self);
1508          end;
1509        end;
1510      taInsert:
1511        begin
1512          // insert node in front of ANode
1513          //DebugLn('[TTreeNode.InternalMove] ANode.Index=',ANode.Index,' ANode=',DbgS(ANode));
1514          FNextBrother:=ANode;
1515          FPrevBrother:=ANode.GetPrevSibling;
1516          if Owner<>nil then begin
1517            Owner.MoveTopLvlNode(-1,ANode.Index,Self);
1518          end;
1519        end;
1520      end;
1521    end;
1522    // connect Next and Prev sibling
1523    if FPrevBrother<>nil then FPrevBrother.FNextBrother:=Self;
1524    if FNextBrother<>nil then FNextBrother.FPrevBrother:=Self;
1525    if Owner.Owner<>nil then
1526      Owner.Owner.FStates:=Owner.Owner.FStates+[tvsMaxRightNeedsUpdate,
1527        tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
1528    // re-add to multiselection list
1529    if MultiSelected then
1530      BindToMultiSelected;
1531    if WasSelected then
1532      Selected:=true;
1533  finally
1534    if TreeView<>nil then TreeView.EndUpdate;
1535  end;
1536
1537  {$IFDEF TREEVIEW_DEBUG}
1538  DbgOut('[TTreeNode.InternalMove] END Self=',DbgS(Self),' Self.Text=',Text
1539         ,' ANode=',DbgS(ANode<>nil),' AddMode=',AddModeStr(AddMode));
1540  if ANode<>nil then
1541    DbgOut(' ANode.Text=',ANode.Text);
1542  DebugLn('');
1543  {$ENDIF}
1544end;
1545
1546procedure TTreeNode.MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode);
1547{
1548  TNodeAttachMode = (naAdd, naAddFirst, naAddChild, naAddChildFirst, naInsert);
1549
1550  naAdd:           add as last sibling of Destination
1551  naAddFirst:      add as first sibling of Destnation
1552  naAddChild:      add as last child of Destination
1553  naAddChildFirst: add as first child of Destination
1554  naInsert:        insert in front of Destination
1555  naInsertBehind:  insert behind Destination
1556}
1557var
1558  AddMode: TAddMode;
1559  //ANode: TTreeNode;
1560  //HItem: HTreeItem;
1561  OldOnChanging: TTVChangingEvent;
1562  OldOnChange: TTVChangedEvent;
1563begin
1564  if (Destination=nil) and not(Mode in [naAdd,naAddFirst]) then
1565    TreeNodeError('TTreeNode.MoveTo Destination=nil');
1566  if Mode=naInsertBehind then begin      // convert naInsertBehind
1567    if Destination.GetNextSibling=nil then
1568      Mode:=naAdd
1569    else begin
1570      Mode:=naInsert;
1571      Destination:=Destination.GetNextSibling;
1572    end;
1573  end;
1574  if (Destination = nil) or not Destination.HasAsParent(Self) then begin
1575    OldOnChanging := TreeView.OnChanging;
1576    OldOnChange := TreeView.OnChange;
1577    TreeView.OnChanging := nil;
1578    TreeView.OnChange := nil;
1579    try
1580      if (Destination <> nil) and (Mode in [naAdd, naAddFirst]) then
1581        Destination := Destination.Parent;
1582      case Mode of
1583        naAddFirst,
1584        naAddChildFirst: AddMode := taAddFirst;
1585        naInsert:        AddMode := taInsert;
1586      else
1587        AddMode:=taAdd;
1588      end;
1589      if (Destination <> Self) then
1590        InternalMove(Destination, AddMode);
1591    finally
1592      TreeView.OnChanging := OldOnChanging;
1593      TreeView.OnChange := OldOnChange;
1594    end;
1595  end;
1596  Changed(ncParentChanged)
1597end;
1598
1599procedure TTreeNode.MultiSelectGroup;
1600var
1601  FirstNode, LastNode, ANode: TTreeNode;
1602begin
1603  if Assigned(TreeView) and not (tvoAllowMultiselect in TreeView.Options) then
1604    Exit;
1605  if Assigned(TreeView) then TreeView.LockSelectionChangeEvent;
1606  try
1607    // We need to select the nodes between the selected node and the current node
1608    FirstNode := GetPrevSibling;
1609    while Assigned(FirstNode) and not FirstNode.Selected do
1610      FirstNode := FirstNode.GetPrevSibling;
1611    if not Assigned(FirstNode) then FirstNode := Self;
1612    LastNode := GetNextSibling;
1613    while Assigned(LastNode) and not LastNode.Selected do
1614      LastNode := LastNode.GetNextSibling;
1615    if not Assigned(LastNode) then LastNode := Self;
1616    ANode := FirstNode;
1617    while Assigned(ANode) do
1618    begin
1619      ANode.MultiSelected := True;
1620      if ANode = LastNode then Break;
1621      ANode := ANode.GetNextSibling;
1622    end;
1623  finally
1624    if Assigned(TreeView) then TreeView.UnlockSelectionChangeEvent;
1625  end;
1626end;
1627
1628procedure TTreeNode.MakeVisible;
1629begin
1630  if Assigned(TreeView) then
1631    TreeView.EnsureNodeIsVisible(Self)
1632  else
1633    ExpandParents;
1634end;
1635
1636function TTreeNode.GetLevel: Integer;
1637// root is on level 0
1638var
1639  ANode: TTreeNode;
1640begin
1641  Result := 0;
1642  ANode := Parent;
1643  while Assigned(ANode) do
1644  begin
1645    Inc(Result);
1646    ANode := ANode.Parent;
1647  end;
1648end;
1649
1650function TTreeNode.GetMultiSelected: Boolean;
1651begin
1652  Result := GetState(nsMultiSelected);
1653end;
1654
1655function TTreeNode.IsNodeVisible: Boolean;
1656begin
1657  if Assigned(TreeView) then
1658    Result := TreeView.IsNodeVisible(Self)
1659  else
1660    Result := AreParentsExpandedAndVisible;
1661end;
1662
1663function TTreeNode.IsNodeHeightFullVisible: Boolean;
1664begin
1665  if Assigned(TreeView) then
1666    Result := TreeView.IsNodeHeightFullVisible(Self)
1667  else
1668    Result := AreParentsExpandedAndVisible;
1669end;
1670
1671procedure TTreeNode.Update;
1672var
1673  TV: TCustomTreeView;
1674begin
1675  TV := TreeView;
1676  if Assigned(TV) and (Owner.FUpdateCount = 0) and (not (csLoading in TV.ComponentState)) then
1677    TV.Invalidate;
1678end;
1679
1680function TTreeNode.EditText: Boolean;
1681var
1682  TV: TCustomTreeView;
1683begin
1684  TV := TreeView;
1685  Result := Assigned(TV) and (tvsIsEditing in TreeView.FStates);
1686  TV.BeginEditing(Self);
1687end;
1688
1689function TTreeNode.DisplayRect(TextOnly: Boolean): TRect;
1690begin
1691  FillChar(Result, SizeOf(Result), 0);
1692  if TreeView <> nil then
1693  begin
1694    Result.Left := TreeView.BorderWidth;
1695    Result.Top := Top - TreeView.ScrolledTop + TreeView.BorderWidth;
1696    Result.Right := TreeView.ClientWidth - TreeView.BorderWidth;
1697    Result.Bottom := Result.Top + Height;
1698    if TextOnly then
1699    begin
1700      Result.Left := DisplayTextLeft;
1701      if Result.Left > Result.Right then
1702        Result.Left := Result.Right;
1703      Result.Right := DisplayTextRight;
1704      if Result.Right < Result.Left then
1705        Result.Right := Result.Left;
1706    end;
1707  end;
1708end;
1709
1710function TTreeNode.DisplayExpandSignLeft: integer;
1711var
1712  TV: TCustomTreeView;
1713  l: LongInt;
1714  RealIndent: Integer;
1715begin
1716  Result := 0;
1717  TV := TreeView;
1718  RealIndent := TV.Indent;
1719  if TV = nil then Exit;
1720  l := Level;
1721  if not (tvoShowRoot in TV.Options) then
1722    inc(Result, RealIndent * (l - 1) + (RealIndent shr 2) + TV.BorderWidth - TV.FScrolledLeft)
1723  else
1724    inc(Result, RealIndent * l + TV.BorderWidth - TV.FScrolledLeft);
1725end;
1726
1727function TTreeNode.DisplayExpandSignRect: TRect;
1728begin
1729  FillChar(Result, SizeOf(Result), 0);
1730  if TreeView <> nil then
1731  begin
1732    Result.Left := DisplayExpandSignLeft;
1733    Result.Top := Top;
1734    Result.Right := Result.Left + TreeView.Indent;
1735    Result.Bottom := Top + Height;
1736  end;
1737end;
1738
1739function TTreeNode.DisplayExpandSignRight: integer;
1740begin
1741  Result := DisplayExpandSignLeft;
1742  if TreeView <> nil then
1743    inc(Result, TreeView.Indent);
1744end;
1745
1746function TTreeNode.DisplayIconLeft: integer;
1747var
1748  TV: TCustomTreeView;
1749begin
1750  Result := DisplayStateIconLeft;
1751  TV := TreeView;
1752  if (TV = nil) or (TV.StateImages = nil) then Exit;
1753  if (StateIndex < 0) or (StateIndex >= TV.StateImages.Count) then Exit;
1754  Inc(Result, TV.StateImages.WidthForPPI[TV.StateImagesWidth, TV.Font.PixelsPerInch] + TV.FDefItemSpace);
1755end;
1756
1757function TTreeNode.DisplayStateIconLeft: integer;
1758begin
1759  Result := DisplayExpandSignRight;
1760end;
1761
1762function TTreeNode.DisplayTextLeft: integer;
1763var
1764  TV: TCustomTreeView;
1765  ImgIndex: TImageIndex;
1766  sz: TSize;
1767begin
1768  Result := DisplayIconLeft;
1769  TV := TreeView;
1770  if TV = nil then
1771    exit;
1772  sz := TV.GetImageSize;
1773  if (TV.Images = nil) then
1774  begin
1775    inc(Result, sz.CX);
1776    exit;
1777  end;
1778  if (TV.Selected = Self) then
1779    ImgIndex:=SelectedIndex
1780  else
1781    ImgIndex:=ImageIndex;
1782  if (ImgIndex<0) or (ImgIndex>=TV.Images.Count) then Exit;
1783  Inc(Result, sz.CX + TV.FDefItemSpace);
1784end;
1785
1786function TTreeNode.DisplayTextRight: integer;
1787var
1788  TV: TCustomTreeView;
1789begin
1790  Result := DisplayTextLeft;
1791  TV := TreeView;
1792  if TV <> nil then
1793    Inc(Result, TV.Canvas.TextWidth(Text) + TV.Indent div 2);
1794end;
1795
1796function TTreeNode.AlphaSort: Boolean;
1797begin
1798  Result := CustomSort(nil);
1799end;
1800
1801function TTreeNode.CustomSort(SortProc: TTreeNodeCompare): Boolean;
1802begin
1803  if FCount>0 then begin
1804    if Owner<>nil then Owner.ClearCache;
1805    if not Assigned(SortProc) then SortProc:=@DefaultTreeViewSort;
1806    Sort(FItems, FCount, SortProc, true);
1807  end;
1808  if (TreeView <> nil) then
1809    Include(TreeView.FStates, tvsTopsNeedsUpdate);
1810  Result:=true;
1811end;
1812
1813procedure TTreeNode.Delete;
1814begin
1815  if not Deleting then Free;
1816end;
1817
1818procedure TTreeNode.DeleteChildren;
1819begin
1820  if Owner<>nil then Owner.ClearCache;
1821  Collapse(true);
1822  HasChildren := False;
1823end;
1824
1825procedure TTreeNode.Assign(Source: TPersistent);
1826var
1827  ANode: TTreeNode;
1828begin
1829  if Owner<>nil then Owner.ClearCache;
1830  if Source is TTreeNode then
1831  begin
1832    ANode := TTreeNode(Source);
1833    Text := ANode.Text;
1834    Data := ANode.Data;
1835    ImageIndex := ANode.ImageIndex;
1836    SelectedIndex := ANode.SelectedIndex;
1837    StateIndex := ANode.StateIndex;
1838    OverlayIndex := ANode.OverlayIndex;
1839    Height := ANode.Height;
1840    Focused := ANode.Focused;
1841    //DropTarget := ANode.DropTarget;
1842    Cut := ANode.Cut;
1843    HasChildren := ANode.HasChildren;
1844  end
1845  else inherited Assign(Source);
1846end;
1847
1848function TTreeNode.IsEqual(Node: TTreeNode): Boolean;
1849begin
1850  Result := (Text = Node.Text) and (Data = Node.Data);
1851end;
1852
1853procedure TTreeNode.ReadData(Stream: TStream; StreamVersion: integer);
1854var
1855  I, ItemCount: Integer;
1856  NewExpanded: boolean;
1857  OldInfo: TOldTreeNodeInfo;
1858  Info: TTreeNodeInfo;
1859  Node: TTreeNode;
1860  lSelfAX: TLazAccessibleObject;
1861begin
1862  if Owner<>nil then Owner.ClearCache;
1863  if StreamVersion=TTreeNodeWithPointerStreamVersion then
1864  begin
1865    Stream.ReadBuffer(OldInfo, SizeOf(TOldTreeNodeInfo));
1866    ImageIndex := OldInfo.ImageIndex;
1867    SelectedIndex := OldInfo.SelectedIndex;
1868    StateIndex := OldInfo.StateIndex;
1869    OverlayIndex := OldInfo.OverlayIndex;
1870    Data := Pointer(OldInfo.Data);
1871    Height := OldInfo.Height;
1872    NewExpanded := OldInfo.Expanded;
1873    ItemCount := OldInfo.Count;
1874    SetLength(FText,OldInfo.TextLen)
1875  end
1876  else
1877  begin
1878    Stream.ReadBuffer(Info, SizeOf(TTreeNodeInfo));
1879    ImageIndex := Info.ImageIndex;
1880    SelectedIndex := Info.SelectedIndex;
1881    StateIndex := Info.StateIndex;
1882    OverlayIndex := Info.OverlayIndex;
1883    Height := Info.Height;
1884    NewExpanded := Info.Expanded;
1885    ItemCount := Info.Count;
1886    SetLength(FText,Info.TextLen);
1887  end;
1888  if FText<>'' then
1889  begin
1890    Stream.Read(FText[1],length(FText));
1891    // Update accessibility information
1892    if TreeView<>nil then
1893    begin
1894     lSelfAX := TreeView.GetAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
1895     if lSelfAX <> nil then
1896       lSelfAX.AccessibleValue := FText;
1897    end;
1898  end;
1899  if Owner<>nil then begin
1900    for I := 0 to ItemCount - 1 do begin
1901      Node:=Owner.AddChild(Self, '');
1902      Node.ReadData(Stream, StreamVersion);
1903      Owner.Owner.Added(Node);
1904    end;
1905  end;
1906  Expanded := NewExpanded;
1907end;
1908
1909procedure TTreeNode.ReadDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
1910var
1911  I, Size, ItemCount: Integer;
1912begin
1913  if Owner<>nil then Owner.ClearCache;
1914  Stream.ReadBuffer(Size, SizeOf(Size));
1915  Stream.ReadBuffer(Info^, Size);
1916  Text := Info^.Text;
1917  ImageIndex := Info^.ImageIndex;
1918  SelectedIndex := Info^.SelectedIndex;
1919  StateIndex := Info^.StateIndex;
1920  OverlayIndex := Info^.OverlayIndex;
1921  Data := {%H-}Pointer(Info^.Data);
1922  if Owner<>nil then begin
1923    ItemCount := Info^.Count;
1924    for I := 0 to ItemCount - 1 do
1925      Owner.AddChild(Self, '').ReadDelphiData(Stream, Info);
1926  end;
1927end;
1928
1929procedure TTreeNode.WriteData(Stream: TStream; StreamVersion: integer);
1930var
1931  i: integer;
1932  OldInfo: TOldTreeNodeInfo;
1933  Info: TTreeNodeInfo;
1934begin
1935  if StreamVersion=TTreeNodeWithPointerStreamVersion then
1936  begin
1937    OldInfo.ImageIndex := ImageIndex;
1938    OldInfo.SelectedIndex := SelectedIndex;
1939    OldInfo.OverlayIndex := OverlayIndex;
1940    OldInfo.StateIndex := StateIndex;
1941    OldInfo.Height := FHeight;
1942    OldInfo.Data := PtrUInt(Data);
1943    OldInfo.Count := Count;
1944    OldInfo.Expanded := Expanded;
1945    OldInfo.TextLen := Length(Text);
1946    Stream.WriteBuffer(OldInfo, SizeOf(TOldTreeNodeInfo));
1947  end
1948  else
1949  begin
1950    Info.ImageIndex := ImageIndex;
1951    Info.SelectedIndex := SelectedIndex;
1952    Info.OverlayIndex := OverlayIndex;
1953    Info.StateIndex := StateIndex;
1954    Info.Height := FHeight;
1955    Info.Count := Count;
1956    Info.Expanded := Expanded;
1957    Info.TextLen := Length(Text);
1958    Stream.WriteBuffer(Info, SizeOf(TTreeNodeInfo));
1959  end;
1960  if Text<>'' then
1961    Stream.Write(FText[1],length(Text));
1962  for i := 0 to Count - 1 do
1963    Items[i].WriteData(Stream, StreamVersion);
1964end;
1965
1966procedure TTreeNode.WriteDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
1967var
1968  I, Size, L, ItemCount: Integer;
1969begin
1970  L := Length(Text);
1971  if L > 255 then L := 255;
1972  Size := SizeOf(TDelphiNodeInfo) + L - 255;
1973  Info^.Text := Text;
1974  Info^.ImageIndex := ImageIndex;
1975  Info^.SelectedIndex := SelectedIndex;
1976  Info^.OverlayIndex := OverlayIndex;
1977  Info^.StateIndex := StateIndex;
1978  Info^.Data := {%H-}Cardinal(Data);
1979  ItemCount := Count;
1980  Info^.Count := ItemCount;
1981  Stream.WriteBuffer(Size, SizeOf(Size));
1982  Stream.WriteBuffer(Info^, Size);
1983  for I := 0 to ItemCount - 1 do
1984    Items[I].WriteDelphiData(Stream, Info);
1985end;
1986
1987procedure TTreeNode.Changed(ChangeReason: TTreeNodeChangeReason);
1988begin
1989  TreeView.NodeChanged(self,ChangeReason);
1990end;
1991
1992function TTreeNode.GetOwner: TPersistent;
1993begin
1994  Result := FOwner;
1995end;
1996
1997procedure TTreeNode.ConsistencyCheck;
1998var
1999  RealSubTreeCount: integer;
2000  i: integer;
2001  Node1: TTreeNode;
2002begin
2003  if FCapacity<0 then
2004    RaiseGDBException('');
2005  if FCapacity<FCount then
2006    RaiseGDBException('');
2007  if FCount<0 then
2008    RaiseGDBException('');
2009  if FHeight<0 then
2010    RaiseGDBException('');
2011  if (FItems<>nil) and (FCapacity<=0) then
2012    RaiseGDBException('');
2013  if (FCapacity>0) and (FItems=nil) then
2014    RaiseGDBException('');
2015  if (FNextBrother<>nil) and (FNextBrother.FPrevBrother<>Self) then
2016    RaiseGDBException('');
2017  if (FPrevBrother<>nil) and (FPrevBrother.FNextBrother<>Self) then
2018    RaiseGDBException('');
2019  if (FNextMultiSelected<>nil) and (FNextMultiSelected.FPrevMultiSelected<>Self) then
2020    RaiseGDBException('');
2021  if (FPrevMultiSelected<>nil) and (FPrevMultiSelected.FNextMultiSelected<>Self) then
2022    RaiseGDBException('');
2023  if MultiSelected then begin
2024    Node1:=TreeView.GetFirstMultiSelected;
2025    while (Node1<>nil) and (Node1<>Self) do Node1:=Node1.FNextMultiSelected;
2026    if Node1=nil then
2027      RaiseGDBException('');
2028  end;
2029  if Selected and (TreeView<>nil) and (tvoAllowMultiselect in TreeView.Options)
2030  and (not MultiSelected) then
2031    RaiseGDBException('');// selected, but not multiselected
2032
2033  // check children
2034  RealSubTreeCount:=1;
2035  for i:=0 to FCount-1 do begin
2036    if (Items[i]=nil) then RaiseGDBException('');
2037    Node1:=Items[i];
2038    if Node1.FParent<>Self then RaiseGDBException('');
2039    if (i=0) and (Node1.FPrevBrother<>nil) then
2040      RaiseGDBException('');
2041    if (i>0) and (Node1.FPrevBrother=nil) then
2042      RaiseGDBException('');
2043    if (i>0) and (Node1.FPrevBrother<>Items[i-1]) then
2044      RaiseGDBException('');
2045    if (i<FCount-1) and (Node1.FNextBrother=nil) then
2046      RaiseGDBException('');
2047    if (i<FCount-1) and (Node1.FNextBrother<>Items[i+1]) then
2048      RaiseGDBException('');
2049    if (i=FCount-1) and (Node1.FNextBrother<>nil) then
2050      RaiseGDBException('');
2051    if Node1.FIndex<>i then
2052      RaiseGDBException('');
2053    Node1.ConsistencyCheck;
2054    inc(RealSubTreeCount,Node1.SubTreeCount);
2055  end;
2056  if FParent<>nil then begin
2057    if FParent.IndexOf(Self)<0 then RaiseGDBException('');
2058  end;
2059  if RealSubTreeCount<>SubTreeCount then RaiseGDBException('');
2060  if FTop<0 then RaiseGDBException('');
2061  // check for circles
2062  if FNextBrother=Self then RaiseGDBException('');
2063  if FPrevBrother=Self then RaiseGDBException('');
2064  if FParent=Self then RaiseGDBException('');
2065  Node1:=FParent;
2066  while Node1<>nil do begin
2067    if (Node1=Self) then RaiseGDBException('');
2068    Node1:=Node1.FParent;
2069  end;
2070end;
2071
2072procedure TTreeNode.WriteDebugReport(const Prefix: string; Recurse: boolean);
2073var i: integer;
2074begin
2075  DbgOut('%s%s.WriteDebugReport Self=%p',[Prefix, ClassName, Pointer(Self)]);
2076  ConsistencyCheck;
2077  DebugLn(' Text=',Text);
2078  if Recurse then begin
2079    for i:=0 to FCount-1 do
2080      Items[i].WriteDebugReport(Prefix+'  ',true);
2081  end;
2082end;
2083
2084
2085{ TTreeNodes }
2086
2087constructor TTreeNodes.Create(AnOwner: TCustomTreeView);
2088begin
2089  inherited Create;
2090  FSelection := TFPList.Create;
2091  FOwner := AnOwner;
2092end;
2093
2094destructor TTreeNodes.Destroy;
2095begin
2096  Clear;
2097  FreeThenNil(FSelection);
2098  inherited Destroy;
2099end;
2100
2101function TTreeNodes.GetCount: Integer;
2102begin
2103  Result:=FCount;
2104end;
2105
2106function TTreeNodes.GetOwner: TPersistent;
2107begin
2108  Result := FOwner;
2109end;
2110
2111function TTreeNodes.GetHandle: THandle;
2112begin
2113  if Owner<>nil then
2114    Result:=Owner.Handle
2115  else
2116    Result:=0;
2117end;
2118
2119procedure TTreeNodes.Delete(Node: TTreeNode);
2120begin
2121  Node.Delete;
2122  if (FUpdateCount=0) and (Owner<>nil) then
2123    Owner.Invalidate;
2124end;
2125
2126procedure TTreeNodes.Clear;
2127var
2128  Node: TTreeNode;
2129begin
2130  BeginUpdate;
2131  ClearCache;
2132  Node := GetLastNode;
2133  if Assigned(Node) then
2134  begin
2135    while Assigned(Node) do
2136    begin
2137      Node.Delete;
2138      Node := GetLastNode;
2139    end;
2140  end;
2141  FSelection.Clear;
2142  if (FOwner <> nil) then
2143    FOwner.GetAccessibleObject().ClearChildAccessibleObjects();
2144  EndUpdate;
2145end;
2146
2147procedure TTreeNodes.ClearMultiSelection(ClearSelected: boolean = false);
2148var
2149  ANode, OldNode: TTreeNode;
2150begin
2151  if Assigned(Owner) then Owner.LockSelectionChangeEvent;
2152  try
2153    ANode := FFirstMultiSelected;
2154    while Assigned(ANode) do
2155    begin
2156      OldNode := ANode;
2157      ANode := ANode.GetNextMultiSelected;
2158      OldNode.MultiSelected := false;
2159    end;
2160    if ClearSelected then
2161      Owner.Selected := nil;
2162  finally
2163    if Assigned(Owner) then Owner.UnlockSelectionChangeEvent;
2164  end;
2165end;
2166
2167procedure TTreeNodes.SelectOnlyThis(Node: TTreeNode);
2168begin
2169  if Owner<>nil then Owner.LockSelectionChangeEvent;
2170  try
2171    ClearMultiSelection(true);
2172    Node.Selected:=true;
2173  finally
2174    if Owner<>nil then Owner.UnlockSelectionChangeEvent;
2175  end;
2176end;
2177
2178function TTreeNodes.IsMultiSelection: boolean;
2179begin
2180  Result:=(FFirstMultiSelected<>nil)
2181          and (FFirstMultiSelected.GetNextMultiSelected<>nil);
2182end;
2183
2184function TTreeNodes.AddChildFirst(ParentNode: TTreeNode; const S: string): TTreeNode;
2185begin
2186  Result := AddChildObjectFirst(ParentNode, S, nil);
2187end;
2188
2189function TTreeNodes.AddChildObjectFirst(ParentNode: TTreeNode; const S: string;
2190  Data: Pointer): TTreeNode;
2191begin
2192  Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
2193end;
2194
2195function TTreeNodes.AddChild(ParentNode: TTreeNode; const S: string): TTreeNode;
2196begin
2197  Result := AddChildObject(ParentNode, S, nil);
2198end;
2199
2200function TTreeNodes.AddChildObject(ParentNode: TTreeNode; const S: string;
2201  Data: Pointer): TTreeNode;
2202begin
2203  Result := InternalAddObject(ParentNode, S, Data, taAdd);
2204end;
2205
2206function TTreeNodes.AddFirst(SiblingNode: TTreeNode; const S: string): TTreeNode;
2207begin
2208  Result := AddObjectFirst(SiblingNode, S, nil);
2209end;
2210
2211function TTreeNodes.AddObjectFirst(SiblingNode: TTreeNode; const S: string;
2212  Data: Pointer): TTreeNode;
2213var ParentNode: TTreeNode;
2214begin
2215  if SiblingNode <> nil then
2216    ParentNode := SiblingNode.Parent
2217  else
2218    ParentNode := nil;
2219  Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
2220end;
2221
2222function TTreeNodes.AddNode(Node: TTreeNode; Relative: TTreeNode;
2223  const S: string; Ptr: Pointer; Method: TNodeAttachMode): TTreeNode;
2224var
2225   AddMode: TAddMode;
2226begin
2227  if (Relative=nil) and not (Method in [naAdd,naAddFirst]) then
2228    TreeNodeError('TTreeNode.AddNode Relative=nil');
2229  if Method=naInsertBehind then begin    // convert naInsertBehind
2230    if Relative.GetNextSibling=nil then
2231      Method:=naAdd
2232    else begin
2233      Method:=naInsert;
2234      Relative:=Relative.GetNextSibling;
2235    end;
2236  end;
2237  if (Relative <> nil) and (Method in [naAdd, naAddFirst]) then
2238    Relative := Relative.Parent;
2239  // Convert TNodeAttachMode to TAddMode
2240  case Method of
2241    naAddFirst,naAddChildFirst: AddMode := taAddFirst;
2242    naInsert: AddMode := taInsert;
2243  else
2244    AddMode:=taAdd;
2245  end;
2246  fNewNodeToBeAdded := Node;
2247  Result := InternalAddObject(Relative, S, Ptr, AddMode);
2248end;
2249
2250procedure TTreeNodes.SelectionsChanged(ANode: TTreeNode; const AIsSelected: Boolean);
2251begin
2252  if ANode <> nil then
2253  begin
2254    if AIsSelected then
2255      FSelection.Add(ANode)
2256    else
2257      FSelection.Remove(ANode);
2258  end;
2259end;
2260
2261function TTreeNodes.GetSelections(const AIndex: Integer): TTreeNode;
2262
2263  procedure RaiseOutOfBounds;
2264  begin
2265    TreeNodeError('TTreeNodes.GetSelections Index '+IntToStr(AIndex)
2266        +' out of bounds (Count='+IntToStr(FSelection.Count)+')');
2267  end;
2268
2269begin
2270  if (AIndex < 0) or (AIndex > FSelection.Count - 1) then
2271    RaiseOutOfBounds;
2272  Result := TTreeNode(FSelection.Items[AIndex]);
2273end;
2274
2275function TTreeNodes.Add(SiblingNode: TTreeNode; const S: string): TTreeNode;
2276begin
2277  Result := AddObject(SiblingNode, S, nil);
2278end;
2279
2280procedure TTreeNodes.Repaint(ANode: TTreeNode);
2281var
2282  R: TRect;
2283begin
2284  if (FUpdateCount < 1) and (Owner<>nil) then begin
2285    while (ANode <> nil) and not ANode.IsVisible do ANode := ANode.Parent;
2286    if ANode <> nil then begin
2287      R := ANode.DisplayRect(False);
2288      InvalidateRect(Owner.Handle, @R, True);
2289    end;
2290  end;
2291end;
2292
2293function TTreeNodes.AddObject(SiblingNode: TTreeNode; const S: string;
2294  Data: Pointer): TTreeNode;
2295var ParentNode: TTreeNode;
2296begin
2297  if SiblingNode <> nil then
2298    ParentNode := SiblingNode.Parent
2299  else
2300    ParentNode := nil;
2301  Result := InternalAddObject(ParentNode, S, Data, taAdd);
2302end;
2303
2304function TTreeNodes.Insert(NextNode: TTreeNode; const S: string): TTreeNode;
2305begin
2306  Result := InsertObject(NextNode, S, nil);
2307end;
2308
2309function TTreeNodes.InsertObject(NextNode: TTreeNode; const S: string;
2310  Data: Pointer): TTreeNode;
2311// create a new node with Text=S and Data=Data and insert in front of
2312// NextNode (as sibling with same parent).
2313begin
2314  Result:=InternalAddObject(NextNode,S,Data,taInsert);
2315end;
2316
2317function TTreeNodes.InsertBehind(PrevNode: TTreeNode; const S: string): TTreeNode;
2318begin
2319  Result := InsertObjectBehind(PrevNode, S, nil);
2320end;
2321
2322function TTreeNodes.InsertObjectBehind(PrevNode: TTreeNode; const S: string;
2323  Data: Pointer): TTreeNode;
2324// create a new node with Text=S and Data=Data and insert in front of
2325// NextNode (as sibling with same parent).
2326begin
2327  if (PrevNode<>nil) and (PrevNode.GetNextSibling<>nil) then
2328    Result:=InternalAddObject(PrevNode.GetNextSibling,S,Data,taInsert)
2329  else
2330    Result:=AddObject(PrevNode,S,Data);
2331end;
2332
2333procedure TTreeNodes.SortTopLevelNodes(SortProc: TTreeNodeCompare);
2334begin
2335  Sort(FTopLvlItems, FTopLvlCount, SortProc, true);
2336end;
2337
2338function TTreeNodes.InternalAddObject(Node: TTreeNode; const S: string;
2339  Data: Pointer; AddMode: TAddMode): TTreeNode;
2340{
2341  TAddMode = (taAddFirst, taAdd, taInsert);
2342
2343  taAdd:      add Result as last child of Node
2344  taAddFirst: add Result as first child of Node
2345  taInsert:   add Result in front of Node
2346}
2347var
2348  ok: boolean;
2349  // Item: HTreeItem;
2350  lAccessibleObject: TLazAccessibleObject;
2351begin
2352  if Owner=nil then
2353    TreeNodeError('TTreeNodes.InternalAddObject Owner=nil');
2354  {$IFDEF TREEVIEW_DEBUG}
2355  write('[TTreeNodes.InternalAddObject] Node=',Node<>nil,' S=',S,
2356        ' AddMode=',AddModeStr(AddMode));
2357  if Node<>nil then
2358    DbgOut(' Node.Text=',Node.Text);
2359  DebugLn('');
2360  {$ENDIF}
2361  Result := fNewNodeToBeAdded; // Used by AddNode to pass an existing node.
2362  if Result = Nil then
2363    Result := Owner.CreateNode;
2364  fNewNodeToBeAdded := nil;
2365  ok:=false;
2366  try
2367    Result.Data := Data;
2368    Result.Text := S;
2369    // move node in tree (tree of TTreeNode)
2370    Result.InternalMove(Node,AddMode);
2371    if (Owner<>nil) and Owner.AutoExpand and (Result.Parent<>nil) then
2372      Result.Parent.Expanded:=true;
2373    if (Owner<>nil) and (not (csReading in Owner.ComponentState)) then
2374      Owner.Added(Result);
2375    ok:=true;
2376    if ok and (Owner<>nil) and (Owner.AccessibilityOn) then
2377    begin
2378      lAccessibleObject := FOwner.GetAccessibleObject().AddChildAccessibleObject(Result);
2379      lAccessibleObject.AccessibleDescription := 'Item';
2380      lAccessibleObject.AccessibleValue := S;
2381      lAccessibleObject.AccessibleRole := larTreeItem;
2382    end;
2383  finally
2384    Include(FOwner.FStates, tvsScrollbarChanged);
2385    FOwner.UpdateScrollbars;
2386    // this construction creates nicer exception output
2387    if not ok then
2388      Result.Free;
2389  end;
2390end;
2391
2392function TTreeNodes.GetFirstNode: TTreeNode;
2393begin
2394  if Assigned(FTopLvlItems) then
2395    Result := FTopLvlItems[0]
2396  else
2397    Result := nil;
2398end;
2399
2400function TTreeNodes.GetFirstVisibleNode: TTreeNode;
2401var
2402  Node: TTreeNode;
2403  i: Integer;
2404begin
2405  Result := nil;
2406  if Assigned(FTopLvlItems) then
2407    for i := 0 to FTopLvlCount-1 do begin
2408      Node := FTopLvlItems[i];
2409      if Node.Visible then begin
2410        Result := Node;
2411        Break;
2412      end;
2413    end;
2414end;
2415
2416function TTreeNodes.GetLastNode: TTreeNode;
2417begin
2418  if Assigned(FTopLvlItems) then
2419    Result := FTopLvlItems[FTopLvlCount - 1]
2420  else
2421    Result := nil;
2422end;
2423
2424function TTreeNodes.GetLastVisibleNode: TTreeNode;
2425var
2426  Node: TTreeNode;
2427  i: Integer;
2428begin
2429  Result := nil;
2430  if Assigned(FTopLvlItems) then
2431    for i := FTopLvlCount-1 downto 0 do begin
2432      Node := FTopLvlItems[i];
2433      if Node.Visible then begin
2434        Result := Node;
2435        Break;
2436      end;
2437    end;
2438end;
2439
2440function TTreeNodes.GetLastSubNode: TTreeNode;
2441// absolute last node
2442var
2443  Node: TTreeNode;
2444begin
2445  Result := GetLastNode;
2446  if Assigned(Result) then
2447  begin
2448    Node := Result.GetLastSubChild;
2449    if Assigned(Node) then Result := Node;
2450  end;
2451end;
2452
2453function TTreeNodes.GetLastExpandedSubNode: TTreeNode;
2454// absolute last expanded node
2455var
2456  Node: TTreeNode;
2457begin
2458  Result := GetLastVisibleNode;
2459  while Assigned(Result) and (Result.Expanded) do
2460  begin
2461    Node := Result.GetLastVisibleChild;
2462    if Assigned(Node) then
2463      Result := Node
2464    else
2465      exit;
2466  end;
2467end;
2468
2469function TTreeNodes.FindTopLvlNode(const NodeText: string): TTreeNode;
2470begin
2471  Result := GetFirstNode;
2472  while Assigned(Result) and (Result.Text <> NodeText) do
2473    Result := Result.GetNextSibling;
2474end;
2475
2476function TTreeNodes.FindNodeWithText(const NodeText: string): TTreeNode;
2477begin
2478  Result := GetFirstNode;
2479  while Assigned(Result) and (Result.Text <> NodeText) do
2480    Result := Result.GetNext;
2481end;
2482
2483function TTreeNodes.FindNodeWithTextPath(TextPath: string): TTreeNode;
2484var
2485  p: SizeInt;
2486  CurText: String;
2487begin
2488  Result:=nil;
2489  repeat
2490    p:=System.Pos('/',TextPath);
2491    if p>0 then begin
2492      CurText:=LeftStr(TextPath,p-1);
2493      System.Delete(TextPath,1,p);
2494    end else begin
2495      CurText:=TextPath;
2496      TextPath:='';
2497    end;
2498    //debugln(['TTreeNodes.FindNodeWithTextPath CurText=',CurText,' Rest=',TextPath]);
2499    if Result=nil then
2500      Result:=FindTopLvlNode(CurText)
2501    else
2502      Result:=Result.FindNode(CurText);
2503  until (Result=nil) or (TextPath='');
2504end;
2505
2506function TTreeNodes.FindNodeWithData(const NodeData: Pointer): TTreeNode;
2507begin
2508  Result := GetFirstNode;
2509  while Assigned(Result) and (Result.Data <> NodeData) do
2510    Result := Result.GetNext;
2511end;
2512
2513function TTreeNodes.GetNodeFromIndex(Index: Integer): TTreeNode;
2514// find node with absolute index in ALL nodes (even collapsed)
2515
2516  procedure RaiseIndexOutOfBounds;
2517  begin
2518    TreeNodeError('TTreeNodes.GetNodeFromIndex Index '+IntToStr(Index)
2519           +' out of bounds (Count='+IntToStr(FCount)+')');
2520  end;
2521
2522  procedure RaiseSubTreeCount0;
2523  begin
2524    TreeNodeError(
2525      'TTreeNodes.GetNodeFromIndex: Consistency error - SubTreeCount=0');
2526  end;
2527
2528  procedure RaiseSubTreeCountTooBig;
2529  begin
2530    TreeNodeError(
2531      'TTreeNodes.GetNodeFromIndex: Consistency error - invalid SubTreeCount');
2532  end;
2533
2534  procedure RaiseCountTooBig;
2535  begin
2536    TreeNodeError(
2537      'TTreeNodes.GetNodeFromIndex: Consistency Error - Count too big');
2538  end;
2539
2540var
2541  I, J: Integer;
2542begin
2543  if (Index < 0) or (Index >= FCount) then
2544    RaiseIndexOutOfBounds;
2545  if (FNodeCache.CacheNode <> nil) and (Abs(FNodeCache.CacheIndex - Index) <= 1)
2546  then begin
2547    with FNodeCache do
2548    begin
2549      if Index = CacheIndex then Result := CacheNode
2550      else if Index < CacheIndex then Result := CacheNode.GetPrev
2551      else Result := CacheNode.GetNext;
2552    end;
2553  end
2554  else if Index>Count-5 then begin
2555    // optimization for the last nodes
2556    Result:=GetLastSubNode;
2557    i:=Count-1;
2558    while (Index<i) do begin
2559      Result:=Result.GetPrev;
2560      dec(i);
2561    end;
2562  end
2563  else begin
2564    Result := GetFirstNode;
2565    I:=0;
2566    while (Result<>nil) and (Index>I) do begin
2567      Repeat
2568        // calculate the absolute index of the next sibling
2569        J:=I+Result.FSubTreeCount;
2570        if J=I then RaiseSubTreeCount0;
2571        if J<=Index then begin
2572          // Index > absolute index of next sibling -> search in next sibling
2573          Result:=Result.GetNextSibling;
2574          I:=J;
2575        end else
2576          break;
2577      until false;
2578      if (Result<>nil) and (Index>I) then begin
2579        // Index is somewhere in subtree of Result
2580        Result:=Result.GetFirstChild;
2581        if Result=nil then RaiseSubTreeCountTooBig;
2582        inc(I);
2583      end;
2584    end;
2585  end;
2586  if Result = nil then RaiseCountTooBig;
2587  FNodeCache.CacheNode := Result;
2588  FNodeCache.CacheIndex := Index;
2589end;
2590
2591function TTreeNodes.GetSelectionCount: Cardinal;
2592begin
2593  Result := Cardinal(FSelection.Count);
2594end;
2595
2596procedure TTreeNodes.SetItem(Index: Integer; AValue: TTreeNode);
2597begin
2598  GetNodeFromIndex(Index).Assign(AValue);
2599end;
2600
2601procedure TTreeNodes.SetTopLvlItems(Index: integer; AValue: TTreeNode);
2602begin
2603  GetTopLvlItems(Index).Assign(AValue);
2604end;
2605
2606procedure TTreeNodes.BeginUpdate;
2607begin
2608  Inc(FUpdateCount);
2609end;
2610
2611procedure TTreeNodes.EndUpdate;
2612begin
2613  Dec(FUpdateCount);
2614  if FUpdateCount = 0 then
2615  begin
2616    Include(Owner.FStates,tvsScrollbarChanged);
2617    Owner.UpdateScrollbars;
2618    Owner.Invalidate;
2619  end;
2620end;
2621
2622procedure TTreeNodes.FreeAllNodeData;
2623var
2624  i: Integer;
2625begin
2626  BeginUpdate;
2627  for i:=0 to TopLvlCount-1 do
2628    TopLvlItems[i].FreeAllNodeData;
2629  EndUpdate;
2630end;
2631
2632function TTreeNodes.GetEnumerator: TTreeNodesEnumerator;
2633begin
2634  Result := TTreeNodesEnumerator.Create(Self);
2635end;
2636
2637procedure TTreeNodes.GrowTopLvlItems;
2638begin
2639  if FTopLvlItems<>nil then begin
2640    FTopLvlCapacity:=FTopLvlCapacity shl 1;
2641    ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
2642  end else begin
2643    FTopLvlCapacity:=MinNodeCapacity;
2644    GetMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
2645  end;
2646  //debugln('TTreeNodes.GrowTopLvlItems END FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
2647end;
2648
2649function TTreeNodes.GetTopLvlItems(Index: integer): TTreeNode;
2650begin
2651  Result:=FTopLvlItems[Index];
2652end;
2653
2654procedure TTreeNodes.ShrinkTopLvlItems;
2655var
2656  OldCapacity: LongInt;
2657begin
2658  if FTopLvlItems<>nil then begin
2659    OldCapacity:=FTopLvlCapacity;
2660    FTopLvlCapacity:=FTopLvlCapacity shr 1;
2661    if FTopLvlCapacity<FTopLvlCount then FTopLvlCapacity:=FTopLvlCount;
2662    if (FTopLvlCapacity<MinNodeCapacity) then begin
2663      if (FTopLvlCount>0) then
2664        FTopLvlCapacity:=MinNodeCapacity
2665      else
2666        FTopLvlCapacity:=0;
2667    end;
2668    if OldCapacity=FTopLvlCapacity then exit;
2669    //debugln('TTreeNodes.ShrinkTopLvlItems A FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
2670    ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
2671    //debugln('TTreeNodes.ShrinkTopLvlItems B FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
2672  end else begin
2673    if (FTopLvlCapacity>0) then
2674      TreeNodeError('TTreeNodes.ShrinkTopLvlItems FTopLvlCapacity>0');
2675  end;
2676end;
2677
2678function TTreeNodes.IndexOfTopLvlItem(Node: TTreeNode): integer;
2679begin
2680  if (Node<>nil) and (Node.Owner=Self) then
2681    Result:=Node.FIndex
2682  else
2683    Result:=-1;
2684end;
2685
2686procedure TTreeNodes.MoveTopLvlNode(TopLvlFromIndex, TopLvlToIndex: integer;
2687  Node: TTreeNode);
2688// TopLvlFromIndex = -1 and is insert
2689// TopLvlToIndex = -1 is remove
2690var i: integer;
2691begin
2692  {$IFDEF TREEVIEW_DEBUG}
2693  DebugLn('[TTreeNodes.MoveTopLvlNode] TopLvlFromIndex=',TopLvlFromIndex,
2694  ' TopLvlToIndex=',TopLvlToIndex,' Node.Text=',Node.Text);
2695  {$ENDIF}
2696  if TopLvlFromIndex=TopLvlToIndex then exit;
2697  if (TopLvlFromIndex>=FTopLvlCount) then
2698    TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
2699  if (TopLvlToIndex>FTopLvlCount) then
2700    TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
2701  if (TopLvlFromIndex>=0) then begin
2702    Node:=FTopLvlItems[TopLvlFromIndex];
2703    if (TopLvlToIndex>=0) then begin
2704      // move node
2705      if TopLvlFromIndex<TopLvlToIndex then begin
2706        // move forward
2707        for i:=TopLvlFromIndex to TopLvlToIndex-1 do begin
2708          FTopLvlItems[i]:=FTopLvlItems[i+1];
2709          FTopLvlItems[i].FIndex:=i;
2710        end;
2711      end else begin
2712        // move backward
2713        for i:=TopLvlToIndex downto TopLvlFromIndex+1 do begin
2714          FTopLvlItems[i]:=FTopLvlItems[i-1];
2715          FTopLvlItems[i].FIndex:=i;
2716        end;
2717      end;
2718      FTopLvlItems[TopLvlToIndex]:=Node;
2719      FTopLvlItems[TopLvlToIndex].FIndex:=TopLvlToIndex;
2720    end else begin
2721      // remove node
2722      if FTopLvlItems<>nil then begin
2723        for i:=TopLvlFromIndex to FTopLvlCount-2 do begin
2724          FTopLvlItems[i]:=FTopLvlItems[i+1];
2725          FTopLvlItems[i].FIndex:=i;
2726        end;
2727      end;
2728      Dec(FTopLvlCount);
2729      if FTopLvlCount<0 then
2730        TreeNodeError('TTreeNodes.MoveTopLvlNode FTopLvlCount<0');
2731      if FTopLvlCount<(FTopLvlCapacity shr 2) then ShrinkTopLvlItems;
2732    end;
2733  end else begin
2734    if (TopLvlToIndex>=0) then begin
2735      if Node=nil then
2736        TreeNodeError('TTreeNodes.MoveTopLvlNode inserting nil');
2737      // insert node
2738      if (FTopLvlCount=FTopLvlCapacity) then GrowTopLvlItems;
2739      inc(FTopLvlCount);
2740      if FTopLvlItems<>nil then begin
2741        for i:=FTopLvlCount-1 downto TopLvlToIndex+1 do begin
2742          FTopLvlItems[i]:=FTopLvlItems[i-1];
2743          FTopLvlItems[i].FIndex:=i;
2744        end;
2745        FTopLvlItems[TopLvlToIndex]:=Node;
2746        FTopLvlItems[TopLvlToIndex].FIndex:=TopLvlToIndex;
2747      end;
2748    end else begin
2749      // nothing to do
2750    end;
2751  end;
2752end;
2753
2754procedure TTreeNodes.MultiSelect(Node: TTreeNode; ClearWholeSelection: Boolean);
2755var
2756  bGoNext, bOnlySiblings, bOnlyVisible: Boolean;
2757  //
2758  procedure _TakeNext(var N: TTreeNode);
2759  begin
2760    if bGoNext then
2761    begin
2762      if bOnlySiblings and bOnlyVisible then
2763        N := N.GetNextVisibleSibling
2764      else
2765      if bOnlySiblings and not bOnlyVisible then
2766        N := N.GetNextSibling
2767      else
2768      if not bOnlySiblings and bOnlyVisible then
2769        N := N.GetNextVisible
2770      else
2771        N := N.GetNext;
2772    end
2773    else
2774    begin
2775      if bOnlySiblings and bOnlyVisible then
2776        N := N.GetPrevVisibleSibling
2777      else
2778      if bOnlySiblings and not bOnlyVisible then
2779        N := N.GetPrevSibling
2780      else
2781      if not bOnlySiblings and bOnlyVisible then
2782        N := N.GetPrevVisible
2783      else
2784        N := N.GetPrev;
2785    end;
2786  end;
2787  //
2788var
2789  I, FirstNode, LastNode: TTreeNode;
2790begin
2791  if Owner<>nil then Owner.LockSelectionChangeEvent;
2792  bOnlySiblings := Assigned(Owner) and (msSiblingOnly in Owner.MultiSelectStyle);
2793  bOnlyVisible := Assigned(Owner) and (msVisibleOnly in Owner.MultiSelectStyle);
2794
2795  try
2796    if FStartMultiSelected=nil then
2797    begin
2798      FirstNode := Node;
2799      FStartMultiSelected := Node;
2800    end else
2801      FirstNode := FStartMultiSelected;
2802
2803    if ClearWholeSelection then
2804    begin
2805      ClearMultiSelection(True);
2806    end else
2807    begin
2808      //clear only last selection
2809      if Assigned(FLastMultiSelected) then
2810      begin
2811        LastNode := FLastMultiSelected;
2812        bGoNext := (FirstNode.Index <= LastNode.Index);
2813        I := FirstNode;
2814        I.MultiSelected:=False;
2815        while (I<>LastNode) do
2816        begin
2817          _TakeNext(I);
2818          if I=nil then Break;
2819          I.MultiSelected:=False;
2820        end;
2821      end;
2822      if Assigned(Owner) then
2823        Owner.Selected := nil;
2824    end;
2825
2826    //select again
2827    bGoNext := (FirstNode.AbsoluteIndex <= Node.AbsoluteIndex);
2828    I := FirstNode;
2829    I.MultiSelected:=True;
2830    while (I<>Node) do
2831    begin
2832      _TakeNext(I);
2833      if I=nil then Break;
2834      I.MultiSelected:=True;
2835    end;
2836
2837    FStartMultiSelected := FirstNode;
2838    FLastMultiSelected := Node;
2839  finally
2840    if Owner<>nil then Owner.UnlockSelectionChangeEvent;
2841  end;
2842end;
2843
2844procedure TTreeNodes.Assign(Source: TPersistent);
2845var
2846  SrcNodes: TTreeNodes;
2847  SrcStream: TMemoryStream;
2848begin
2849  ClearCache;
2850  if Source is TTreeNodes then begin
2851    SrcNodes := TTreeNodes(Source);
2852    Clear;
2853    SrcStream := TMemoryStream.Create;
2854    try
2855      SrcNodes.WriteData(SrcStream, true);
2856      SrcStream.Position := 0;
2857      ReadData(SrcStream);
2858    finally
2859      SrcStream.Free;
2860    end;
2861  end
2862  else
2863    inherited Assign(Source);
2864end;
2865
2866procedure TTreeNodes.DefineProperties(Filer: TFiler);
2867
2868  function WriteNodes: Boolean;
2869  var
2870    I: Integer;
2871    Nodes: TTreeNodes;
2872  begin
2873    Nodes := TTreeNodes(Filer.Ancestor);
2874    if Nodes = nil then
2875      Result := Count > 0
2876    else if Nodes.Count <> Count then
2877      Result := True
2878    else
2879    begin
2880      Result := False;
2881      for I := 0 to Count - 1 do
2882      begin
2883        Result := not Item[I].IsEqual(Nodes[I]);
2884        if Result then Break;
2885      end
2886    end;
2887  end;
2888
2889begin
2890  inherited DefineProperties(Filer);
2891  Filer.DefineBinaryProperty('Data', @ReadData, @WriteData, WriteNodes);
2892end;
2893
2894procedure TTreeNodes.ReadData(Stream: TStream);
2895var
2896  I, NewCount, MagicNumber: Integer;
2897  DelphiNodeInfo: TDelphiNodeInfo;
2898  StreamVersion: word;
2899begin
2900  Clear;
2901  // -7 for lcl stream
2902  Stream.ReadBuffer(MagicNumber,SizeOf(Integer));
2903  if MagicNumber=LCLStreamID then begin
2904    // read stream version
2905    Stream.ReadBuffer(StreamVersion,SizeOf(StreamVersion));
2906    // read top level node count
2907    Stream.ReadBuffer(NewCount, SizeOf(NewCount));
2908    for I := 0 to NewCount - 1 do
2909      Add(nil, '').ReadData(Stream, StreamVersion);
2910  end else begin
2911    // delphi stream
2912    NewCount:=MagicNumber;
2913    for I := 0 to NewCount - 1 do
2914      Add(nil, '').ReadDelphiData(Stream, @DelphiNodeInfo);
2915  end;
2916end;
2917
2918procedure TTreeNodes.WriteData(Stream: TStream);
2919begin
2920   WriteData(Stream, false);
2921end;
2922
2923procedure TTreeNodes.WriteData(Stream: TStream; WriteDataPointer: boolean);
2924var
2925  ANode: TTreeNode;
2926  MagicNumber: integer;
2927  StreamVersion: word;
2928begin
2929  // -7 for lcl stream
2930  MagicNumber:=LCLStreamID;
2931  Stream.WriteBuffer(MagicNumber,SizeOf(MagicNumber));
2932  // write stream version
2933  if WriteDataPointer then
2934    StreamVersion:=TTreeNodeWithPointerStreamVersion
2935  else
2936    StreamVersion:=TTreeNodeStreamVersion;
2937  Stream.WriteBuffer(StreamVersion,SizeOf(Word));
2938  // write top level node count
2939  Stream.WriteBuffer(FTopLvlCount, SizeOf(Integer));
2940  // write all nodes recursively
2941  ANode := GetFirstNode;
2942  while ANode <> nil do begin
2943    ANode.WriteData(Stream, StreamVersion);
2944    ANode := ANode.GetNextSibling;
2945  end;
2946end;
2947
2948procedure TTreeNodes.ReadExpandedState(Stream: TStream);
2949var
2950  ItemCount,
2951  Index: Integer;
2952  Node: TTreeNode;
2953  NodeExpanded: Boolean;
2954begin
2955  if Stream.Position < Stream.Size then
2956    Stream.ReadBuffer(ItemCount, SizeOf(ItemCount))
2957  else Exit;
2958  Index := 0;
2959  Node := GetFirstNode;
2960  while (Index < ItemCount) and (Node <> nil) do begin
2961    Stream.ReadBuffer(NodeExpanded, SizeOf(NodeExpanded));
2962    Node.Expanded := NodeExpanded;
2963    Inc(Index);
2964    Node := Node.GetNext;
2965  end;
2966end;
2967
2968procedure TTreeNodes.WriteExpandedState(Stream: TStream);
2969var
2970  Size: Integer;
2971  ANode: TTreeNode;
2972  NodeExpanded: Boolean;
2973begin
2974  Size := SizeOf(Boolean) * Count;
2975  Stream.WriteBuffer(Size, SizeOf(Size));
2976  ANode := GetFirstNode;
2977  while (ANode <> nil) do begin
2978    NodeExpanded := ANode.Expanded;
2979    Stream.WriteBuffer(NodeExpanded, SizeOf(Boolean));
2980    ANode := ANode.GetNext;
2981  end;
2982end;
2983
2984procedure TTreeNodes.ClearCache;
2985begin
2986  FNodeCache.CacheNode := nil;
2987end;
2988
2989procedure TTreeNodes.ConsistencyCheck;
2990var
2991  Node: TTreeNode;
2992  RealCount, i: integer;
2993  OldCache: TNodeCache;
2994begin
2995  if FUpdateCount<0 then
2996    RaiseGDBException('FUpdateCount<0');
2997  RealCount:=0;
2998  Node:=GetFirstNode;
2999  while Node<>nil do begin
3000    Node.ConsistencyCheck;
3001    inc(RealCount,Node.SubTreeCount);
3002    //DebugLn(' ConsistencyCheck: B  ',RealCount,',',Node.SubTreeCount);
3003    Node:=Node.FNextBrother;
3004  end;
3005  //DebugLn(' ConsistencyCheck: B  ',RealCount,',',FCount);
3006  if RealCount<>FCount then
3007    RaiseGDBException('RealCount<>FCount');
3008  if (FTopLvlCapacity<=0) and (FTopLvlItems<>nil) then
3009    RaiseGDBException('');
3010  if (FTopLvlCapacity>0) and (FTopLvlItems=nil) then
3011    RaiseGDBException('');
3012  if FTopLvlCapacity<FTopLvlCount then
3013    RaiseGDBException('');
3014  if (FTopLvlCount<0) then
3015    RaiseGDBException('');
3016  for i:=0 to FTopLvlCount-1 do begin
3017    Node:=FTopLvlItems[i];
3018    if (i=0) and (Node.FPrevBrother<>nil) then
3019      RaiseGDBException('');
3020    if (i>0) and (Node.FPrevBrother<>FTopLvlItems[i-1]) then
3021      RaiseGDBException('');
3022    if (i<FTopLvlCount-1) and (Node.FNextBrother<>FTopLvlItems[i+1])
3023    then begin
3024      DebugLn(' CONSISTENCY i=%d FTopLvlCount=%d FTopLvlItems[i]=%p FTopLvlItems[i].FNextBrother=%p FTopLvlItems[i+1]=%p',
3025             [i, FTopLvlCount, Pointer(Node), Pointer(Node.FNextBrother), Pointer(FTopLvlItems[i+1])]);
3026      RaiseGDBException('');
3027    end;
3028    if (i=FTopLvlCount-1) and (Node.FNextBrother<>nil) then
3029      RaiseGDBException('');
3030    if Node.FIndex<>i then
3031      RaiseGDBException('');
3032  end;
3033  if FNodeCache.CacheNode<>nil then begin
3034    OldCache:=FNodeCache;
3035    ClearCache;
3036    if GetNodeFromIndex(OldCache.CacheIndex)<>OldCache.CacheNode then
3037      RaiseGDBException('');
3038  end;
3039end;
3040
3041procedure TTreeNodes.WriteDebugReport(const Prefix: string; AllNodes: boolean);
3042var
3043  Node: TTreeNode;
3044begin
3045  DbgOut('%s%s.WriteDebugReport Self=%p', [Prefix, ClassName, Pointer(Self)]);
3046  ConsistencyCheck;
3047  DebugLn('');
3048  if AllNodes then begin
3049    Node:=GetFirstNode;
3050    while Node<>nil do begin
3051      Node.WriteDebugReport(Prefix+'  ',true);
3052      Node:=Node.GetNextSibling;
3053    end;
3054  end;
3055end;
3056
3057
3058type
3059  TTreeStrings = class(TStrings)
3060  private
3061    FOwner: TTreeNodes;
3062  protected
3063    function Get(Index: Integer): string; override;
3064    function GetBufStart(Buffer: PChar; var Level: Integer): PChar;
3065    function GetCount: Integer; override;
3066    function GetObject(Index: Integer): TObject; override;
3067    procedure PutObject(Index: Integer; AObject: TObject); override;
3068    procedure SetUpdateState(Updating: Boolean); override;
3069  public
3070    constructor Create(AnOwner: TTreeNodes);
3071    function Add(const S: string): Integer; override;
3072    procedure Clear; override;
3073    procedure Delete(Index: Integer); override;
3074    procedure Insert(Index: Integer; const S: string); override;
3075    procedure LoadTreeFromStream(Stream: TStream);
3076    procedure SaveTreeToStream(Stream: TStream);
3077    function ConsistencyCheck: integer;
3078    procedure WriteDebugReport(const Prefix: string);
3079    property Owner: TTreeNodes read FOwner;
3080  end;
3081
3082constructor TTreeStrings.Create(AnOwner: TTreeNodes);
3083begin
3084  inherited Create;
3085  FOwner := AnOwner;
3086end;
3087
3088function TTreeStrings.Get(Index: Integer): string;
3089const
3090  TabChar = #9;
3091var
3092  Level, I: Integer;
3093  Node: TTreeNode;
3094begin
3095  Result := '';
3096  Node := Owner.GetNodeFromIndex(Index);
3097  Level := Node.Level;
3098  for I := 0 to Level - 1 do
3099    Result := Result + TabChar;
3100  Result := Result + Node.Text;
3101end;
3102
3103function TTreeStrings.GetBufStart(Buffer: PChar; var Level: Integer): PChar;
3104begin
3105  Level := 0;
3106  while Buffer^ in [' ', #9] do
3107  begin
3108    Inc(Buffer);
3109    Inc(Level);
3110  end;
3111  Result := Buffer;
3112end;
3113
3114function TTreeStrings.GetObject(Index: Integer): TObject;
3115begin
3116  Result := TObject(Owner.GetNodeFromIndex(Index).Data);
3117end;
3118
3119procedure TTreeStrings.PutObject(Index: Integer; AObject: TObject);
3120begin
3121  Owner.GetNodeFromIndex(Index).Data := AObject;
3122end;
3123
3124function TTreeStrings.GetCount: Integer;
3125begin
3126  Result := Owner.Count;
3127end;
3128
3129procedure TTreeStrings.Clear;
3130begin
3131  Owner.Clear;
3132end;
3133
3134procedure TTreeStrings.Delete(Index: Integer);
3135begin
3136  Owner.GetNodeFromIndex(Index).Delete;
3137end;
3138
3139procedure TTreeStrings.SetUpdateState(Updating: Boolean);
3140begin
3141  //SendMessage(Owner.Handle, WM_SETREDRAW, Ord(not Updating), 0);
3142  if not Updating then
3143    Owner.Owner.Invalidate;
3144end;
3145
3146function TTreeStrings.Add(const S: string): Integer;
3147var
3148  Level, OldLevel, I: Integer;
3149  NewStr: string;
3150  Node: TTreeNode;
3151begin
3152  Result := GetCount;
3153  if (Length(S) = 1) and (S[1] = Chr($1A)) then Exit;
3154  Node := nil;
3155  OldLevel := 0;
3156  NewStr := GetBufStart(PChar(S), Level);
3157  if Result > 0 then
3158  begin
3159    Node := Owner.GetNodeFromIndex(Result - 1);
3160    OldLevel := Node.Level;
3161  end;
3162  if (Level > OldLevel) or (Node = nil) then
3163  begin
3164    if Level - OldLevel > 1 then
3165      TreeViewError('TTreeStrings.Add: Invalid level, Level='+IntToStr(Level)
3166        +' OldLevel='+IntToStr(OldLevel));
3167  end
3168  else begin
3169    for I := OldLevel downto Level do
3170    begin
3171      Node := Node.Parent;
3172      if (Node = nil) and (I - Level > 0) then
3173        TreeViewError('TTreeStrings.Add: Invalid level, Node=nil I='+IntToStr(I)
3174          +' Level='+IntToStr(Level));
3175    end;
3176  end;
3177  Owner.AddChild(Node, NewStr);
3178end;
3179
3180procedure TTreeStrings.Insert(Index: Integer; const S: string);
3181begin
3182  with Owner do
3183    Insert(GetNodeFromIndex(Index), S);
3184end;
3185
3186procedure TTreeStrings.LoadTreeFromStream(Stream: TStream);
3187var
3188  List: TStringList;
3189  ANode, NextNode: TTreeNode;
3190  ALevel, i: Integer;
3191  CurrStr: string;
3192begin
3193  List := TStringList.Create;
3194  Owner.BeginUpdate;
3195  try
3196    Clear;
3197    List.LoadFromStream(Stream);
3198    ANode := nil;
3199    for i := 0 to List.Count - 1 do
3200    begin
3201      CurrStr := GetBufStart(PChar(List[i]), ALevel);
3202      if ANode = nil then
3203        ANode := Owner.AddChild(nil, CurrStr)
3204      else if ANode.Level = ALevel then
3205        ANode := Owner.AddChild(ANode.Parent, CurrStr)
3206      else if ANode.Level = (ALevel - 1) then
3207        ANode := Owner.AddChild(ANode, CurrStr)
3208      else if ANode.Level > ALevel then
3209      begin
3210        NextNode := ANode.Parent;
3211        while NextNode.Level > ALevel do
3212          NextNode := NextNode.Parent;
3213        ANode := Owner.AddChild(NextNode.Parent, CurrStr);
3214      end
3215      else TreeViewError('TTreeStrings.LoadTreeFromStream: Level='
3216        +IntToStr(ALevel)+' CuurStr="'+CurrStr+'"');
3217    end;
3218  finally
3219    Owner.EndUpdate;
3220    List.Free;
3221  end;
3222end;
3223
3224procedure TTreeStrings.SaveTreeToStream(Stream: TStream);
3225const
3226  TabChar = #9;
3227  EndOfLine = #13#10;
3228var
3229  i: Integer;
3230  ANode: TTreeNode;
3231  NodeStr: string;
3232begin
3233  if Count > 0 then
3234  begin
3235    ANode := Owner[0];
3236    while ANode <> nil do
3237    begin
3238      NodeStr := '';
3239      for i := 0 to ANode.Level - 1 do NodeStr := NodeStr + TabChar;
3240      NodeStr := NodeStr + ANode.Text + EndOfLine;
3241      Stream.Write(Pointer(NodeStr)^, Length(NodeStr));
3242      ANode := ANode.GetNext;
3243    end;
3244  end;
3245end;
3246
3247function TTreeStrings.ConsistencyCheck: integer;
3248begin
3249  Result:=0;
3250end;
3251
3252procedure TTreeStrings.WriteDebugReport(const Prefix: string);
3253begin
3254  DebugLn('%sTTreeStrings.WriteDebugReport Self=%p Consistency=%d', [Prefix, Pointer(Self), ConsistencyCheck]);
3255end;
3256
3257
3258{ TCustomTreeView }
3259
3260constructor TCustomTreeView.Create(AnOwner: TComponent);
3261var
3262  Details: TThemedElementDetails;
3263begin
3264  inherited Create(AnOwner);
3265  ControlStyle := ControlStyle - [csCaptureMouse]
3266                               + [csDisplayDragImage, csReflector];
3267  Width := 121;
3268  Height := 97;
3269  Color := clWindow;
3270
3271  FSBVertShowing:=-1;
3272  FSBHorzShowing:=-1;
3273  TabStop := True;
3274  ParentColor := False;
3275//  FBackgroundColor := clWindow;
3276  FDefItemHeight := DefaultTreeNodeHeight;
3277  FDefItemSpace := ScaleY(2, 96);
3278  FExpandSignType := tvestTheme;
3279  FExpandSignSize := -1;
3280  Details := ThemeServices.GetElementDetails(ttGlyphOpened);
3281  FThemeExpandSignSize := ThemeServices.GetDetailSize(Details).cx;
3282  FTreeNodes := CreateNodes;
3283  BorderStyle := bsSingle;
3284  BorderWidth := 0;
3285  FMultiSelectStyle := DefaultMultiSelectStyle;
3286  FOptions := DefaultTreeViewOptions;
3287  Items.KeepCollapsedNodes:=KeepCollapsedNodes;
3288  FScrollBars:=ssBoth;
3289  FDragImage := TDragImageList.CreateSize(32, 32);
3290  FIndent:=-1;
3291  FChangeTimer := TTimer.Create(Self);
3292  FChangeTimer.Enabled := False;
3293  FChangeTimer.Interval := 1;
3294  FChangeTimer.OnTimer := @OnChangeTimer;
3295  FImageChangeLink := TChangeLink.Create;
3296  FImageChangeLink.OnChange := @ImageListChange;
3297  FSelectedColor:=clHighlight;
3298  FSelectedFontColor:=clWhite;
3299  FSelectedFontColorUsed:=false;
3300  fSeparatorColor:=clGray;
3301  FStateChangeLink := TChangeLink.Create;
3302  FStateChangeLink.OnChange := @ImageListChange;
3303  FStates:=[tvsMaxLvlNeedsUpdate,tvsMaxRightNeedsUpdate,tvsScrollbarChanged];
3304  FTreeLineColor := clWindowFrame;
3305  FTreeLinePenStyle := psPattern;
3306  SetLength(FTreeLinePenPattern, 2);
3307  FTreeLinePenPattern[0] := 1;
3308  FTreeLinePenPattern[1] := 1;
3309  FExpandSignColor := clWindowFrame;
3310  FHotTrackColor := clNone;
3311  FDisabledFontColor := clGrayText;
3312  // Accessibility
3313  AccessibleDescription := rsTTreeViewAccessibilityDescription;
3314  AccessibleRole := larTreeView;
3315  FAccessibilityOn := WidgetSet.GetLCLCapability(lcAccessibilitySupport) = LCL_CAPABILITY_YES;
3316
3317  FDragScrollMargin := 20; // Height of 2 areas, on top/bottom of treeview, which auto-scroll treeview up/down
3318  FDragScrollTimer := TTimer.Create(Self);
3319  FDragScrollTimer.Enabled := false;
3320  FDragScrollTimer.Interval := 150;
3321  FDragScrollTimer.OnTimer := @DragScrollTimerTick;
3322end;
3323
3324destructor TCustomTreeView.Destroy;
3325begin
3326  LockSelectionChangeEvent; // prevent change event during destroying
3327
3328  Images:=nil;
3329  FreeThenNil(FTreeNodes);
3330  FreeThenNil(FSaveItems);
3331  FreeThenNil(FDragImage);
3332  FreeThenNil(FImageChangeLink);
3333  FreeThenNil(FStateChangeLink);
3334  inherited Destroy;
3335end;
3336
3337procedure TCustomTreeView.CreateWnd;
3338begin
3339  Exclude(FStates,tvsStateChanging);
3340  FSBHorzShowing:=-1;
3341  FSBVertShowing:=-1;
3342  inherited CreateWnd;
3343end;
3344
3345procedure TCustomTreeView.Click;
3346begin
3347  if not FMouseDownOnFoldingSign then
3348    inherited;
3349end;
3350
3351procedure TCustomTreeView.DblClick;
3352begin
3353  if not FMouseDownOnFoldingSign then
3354    inherited;
3355end;
3356
3357procedure TCustomTreeView.TripleClick;
3358begin
3359  if not FMouseDownOnFoldingSign then
3360    inherited;
3361end;
3362
3363procedure TCustomTreeView.QuadClick;
3364begin
3365  if not FMouseDownOnFoldingSign then
3366    inherited;
3367end;
3368
3369procedure TCustomTreeView.InitializeWnd;
3370begin
3371  inherited InitializeWnd;
3372  UpdateDefaultItemHeight;
3373end;
3374
3375procedure TCustomTreeView.Invalidate;
3376begin
3377  if tvsPainting in FStates then exit;
3378  inherited Invalidate;
3379end;
3380
3381procedure TCustomTreeView.EraseBackground(DC: HDC);
3382begin
3383  // everything is painted, so erasing the background is not needed
3384end;
3385
3386procedure TCustomTreeView.DestroyWnd;
3387begin
3388  Include(FStates, tvsStateChanging);
3389  inherited DestroyWnd;
3390  if Canvas <> nil then
3391    TControlCanvas(Canvas).FreeHandle;
3392  FLastHorzScrollInfo.cbSize := 0;
3393  FLastVertScrollInfo.cbSize := 0;
3394end;
3395
3396procedure TCustomTreeView.DoAutoAdjustLayout(
3397  const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double
3398  );
3399begin
3400  inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion);
3401
3402  if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
3403  begin
3404    DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomTreeView.DoAutoAdjustLayout'){$ENDIF};
3405    try
3406      if DefaultItemHeightIsStored then
3407        DefaultItemHeight := Round(DefaultItemHeight*AYProportion);
3408      if IndentIsStored then
3409        FIndent := Round(FIndent*AXProportion);
3410      if ExpandSignSizeIsStored then
3411        FExpandSignSize := Round(FExpandSignSize*AXProportion);
3412    finally
3413      EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomTreeView.DoAutoAdjustLayout'){$ENDIF};
3414    end;
3415  end;
3416end;
3417
3418procedure TCustomTreeView.DoCreateNodeClass(var NewNodeClass: TTreeNodeClass);
3419begin
3420  if Assigned(OnCreateNodeClass) then
3421    OnCreateNodeClass(Self,NewNodeClass);
3422end;
3423
3424procedure TCustomTreeView.BeginAutoDrag;
3425begin
3426  BeginDrag(False);
3427end;
3428
3429procedure TCustomTreeView.BeginEditing(ANode: TTreeNode);
3430var
3431  ARect: TRect;
3432begin
3433  //DebugLn(['TCustomTreeView.BeginEditing tvsIsEditing=',tvsIsEditing in FStates,' Selected=',Selected<>nil]);
3434  if (tvsIsEditing in FStates) or (ANode=nil) then exit;
3435  if (not CanEdit(ANode)) or (not ANode.Visible) then exit;
3436  // if we are asked to edit another node while one is already being edited then
3437  // stop editing that node
3438  if FEditingItem <> nil then
3439    EndEditing;
3440  FEditingItem := ANode;
3441  // make node visible (this will cancel editing, so call this first)
3442  EnsureNodeIsVisible(ANode);
3443
3444  Include(FStates,tvsIsEditing);
3445  if FEditor=nil then begin
3446    FEditor:=TEdit.Create(Self);
3447    FEditor.OnEditingDone:=@EditorEditingDone;
3448    FEditor.OnKeyDown:=@EditorKeyDown;
3449  end;
3450  ARect:=Rect(Max(BorderWidth,ANode.DisplayTextLeft),ANode.Top-ScrolledTop,
3451              ClientWidth-BorderWidth,ANode.Bottom-ScrolledTop);
3452  FEditor.BoundsRect:=ARect;
3453  FEditor.AnchorParallel(akLeft,ARect.Left,Self);
3454  FEditor.AnchorParallel(akRight,BorderWidth,Self);
3455  FEditor.Visible:=true;
3456  FEditor.Parent:=Self;
3457  FEditor.Text:=ANode.Text;
3458  FEditor.SelectAll;
3459  FEditor.SetFocus;
3460end;
3461
3462procedure TCustomTreeView.BeginUpdate;
3463begin
3464  Items.BeginUpdate;
3465  LockSelectionChangeEvent;
3466end;
3467
3468procedure TCustomTreeView.EndUpdate;
3469begin
3470  UnlockSelectionChangeEvent;
3471  if Items.FUpdateCount<=0 then RaiseGDBException('TCustomTreeView.EndUpdate');
3472  Items.EndUpdate;
3473  if Items.FUpdateCount=0 then begin
3474    // ToDo: only refresh if something changed
3475    UpdateScrollBars;
3476  end;
3477end;
3478
3479function TCustomTreeView.AlphaSort: Boolean;
3480begin
3481  Result := CustomSort(nil);
3482end;
3483
3484function TCustomTreeView.CustomSort(SortProc: TTreeNodeCompare): Boolean;
3485var Node: TTreeNode;
3486begin
3487  Result := False;
3488  if FTreeNodes.Count>0 then begin
3489    BeginUpdate;
3490    if not assigned(SortProc) then SortProc := @DefaultTreeViewSort;
3491    FTreeNodes.SortTopLevelNodes(SortProc);
3492
3493    Node := FTreeNodes.GetFirstNode;
3494    while Node <> nil do begin
3495      if (Node.GetFirstChild<>nil) then Node.CustomSort(SortProc);
3496      Node := Node.GetNext;
3497    end;
3498    Items.ClearCache;
3499    FStates:= FStates+[tvsTopsNeedsUpdate, tvsTopItemNeedsUpdate,
3500                       tvsBottomItemNeedsUpdate,tvsScrollbarChanged];
3501    EndUpdate;
3502  end;
3503end;
3504
3505function TCustomTreeView.DefaultItemHeightIsStored: Boolean;
3506begin
3507  Result := not(tvoAutoItemHeight in Options);
3508end;
3509
3510function TCustomTreeView.DefaultTreeViewSort(Node1, Node2: TTreeNode): Integer;
3511begin
3512  if Assigned(OnCompare) then begin
3513    Result:=0;
3514    OnCompare(Node1.TreeView,Node1, Node2, Result);
3515  end else
3516    Result := Utf8CompareStr(Node1.Text,Node2.Text);
3517end;
3518
3519procedure TCustomTreeView.SetAutoExpand(Value: Boolean);
3520begin
3521  if AutoExpand <> Value then begin
3522    if Value then
3523      Include(FOptions,tvoAutoExpand)
3524    else
3525      Exclude(FOptions,tvoAutoExpand);
3526  end;
3527end;
3528
3529procedure TCustomTreeView.SetHotTrack(Value: Boolean);
3530begin
3531  if HotTrack <> Value then begin
3532    if Value then
3533      Include(FOptions,tvoHotTrack)
3534    else
3535      Exclude(FOptions,tvoHotTrack);
3536  end;
3537end;
3538
3539procedure TCustomTreeView.SetRowSelect(Value: Boolean);
3540begin
3541  if RowSelect <> Value then begin
3542    if Value then
3543      Include(FOptions,tvoRowSelect)
3544    else
3545      Exclude(FOptions,tvoRowSelect);
3546    if FSelectedNode<>nil then
3547      Invalidate;
3548  end;
3549end;
3550
3551procedure TCustomTreeView.SetScrollBars(const Value: TScrollStyle);
3552begin
3553  if (FScrollBars <> Value) then begin
3554    FScrollBars := Value;
3555    Include(FStates,tvsScrollbarChanged);
3556    UpdateScrollBars;
3557  end;
3558end;
3559
3560procedure TCustomTreeView.SetScrolledLeft(AValue: integer);
3561var
3562  OldScrolledLeft: Integer;
3563begin
3564  OldScrolledLeft := FScrolledLeft;
3565  if AValue<0 then AValue:=0;
3566  if AValue=FScrolledLeft then exit;
3567  if AValue>GetMaxScrollLeft then AValue:=GetMaxScrollLeft;
3568  if AValue=FScrolledLeft then exit;
3569  EndEditing(true);
3570  FScrolledLeft:=AValue;
3571  ScrollView(OldScrolledLeft-FScrolledLeft, 0);
3572end;
3573
3574procedure TCustomTreeView.SetScrolledTop(AValue: integer);
3575var
3576  OldScrolledTop: Integer;
3577begin
3578  OldScrolledTop:=FScrolledTop;
3579  if FScrolledTop=AValue then exit;
3580  if AValue<0 then AValue:=0;
3581  if AValue>GetMaxScrollTop then AValue:=GetMaxScrollTop;
3582  if AValue=FScrolledTop then exit;
3583  EndEditing(true);
3584  FScrolledTop:=AValue;
3585  FStates:=FStates+[tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
3586  ScrollView(0, OldScrolledTop-FScrolledTop);
3587end;
3588
3589procedure TCustomTreeView.SetToolTips(Value: Boolean);
3590begin
3591  if ToolTips <> Value then begin
3592    if Value then
3593      Include(FOptions,tvoToolTips)
3594    else
3595      Exclude(FOptions,tvoToolTips);
3596  end;
3597end;
3598
3599procedure TCustomTreeView.SetSortType(Value: TSortType);
3600begin
3601  if SortType <> Value then begin
3602    FSortType := Value;
3603    if ((SortType in [stData, stBoth]) and Assigned(OnCompare)) or
3604      (SortType in [stText, stBoth]) then
3605      AlphaSort;
3606  end;
3607end;
3608
3609procedure TCustomTreeView.SetBackgroundColor(Value: TColor);
3610begin
3611  if Color<>Value then begin
3612    Color:=Value;
3613    Invalidate;
3614  end;
3615end;
3616
3617procedure TCustomTreeView.SetSelectedColor(Value: TColor);
3618begin
3619  if FSelectedColor<>Value then begin
3620    FSelectedColor:=Value;
3621    Invalidate;
3622  end;
3623end;
3624
3625procedure TCustomTreeView.SetSelectedFontColor(Value: TColor);
3626begin
3627  if FSelectedFontColor<>Value then begin
3628    FSelectedFontColor:=Value;
3629    Invalidate;
3630  end;
3631end;
3632
3633procedure TCustomTreeView.Paint;
3634begin
3635  DoPaint;
3636end;
3637
3638procedure TCustomTreeView.SetDragMode(Value: TDragMode);
3639begin
3640  inherited SetDragMode(Value);
3641end;
3642
3643procedure TCustomTreeView.SetOptions(NewOptions: TTreeViewOptions);
3644var ChangedOptions: TTreeViewOptions;
3645begin
3646  if FOptions=NewOptions then exit;
3647  ChangedOptions:=(FOptions-NewOptions)+(NewOptions-FOptions);
3648  FOptions:=NewOptions;
3649  if tvoKeepCollapsedNodes in ChangedOptions then
3650    Items.KeepCollapsedNodes:=(tvoKeepCollapsedNodes in FOptions);
3651  if (tvoReadOnly in ChangedOptions) and (not (tvoReadOnly in FOptions)) then
3652    EndEditing;
3653  if (tvoAllowMultiselect in ChangedOptions) then begin
3654    if (tvoAllowMultiselect in FOptions) then begin
3655      if Selected<>nil then
3656        Selected.MultiSelected:=true;
3657    end else begin
3658      Items.ClearMultiSelection;
3659    end;
3660  end;
3661  if tvoAutoItemHeight in ChangedOptions then
3662    UpdateDefaultItemHeight;
3663  if ([tvoHideSelection,tvoReadOnly,tvoShowButtons,tvoShowRoot,tvoShowLines]
3664    * ChangedOptions)<>[]
3665  then
3666    Invalidate;
3667end;
3668
3669procedure TCustomTreeView.UpdateDefaultItemHeight;
3670var
3671  NewDefItemHeight: Integer;
3672  ImageSize, StateImageSize: TSize;
3673begin
3674  if (tvoAutoItemHeight in FOptions)
3675  and HandleAllocated and Canvas.HandleAllocated then begin
3676    NewDefItemHeight:=Canvas.TextHeight(TVAutoHeightString)+FDefItemSpace;
3677    ImageSize := GetImageSize;
3678    if Assigned(FStateImages) then
3679      StateImageSize := StateImages.SizeForPPI[StateImagesWidth, Font.PixelsPerInch];
3680    if NewDefItemHeight<FDefItemSpace then NewDefItemHeight:=FDefItemSpace;
3681    if (ImageSize.cy > 0) and (ImageSize.cy + FDefItemSpace > NewDefItemHeight) then
3682      NewDefItemHeight:=ImageSize.cy+FDefItemSpace;
3683    if (StateImages<>nil) and (StateImageSize.cy+FDefItemSpace>NewDefItemHeight) then
3684      NewDefItemHeight:=StateImageSize.cy+FDefItemSpace;
3685    if Odd(NewDefItemHeight) then Inc(NewDefItemHeight);
3686    if NewDefItemHeight<>FDefItemHeight then begin
3687      FDefItemHeight:=NewDefItemHeight;
3688      FStates:=FStates+[tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
3689      Invalidate;
3690    end;
3691  end;
3692end;
3693
3694procedure TCustomTreeView.UpdateAllTops;
3695var
3696  CurTop: integer;
3697
3698  procedure CalculateTops(Node: TTreeNode);
3699  begin
3700    while Node<>nil do begin
3701      if Node.Visible then begin
3702        Node.fTop:=CurTop;
3703        inc(CurTop, Node.Height);
3704        if Node.Expanded then
3705          CalculateTops(Node.GetFirstChild);
3706      end;
3707      Node:=Node.GetNextSibling;
3708    end;
3709  end;
3710
3711begin
3712  if not (tvsTopsNeedsUpdate in FStates) then exit;
3713  CurTop:=0;
3714  CalculateTops(Items.GetFirstVisibleNode);
3715  Exclude(FStates,tvsTopsNeedsUpdate);
3716  Include(FStates,tvsScrollbarChanged);
3717end;
3718
3719procedure TCustomTreeView.UpdateMaxLvl;
3720
3721  procedure LookInChildsAndBrothers(Node: TTreeNode; CurLvl: integer);
3722  begin
3723    if Node=nil then exit;
3724    if CurLvl>FMaxLvl then FMaxLvl:=CurLvl;
3725    LookInChildsAndBrothers(Node.GetFirstChild,CurLvl+1);
3726    LookInChildsAndBrothers(Node.GetNextSibling,CurLvl);
3727  end;
3728
3729begin
3730  if not (tvsMaxLvlNeedsUpdate in FStates) then exit;
3731  FMaxLvl:=0;
3732  LookInChildsAndBrothers(Items.GetFirstNode,1);
3733  Exclude(FStates,tvsMaxRightNeedsUpdate);
3734end;
3735
3736procedure TCustomTreeView.UpdateMaxRight;
3737const
3738  LargeItemCount = 100;
3739  ReservedWidth = 100;
3740var
3741  Node: TTreeNode;
3742  i: integer;
3743  FMaxTextLen, AIndent: Integer;
3744  Cnt: Integer;
3745begin
3746  if not (tvsMaxRightNeedsUpdate in FStates) then exit;
3747  FMaxRight := 0;
3748  FMaxTextLen := 0;
3749  Node := Items.GetFirstNode;
3750  Cnt := 0;
3751  AIndent := Indent;
3752  while Node <> nil do
3753  begin
3754    if not Node.AreParentsExpandedAndVisible then
3755    begin
3756      Node := Node.GetNext;
3757      Continue;
3758    end;
3759    inc(Cnt);
3760    if (Cnt < LargeItemCount) then
3761    begin
3762      i := Node.DisplayTextRight + ScrolledLeft + AIndent div 2;
3763    end else
3764    begin
3765      // computing DisplayTextRight is too expensive when the tree
3766      // has hundreds of nodes
3767      // => use a heuristic
3768      if length(Node.Text) > FMaxTextLen then
3769        i := Node.DisplayTextRight + ScrolledLeft + ReservedWidth
3770      else
3771        i := FMaxRight;
3772    end;
3773    if FMaxRight < i then
3774    begin
3775      FMaxRight := i;
3776      FMaxTextLen := length(Node.Text);
3777    end;
3778    Node := Node.GetNext;
3779  end;
3780  Exclude(FStates, tvsMaxRightNeedsUpdate);
3781  Include(FStates, tvsScrollbarChanged);
3782end;
3783
3784procedure TCustomTreeView.UpdateTopItem;
3785begin
3786  //DebugLn('TCustomTreeView.UpdateTopItem tvsTopItemNeedsUpdate in FStates=',tvsTopItemNeedsUpdate in FStates);
3787  if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate]=[]) then exit;
3788  FTopItem:=GetNodeAtY(BorderWidth);
3789  Exclude(FStates,tvsTopItemNeedsUpdate);
3790end;
3791
3792procedure TCustomTreeView.UpdateBottomItem;
3793begin
3794  if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate,tvsBottomItemNeedsUpdate]=[])
3795  then exit;
3796//  if not (tvsBottomItemNeedsUpdate in FStates) then exit;  already above
3797  FBottomItem:=TopItem;
3798  while (FBottomItem<>nil) and (FBottomItem.GetNextVisible<>nil) do
3799    FBottomItem:=FBottomItem.GetNextVisible;
3800  Exclude(FStates,tvsBottomItemNeedsUpdate);
3801end;
3802
3803procedure TCustomTreeView.SetBottomItem(Value: TTreeNode);
3804begin
3805  if HandleAllocated and (Value <> nil) then begin
3806    Value.MakeVisible;
3807    ScrolledTop:=Value.Top+Value.Height-ClientHeight;
3808  end;
3809end;
3810
3811procedure TCustomTreeView.SetSeparatorColor(const AValue: TColor);
3812begin
3813  if fSeparatorColor=AValue then exit;
3814  fSeparatorColor:=AValue;
3815  if tvoShowSeparators in Options then
3816    Invalidate;
3817end;
3818
3819procedure TCustomTreeView.SetShowButton(Value: Boolean);
3820begin
3821  if ShowButtons <> Value then begin
3822    if Value then
3823      Include(FOptions,tvoShowButtons)
3824    else
3825      Exclude(FOptions,tvoShowButtons);
3826    Invalidate;
3827  end;
3828end;
3829
3830procedure TCustomTreeView.SetShowLines(Value: Boolean);
3831begin
3832  if ShowLines <> Value then begin
3833    if Value then
3834      Include(FOptions,tvoShowLines)
3835    else
3836      Exclude(FOptions,tvoShowLines);
3837    Invalidate;
3838  end;
3839end;
3840
3841procedure TCustomTreeView.SetShowRoot(Value: Boolean);
3842begin
3843  if ShowRoot <> Value then begin
3844    if Value then
3845      Include(FOptions,tvoShowRoot)
3846    else
3847      Exclude(FOptions,tvoShowRoot);
3848    Invalidate;
3849  end;
3850end;
3851
3852procedure TCustomTreeView.SetShowScrollBar(Which: Integer; AShow: Boolean);
3853begin
3854  if ((Which in [SB_Horz, SB_BOTH]) and (FSBHorzShowing<>Ord(AShow)))
3855  or ((Which in [SB_Vert, SB_BOTH]) and (FSBVertShowing<>Ord(AShow)))
3856  then
3857    ShowScrollBar(Handle, Which, AShow);
3858
3859  if Which in [SB_Horz, SB_BOTH] then
3860    FSBHorzShowing:=Ord(AShow);
3861  if Which in [SB_Vert, SB_BOTH] then
3862    FSBVertShowing:=Ord(AShow);
3863end;
3864
3865procedure TCustomTreeView.SetShowSeparators(Value: Boolean);
3866begin
3867  if ShowSeparators <> Value then begin
3868    if Value then
3869      Include(FOptions,tvoShowSeparators)
3870    else
3871      Exclude(FOptions,tvoShowSeparators);
3872    Invalidate;
3873  end;
3874end;
3875
3876procedure TCustomTreeView.SetKeepCollapsedNodes(Value: Boolean);
3877begin
3878  if KeepCollapsedNodes=Value then exit;
3879  if Value then
3880    Include(FOptions,tvoKeepCollapsedNodes)
3881  else
3882    Exclude(FOptions,tvoKeepCollapsedNodes);
3883  Items.KeepCollapsedNodes:=Value;
3884end;
3885
3886procedure TCustomTreeView.SetMultiSelect(const AValue: Boolean);
3887begin
3888  if MultiSelect <> AValue then
3889  begin
3890    ClearSelection;
3891    if AValue then
3892      Include(FOptions,tvoAllowMultiselect)
3893    else
3894      Exclude(FOptions,tvoAllowMultiselect);
3895  end;
3896end;
3897
3898procedure TCustomTreeView.SetMultiSelectStyle(const AValue: TMultiSelectStyle);
3899begin
3900  if FMultiSelectStyle=AValue then exit;
3901  FMultiSelectStyle:=AValue;
3902  // there must be at least one multiselectstyle according to docs
3903  // http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/ComCtrls_TCustomTreeView_MultiSelectStyle.html
3904  if FMultiSelectStyle = [] then
3905    FMultiSelectStyle := DefaultMultiSelectStyle;
3906end;
3907
3908procedure TCustomTreeView.SetReadOnly(Value: Boolean);
3909begin
3910  if ReadOnly = Value then exit;
3911  if Value then
3912    Include(FOptions,tvoReadOnly)
3913  else
3914    Exclude(FOptions,tvoReadOnly);
3915  if not Value then EndEditing;
3916end;
3917
3918procedure TCustomTreeView.SetRightClickSelect(Value: Boolean);
3919begin
3920  if Value then
3921    Include(FOptions,tvoRightClickSelect)
3922  else
3923    Exclude(FOptions,tvoRightClickSelect);
3924end;
3925
3926procedure TCustomTreeView.SetHideSelection(Value: Boolean);
3927begin
3928  if HideSelection <> Value then begin
3929    if Value then
3930      Include(FOptions,tvoHideSelection)
3931    else
3932      Exclude(FOptions,tvoHideSelection);
3933    if FSelectedNode<>nil then Invalidate;
3934  end;
3935end;
3936
3937function TCustomTreeView.GetMaxLvl: integer;
3938begin
3939  UpdateMaxRight;
3940  Result:=FMaxRight;
3941end;
3942
3943function TCustomTreeView.GetMaxScrollLeft: integer;
3944begin
3945  UpdateMaxRight;
3946  Result:=FMaxRight-(ClientWidth-2*BorderWidth);
3947  if Result<0 then Result:=0;
3948end;
3949
3950function TCustomTreeView.GetMaxScrollTop: integer;
3951var
3952  LastVisibleNode: TTreeNode;
3953begin
3954  LastVisibleNode:=Items.GetLastExpandedSubNode;
3955  if LastVisibleNode=nil then
3956    Result:=0
3957  else begin
3958    Result:=LastVisibleNode.Top+LastVisibleNode.Height
3959             -ClientHeight+2*integer(BorderWidth);
3960    //DebugLn('>>> ',LastVisibleNode.Text,' ',Result);
3961    if Result<0 then Result:=0;
3962  end;
3963end;
3964
3965function TCustomTreeView.GetNodeAtY(Y: Integer): TTreeNode;
3966// search in all expanded nodes for the node at the screen coordinate Y
3967var
3968  i: integer;
3969begin
3970  Result := nil;
3971  if not Assigned(Items) then
3972    Exit;
3973  if (Y >= BorderWidth) and (Y < ClientHeight - BorderWidth) then
3974  begin
3975    inc(Y, FScrolledTop - BorderWidth);
3976    i := IndexOfNodeAtTop(Items.FTopLvlItems, Items.FTopLvlCount, Y);
3977    if i >= 0 then
3978    begin
3979      Result := Items.FTopLvlItems[i];
3980      while Result.Visible and Result.Expanded do
3981      begin
3982        i := IndexOfNodeAtTop(Result.FItems, Result.FCount, Y);
3983        if i >= 0 then
3984          Result := Result.Items[i]
3985        else
3986          break;
3987      end;
3988    end;
3989  end;
3990end;
3991
3992function TCustomTreeView.GetNodeDrawAreaWidth: integer;
3993begin
3994  Result:=ClientWidth-BorderWidth*2;
3995end;
3996
3997function TCustomTreeView.GetNodeDrawAreaHeight: integer;
3998begin
3999  Result:=ClientHeight-BorderWidth*2;
4000end;
4001
4002function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode;
4003var
4004  b: Boolean;
4005begin
4006  Result := GetNodeAtY(Y);
4007  if Result = nil then Exit;
4008  if tvoRowSelect in Options then       // row select
4009    b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
4010  else
4011    b := (X < Result.DisplayStateIconLeft) or (X >= Result.DisplayTextRight);
4012  if b then
4013    Result := nil;
4014end;
4015
4016function TCustomTreeView.GetNodeWithExpandSignAt(X, Y: Integer): TTreeNode;
4017var
4018  b: Boolean;
4019begin
4020  Result := GetNodeAtY(Y);
4021  if Result = nil then Exit;
4022  if tvoRowSelect in Options then       // row select
4023    b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
4024  else                                  // need to include DisplayExpandSignLeft
4025    b := (X < Result.DisplayExpandSignLeft) or (X >= Result.DisplayTextRight);
4026  if b then
4027    Result := nil;
4028end;
4029
4030procedure TCustomTreeView.GetInsertMarkAt(X, Y: Integer;
4031  out AnInsertMarkNode: TTreeNode; out AnInsertMarkType: TTreeViewInsertMarkType);
4032var
4033  ANode: TTreeNode;
4034  NodeRect: TRect;
4035  NodeMidY: integer;
4036begin
4037  AnInsertMarkNode:=nil;
4038  AnInsertMarkType:=tvimNone;
4039  if Y<0 then Y:=0;
4040  if Y>=ClientHeight then Y:=ClientHeight-1;
4041  ANode:=GetNodeAtY(Y);
4042  if ANode<>nil then
4043  begin
4044    NodeRect:=ANode.DisplayRect(false);
4045    NodeMidY:=NodeRect.Top + (NodeRect.Bottom-NodeRect.Top) div 2;
4046    AnInsertMarkNode:=ANode;
4047    if (X>AnInsertMarkNode.DisplayExpandSignRight) then
4048      if Y>=NodeMidY then begin
4049        // insert as first child of pointed node
4050        AnInsertMarkType:=tvimAsFirstChild;
4051      end else begin
4052        // insert as previous sibling of pointed node
4053        AnInsertMarkType:=tvimAsPrevSibling;
4054    end else begin
4055      if Y>=NodeMidY then begin
4056        if ANode.Expanded and ANode.HasChildren then begin
4057          // insert as first child of pointed node
4058          AnInsertMarkType:=tvimAsFirstChild;
4059        end else begin
4060          // insert as next sibling of pointed node
4061          AnInsertMarkType:=tvimAsNextSibling;
4062        end;
4063      end else begin
4064        // insert as previous sibling of pointed node
4065        AnInsertMarkType:=tvimAsPrevSibling;
4066      end;
4067    end;
4068  end else begin
4069    // insert behind all nodes
4070    ANode:=Items.GetLastExpandedSubNode;
4071    if ANode<>nil then begin
4072     AnInsertMarkNode:=ANode;
4073     if X>AnInsertMarkNode.DisplayExpandSignRight then
4074       // insert as first child of last visible node
4075       AnInsertMarkType:=tvimAsFirstChild
4076     else
4077       // insert as next sibling of last visible node
4078       AnInsertMarkType:=tvimAsNextSibling;
4079    end else begin
4080      // insert as new root
4081      AnInsertMarkNode:=nil;
4082      AnInsertMarkType:=tvimAsFirstChild;
4083    end;
4084  end;
4085
4086  // normalize (try to replace tvimAsPrevSibling)
4087  if (AnInsertMarkType=tvimAsPrevSibling) and (AnInsertMarkNode<>nil) then begin
4088    if (AnInsertMarkNode.GetPrevSibling<>nil) then begin
4089      if (AnInsertMarkNode.GetPrevSibling.Expanded=false)
4090      and (AnInsertMarkNode.GetPrevSibling.IsVisible) then begin
4091        AnInsertMarkNode:=AnInsertMarkNode.GetPrevSibling;
4092        AnInsertMarkType:=tvimAsNextSibling;
4093      end;
4094    end else if (AnInsertMarkNode.Parent<>nil)
4095    and (AnInsertMarkNode.Parent.IsVisible) then begin
4096      AnInsertMarkNode:=AnInsertMarkNode.Parent;
4097      AnInsertMarkType:=tvimAsFirstChild;
4098    end;
4099  end;
4100end;
4101
4102procedure TCustomTreeView.SetInsertMark(AnInsertMarkNode: TTreeNode;
4103  AnInsertMarkType: TTreeViewInsertMarkType);
4104begin
4105  InsertMarkNode:=AnInsertMarkNode;
4106  InsertMarkType:=AnInsertMarkType;
4107end;
4108
4109procedure TCustomTreeView.SetInsertMarkAt(X, Y: integer);
4110var
4111  AnInsertMarkNode: TTreeNode;
4112  AnInsertMarkType: TTreeViewInsertMarkType;
4113begin
4114  GetInsertMarkAt(X,Y,AnInsertMarkNode,AnInsertMarkType);
4115  SetInsertMark(AnInsertMarkNode,AnInsertMarkType);
4116end;
4117
4118function TCustomTreeView.GetHitTestInfoAt(X, Y: Integer): THitTests;
4119// ToDo: Set also flags [htAbove, htBelow, htOnRight, htToLeft, htToRight];
4120var
4121  Node: TTreeNode;
4122begin
4123  Result := [];
4124  if (X>=0) and (X<ClientWidth) and (Y>=0) and (Y<ClientHeight)
4125  then begin
4126    Node:=GetNodeAtY(Y);
4127    if Node<>nil then begin
4128      Include(Result,htOnItem);
4129      if X<Node.DisplayExpandSignLeft then
4130        Include(Result,htOnIndent)
4131      else if X<Node.DisplayStateIconLeft then
4132        Include(Result,htOnButton)
4133      else if X<Node.DisplayIconLeft then
4134        Include(Result,htOnStateIcon)
4135      else if X<Node.DisplayTextLeft then
4136        Include(Result,htOnIcon)
4137      else if X<Node.DisplayTextRight then
4138        Include(Result,htOnLabel);
4139    end else
4140      Include(Result,htNowhere);
4141  end;
4142end;
4143
4144procedure TCustomTreeView.SetTreeLineColor(Value: TColor);
4145begin
4146  if FTreeLineColor<>Value then begin
4147    FTreeLineColor:=Value;
4148    Invalidate;
4149  end;
4150end;
4151
4152procedure TCustomTreeView.SetTreeNodes(Value: TTreeNodes);
4153begin
4154  Items.Assign(Value);
4155end;
4156
4157procedure TCustomTreeView.SetIndent(Value: Integer);
4158begin
4159  if Value <> Indent then begin
4160    FIndent := Value;
4161    Invalidate;
4162  end;
4163end;
4164
4165procedure TCustomTreeView.FullExpand;
4166var
4167  Node: TTreeNode;
4168begin
4169  Node := Items.GetFirstNode;
4170  while Node <> nil do begin
4171    Node.Expand(True);
4172    Node := Node.GetNextSibling;
4173  end;
4174end;
4175
4176procedure TCustomTreeView.FullCollapse;
4177var
4178  Node: TTreeNode;
4179begin
4180  Node := Items.GetFirstNode;
4181  while Node <> nil do begin
4182    Node.Collapse(True);
4183    Node := Node.GetNextSibling;
4184  end;
4185end;
4186
4187function TCustomTreeView.IsNodeVisible(ANode: TTreeNode): Boolean;
4188begin
4189  Result:=(ANode<>nil) and (ANode.Visible) and (ANode.AreParentsExpandedAndVisible);
4190  if Result then begin
4191    //DebugLn('[TCustomTreeView.IsNodeVisible] B Node=',DbgS(ANode),
4192    //  ' ',dbgs(FScrolledTop)+'>=',dbgs(ANode.Top+ANode.Height)+' or =',dbgs(FScrolledTop),'+'+dbgs(ClientHeight)+'<',dbgs(ANode.Top));
4193    if (FScrolledTop>=ANode.Top+ANode.Height)
4194    or (FScrolledTop+ClientHeight-2*BorderWidth<ANode.Top)
4195    then
4196      Result:=false;
4197  end;
4198  //DebugLn('[TCustomTreeView.IsNodeVisible] END Node=',DbgS(ANode),
4199  //' Node.Text=',ANode.Text,' Visible=',Result);
4200end;
4201
4202function TCustomTreeView.IsNodeHeightFullVisible(ANode: TTreeNode): Boolean;
4203begin
4204  Result:=(ANode<>nil) and (ANode.AreParentsExpandedAndVisible);
4205  if Result then begin
4206    //DebugLn('[TCustomTreeView.IsNodeVisible] B Node=',DbgS(ANode),
4207    //' ',FScrolledTop,'>=',ANode.Top,'+',ANode.Height,' or ',FScrolledTop,'+',ClientHeight,'<',ANode.Top);
4208    if (FScrolledTop>ANode.Top)
4209    or (FScrolledTop+ClientHeight-2*BorderWidth
4210      <ANode.Top+ANode.Height)
4211    then
4212      Result:=false;
4213  end;
4214  //DebugLn('[TCustomTreeView.IsNodeVisible] END Node=',DbgS(ANode),
4215  //' Node.Text=',ANode.Text,' Visible=',Result);
4216end;
4217
4218procedure TCustomTreeView.KeyDown(var Key: Word; Shift: TShiftState);
4219const
4220  EditKey = VK_F2;
4221  EditKeyShift = [];
4222var
4223  lNode: TTreeNode;
4224begin
4225  inherited KeyDown(Key, Shift);
4226
4227  case Key of
4228
4229  VK_DOWN:
4230  begin
4231    MoveToNextNode(AllowMultiSelectWithShift(Shift));
4232    Key:=VK_UNKNOWN;
4233  end;
4234
4235  VK_UP:
4236  begin
4237    MoveToPrevNode(AllowMultiSelectWithShift(Shift));
4238    Key:=VK_UNKNOWN;
4239  end;
4240
4241  VK_HOME:
4242  begin
4243    MoveHome(AllowMultiSelectWithShift(Shift));
4244    Key:=VK_UNKNOWN;
4245  end;
4246
4247  VK_END:
4248  begin
4249    MoveEnd(AllowMultiSelectWithShift(Shift));
4250    Key:=VK_UNKNOWN;
4251  end;
4252
4253  VK_PRIOR: // Page Up
4254  begin
4255    MovePageUp(AllowMultiSelectWithShift(Shift));
4256    Key:=VK_UNKNOWN;
4257  end;
4258
4259  VK_NEXT: // Page Down
4260  begin
4261    MovePageDown(AllowMultiSelectWithShift(Shift));
4262    Key:=VK_UNKNOWN;
4263  end;
4264
4265  else
4266    if not (Key in [VK_LEFT,VK_RIGHT,VK_ADD,VK_SUBTRACT]) then
4267      Exit;
4268
4269    if (tvoAllowMultiSelect in FOptions) and AllowMultiSelectWithShift(Shift) then
4270      lNode := FTreeNodes.FLastMultiSelected
4271    else
4272      lNode := Selected;
4273
4274    case Key of
4275    VK_LEFT:
4276    if lNode <> nil then
4277    begin
4278      if lNode.Expanded then
4279        lNode.Expanded := False
4280      else
4281      if lNode.Parent <> nil then
4282        lNode := lNode.Parent;
4283      Key:=VK_UNKNOWN;
4284    end;
4285
4286    VK_RIGHT:
4287    if lNode <> nil then
4288    begin
4289      if lNode.Expanded then
4290        lNode := lNode.GetNextExpanded
4291      else
4292        lNode.Expanded := True;
4293      Key:=VK_UNKNOWN;
4294    end;
4295
4296    VK_ADD:
4297    if lNode <> nil then
4298      lNode.Expanded := True;
4299
4300    VK_SUBTRACT:
4301    if lNode <> nil then
4302      lNode.Expanded := False;
4303
4304    else
4305      if (Key=EditKey) and (Shift=EditKeyShift) and (not ReadOnly) then
4306        BeginEditing(Selected);
4307      lNode := nil; { No change in selection }
4308    end;
4309
4310    if lNode <> nil then
4311      MoveSelection(lNode, AllowMultiSelectWithShift(Shift));
4312  end;
4313end;
4314
4315procedure TCustomTreeView.Loaded;
4316begin
4317  inherited Loaded;
4318  if csDesigning in ComponentState then FullExpand;
4319  UpdateDefaultItemHeight;
4320end;
4321
4322function TCustomTreeView.GetTopItem: TTreeNode;
4323begin
4324  if HandleAllocated then
4325  begin
4326    UpdateTopItem;
4327    Result := FTopItem;
4328  end
4329  else
4330    Result := FTopItem;
4331end;
4332
4333procedure TCustomTreeView.HintMouseLeave(Sender: TObject);
4334begin
4335  if FindLCLControl(Mouse.CursorPos)<>Self then
4336    FHintWnd.Hide;
4337end;
4338
4339function TCustomTreeView.IsStoredBackgroundColor: Boolean;
4340begin
4341  result := Color <> clWindow;
4342end;
4343
4344procedure TCustomTreeView.SetTopItem(Value: TTreeNode);
4345begin
4346  if HandleAllocated and (Value <> nil) then
4347  begin
4348    Value.MakeVisible;
4349    ScrolledTop:=Value.Top;
4350  end
4351  else
4352    FTopItem := Value;
4353end;
4354
4355procedure TCustomTreeView.OnChangeTimer(Sender: TObject);
4356begin
4357  FChangeTimer.Enabled := False;
4358  //debugln('TCustomTreeView.OnChangeTimer');
4359  FCallingChange := True;
4360  try
4361    Change(FSelectedNode);
4362  finally
4363    FCallingChange := False;
4364  end;
4365end;
4366
4367procedure TCustomTreeView.UpdateScrollbars;
4368var
4369  ScrollInfo: TScrollInfo;
4370  MaxScrollLeft, MaxScrollTop: Integer;
4371begin
4372  if not (tvsScrollbarChanged in FStates) then exit;
4373
4374  if not HandleAllocated or (Items.FUpdateCount>0) then
4375    exit;
4376
4377  MaxScrollLeft := GetMaxScrollLeft;
4378  MaxScrollTop := GetMaxScrollTop;
4379
4380  //DebugLn('* TCustomTreeView.UpdateScrollbars Enter *');
4381  if ScrolledLeft>MaxScrollLeft then ScrolledLeft:=MaxScrollLeft;
4382  if ScrolledTop>MaxScrollTop then ScrolledTop:=MaxScrollTop;
4383  Exclude(FStates,tvsScrollbarChanged);
4384
4385  if fScrollBars in [ssBoth, ssHorizontal, ssAutoBoth, ssAutoHorizontal] then
4386  begin
4387    // horizontal scrollbar
4388    FillChar(ScrollInfo,SizeOf(ScrollInfo),0);
4389    ScrollInfo.cbSize := SizeOf(ScrollInfo);
4390    ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL;
4391    ScrollInfo.nTrackPos := 0;
4392    ScrollInfo.nMin := 0;
4393    ScrollInfo.nPage := Max(1,ClientWidth-2*BorderWidth);
4394    ScrollInfo.nMax := Max(1,MaxScrollLeft+integer(ScrollInfo.nPage));
4395    ScrollInfo.nPos := Max(FScrolledLeft,0);
4396    if not CompareMem(@ScrollInfo,@FLastHorzScrollInfo,SizeOf(TScrollInfo))
4397    then begin
4398      if (fScrollBars in [ssAutoBoth, ssAutoHorizontal])
4399      and (ScrollInfo.nPage>=cardinal(ScrollInfo.nMax)) then begin
4400        //DebugLn(['TCustomTreeView.UpdateScrollbars Hide Horizontal.']);
4401        FLastHorzScrollInfo.cbSize:=0;
4402        SetShowScrollBar(SB_HORZ, false);
4403      end else begin
4404        //DebugLn(['TCustomTreeView.UpdateScrollbars Show Horizontal: nMin=',ScrollInfo.nMin,
4405        //' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
4406        //' nPos=',ScrollInfo.nPos,' GetMaxScrollLeft=',MaxScrollLeft,
4407        //' ClientW=',ClientWidth, ' MaxRight=',FMaxRight]);
4408        FLastHorzScrollInfo:=ScrollInfo;
4409        SetShowScrollBar(SB_HORZ, true);
4410        SetScrollInfo(Handle, SB_HORZ, ScrollInfo, true);
4411      end;
4412    end;
4413  end else begin
4414    FLastHorzScrollInfo.cbSize:=0;
4415    SetShowScrollBar(SB_HORZ,false);
4416  end;
4417
4418  if fScrollBars in [ssBoth, ssVertical, ssAutoBoth, ssAutoVertical] then begin
4419    // vertical scrollbar
4420    ScrollInfo.cbSize := SizeOf(ScrollInfo);
4421    ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL;
4422    ScrollInfo.nTrackPos := 0;
4423    ScrollInfo.nMin := 0;
4424    ScrollInfo.nPage := Max(1,ClientHeight-FDefItemHeight);
4425    ScrollInfo.nMax := Max(1,MaxScrollTop+integer(ScrollInfo.nPage));
4426    ScrollInfo.nTrackPos := 0;
4427    ScrollInfo.nPos := Max(0,FScrolledTop);
4428    if not CompareMem(@ScrollInfo,@FLastVertScrollInfo,SizeOf(TScrollInfo))
4429    then begin
4430      if (fScrollBars in [ssAutoBoth, ssAutoVertical])
4431      and (ScrollInfo.nPage>=cardinal(ScrollInfo.nMax)) then begin
4432        //DebugLn(['TCustomTreeView.UpdateScrollbars Hide Vertical.']);
4433        ScrollInfo.nPos:=0;
4434        SetScrollInfo(Handle, SB_VERT, ScrollInfo, false);
4435        FLastVertScrollInfo.cbSize:=0;
4436        SetShowScrollBar(SB_VERT, false);
4437      end else begin
4438        //DebugLn(['TCustomTreeView.UpdateScrollbars Show Vertical: nMin=',ScrollInfo.nMin,
4439        //' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
4440        //' nPos=',ScrollInfo.nPos,' GetMaxScrollTop=',MaxScrollTop,
4441        //' ClientH=',ClientHeight]);
4442        FLastVertScrollInfo:=ScrollInfo;
4443        SetShowScrollBar(SB_VERT, true);
4444        SetScrollInfo(Handle, SB_VERT, ScrollInfo, true);
4445      end;
4446    end;
4447  end else begin
4448    FLastVertScrollInfo.cbSize:=0;
4449    SetShowScrollBar(SB_VERT,false);
4450  end;
4451end;
4452
4453procedure TCustomTreeView.UpdateTooltip(X, Y: integer);
4454var
4455  Node: TTreeNode;
4456  PHint, PLeft: TPoint;
4457  R, TextRect, IntRect: TRect;
4458  CurMonitor: TMonitor;
4459begin
4460  if not (tvoToolTips in FOptions) then exit;
4461
4462  if FHintWnd=nil then
4463  begin
4464    FHintWnd:=THintWindow.Create(Self);
4465    FHintWnd.OnMouseLeave:=@HintMouseLeave;
4466  end;
4467
4468  Node := GetNodeAt(X, Y);
4469  if Node=nil then
4470  begin
4471    FHintWnd.Hide;
4472    exit;
4473  end;
4474
4475  TextRect := Rect(Node.DisplayTextLeft, Node.Top, Node.DisplayTextRight, Node.Top + Node.Height);
4476  OffsetRect(TextRect, 0, -ScrolledTop);
4477  if not PtInRect(TextRect, Point(X, Y))
4478  or (IntersectRect(IntRect, TextRect, ClientRect) and EqualRect(IntRect, TextRect)) then
4479  begin
4480    FHintWnd.Hide;
4481    exit;
4482  end;
4483
4484  // Get max width for hint from monitor's work area.
4485  CurMonitor := GetParentForm(Self).Monitor;
4486  R := CurMonitor.WorkareaRect;
4487  R := FHintWnd.CalcHintRect(R.Right-R.Left, Node.Text, nil);
4488
4489  if WidgetSet.GetLCLCapability(lcTransparentWindow) = LCL_CAPABILITY_YES then
4490  begin
4491    // Font is explicitly set for transparent hints, otherwise default font is used.
4492    if not FHintWnd.Visible then
4493    begin
4494      FHintWnd.Font.Assign(Self.Font);
4495      FHintWnd.Font.Color := Screen.HintFont.Color;
4496    end;
4497    // Put transparent hint exactly on the node.
4498    PHint := ClientToScreen(Point(TextRect.Left-1, TextRect.Top-3+BorderWidth));
4499  end
4500  else begin
4501    // By default put hint to the right side of node.
4502    PHint := ClientToScreen(Point(ClientWidth, TextRect.Top-3+BorderWidth));
4503    if PHint.X + R.Right > CurMonitor.BoundsRect.Right then
4504    begin                      // No space on the right? Put it to the left side.
4505      PLeft := ClientToScreen(Point(ClientRect.Left, ClientRect.Top));
4506      if PLeft.X >= R.Right then  // enough space on left?
4507        PHint.X := PLeft.X - R.Right;
4508    end;
4509  end;
4510  OffsetRect(R, PHint.X, PHint.Y);
4511  FHintWnd.ActivateHint(R, Node.Text)
4512end;
4513
4514function TCustomTreeView.GetSelection: TTreeNode;
4515begin
4516  if RightClickSelect and Assigned(FRClickNode) then
4517    Result := FRClickNode
4518  else
4519    Result := FSelectedNode;
4520end;
4521
4522function TCustomTreeView.GetSelectionCount: Cardinal;
4523begin
4524  Result := Items.SelectionCount;
4525end;
4526
4527function TCustomTreeView.GetSelections(AIndex: Integer): TTreeNode;
4528begin
4529  if (AIndex >= 0) and (AIndex < Items.SelectionCount) then
4530    Result := Items.GetSelections(AIndex)
4531  else
4532    Result := nil;
4533end;
4534
4535procedure TCustomTreeView.SetSelection(Value: TTreeNode);
4536var
4537  OldNode: TTreeNode;
4538begin
4539  if FSelectedNode = Value then Exit;
4540  if not CanChange(Value) then
4541    exit;
4542  {$IFDEF TREEVIEW_DEBUG}
4543  DebugLn('TCustomTreeView.SetSelection: Changing selection for Node: ', Text);
4544  {$ENDIF}
4545  EndEditing(true); // end editing before FSelectedNode change
4546  OldNode := FSelectedNode;
4547  FSelectedNode := Value;
4548  if Assigned(OldNode) then
4549    OldNode.Selected := False;
4550  if Assigned(Value) then
4551  begin
4552    Value.Selected := True;
4553    Value.MakeVisible;
4554  end;
4555  InternalSelectionChanged;
4556end;
4557
4558function TCustomTreeView.GetShowButtons: boolean;
4559begin
4560  Result:=(tvoShowButtons in FOptions);
4561end;
4562
4563function TCustomTreeView.GetShowLines: boolean;
4564begin
4565  Result:=(tvoShowLines in FOptions);
4566end;
4567
4568function TCustomTreeView.GetShowRoot: boolean;
4569begin
4570  Result:=(tvoShowRoot in FOptions);
4571end;
4572
4573function TCustomTreeView.GetShowSeparators: boolean;
4574begin
4575  Result:=(tvoShowSeparators in FOptions);
4576end;
4577
4578function TCustomTreeView.GetToolTips: boolean;
4579begin
4580  Result:=(tvoToolTips in FOptions);
4581end;
4582
4583procedure TCustomTreeView.SetExpandSignType(Value: TTreeViewExpandSignType);
4584begin
4585  if Value <> FExpandSignType then
4586  begin
4587    FExpandSignType := Value;
4588    Invalidate;
4589  end;
4590end;
4591
4592procedure TCustomTreeView.SetDefaultItemHeight(Value: integer);
4593begin
4594  if (tvoAutoItemHeight in FOptions) and (not (csLoading in ComponentState))
4595  then exit;
4596  if Value<=0 then Value:=DefaultTreeNodeHeight;
4597  if Value=FDefItemHeight then exit;
4598  FDefItemHeight:=Value;
4599  Include(FStates,tvsTopsNeedsUpdate);
4600  Invalidate;
4601end;
4602
4603function TCustomTreeView.GetAutoExpand: boolean;
4604begin
4605  Result:=(tvoAutoExpand in FOptions);
4606end;
4607
4608function TCustomTreeView.GetBackgroundColor: TColor;
4609begin
4610  Result := Color;
4611end;
4612
4613function TCustomTreeView.GetBottomItem: TTreeNode;
4614begin
4615  if HandleAllocated then begin
4616    UpdateBottomItem;
4617    Result := FBottomItem;
4618  end else
4619    Result := nil;
4620end;
4621
4622function TCustomTreeView.GetDropTarget: TTreeNode;
4623begin
4624  if HandleAllocated then
4625  begin
4626    Result := FLastDropTarget;
4627  end
4628  else
4629    Result := nil;
4630end;
4631
4632function TCustomTreeView.GetHideSelection: boolean;
4633begin
4634  Result:=(tvoHideSelection in FOptions);
4635end;
4636
4637function TCustomTreeView.GetHotTrack: boolean;
4638begin
4639  Result:=(tvoHotTrack in FOptions);
4640end;
4641
4642function TCustomTreeView.GetKeepCollapsedNodes: boolean;
4643begin
4644  Result:=(tvoKeepCollapsedNodes in FOptions);
4645end;
4646
4647function TCustomTreeView.GetMultiSelect: Boolean;
4648begin
4649  Result := (tvoAllowMultiSelect in FOptions);
4650end;
4651
4652function TCustomTreeView.GetReadOnly: boolean;
4653begin
4654  Result:=(tvoReadOnly in FOptions);
4655end;
4656
4657function TCustomTreeView.GetExpandSignSize: integer;
4658begin
4659  if FExpandSignSize>=0 then
4660    Result := FExpandSignSize
4661  else
4662  if ExpandSignType = tvestTheme then
4663    Result := ScaleScreenToFont(FThemeExpandSignSize)
4664  else
4665    Result := Scale96ToFont(DefaultTreeNodeExpandSignSize);
4666end;
4667
4668function TCustomTreeView.GetIndent: Integer;
4669begin
4670  if FIndent<0 then
4671    Result := Scale96ToFont(15)
4672  else
4673    Result := FIndent;
4674end;
4675
4676function TCustomTreeView.GetRightClickSelect: boolean;
4677begin
4678  Result:=(tvoRightClickSelect in FOptions);
4679end;
4680
4681function TCustomTreeView.GetRowSelect: boolean;
4682begin
4683  Result:=(tvoRowSelect in FOptions);
4684end;
4685
4686procedure TCustomTreeView.SetDropTarget(Value: TTreeNode);
4687begin
4688  if HandleAllocated then
4689    if Value <> nil then
4690      Value.DropTarget := True;
4691end;
4692
4693procedure TCustomTreeView.SetExpandSignSize(const AExpandSignSize: integer);
4694begin
4695  if FExpandSignSize = AExpandSignSize then Exit;
4696  FExpandSignSize := AExpandSignSize;
4697  Invalidate;
4698end;
4699
4700function TCustomTreeView.IsEditing: Boolean;
4701begin
4702  Result:=tvsIsEditing in FStates;
4703end;
4704
4705function TCustomTreeView.GetDragImages: TDragImageList;
4706begin
4707  if Assigned(FDragImage) and (FDragImage.Count > 0) then
4708    Result := FDragImage
4709  else
4710    Result := nil;
4711end;
4712
4713function TCustomTreeView.GetBuiltinIconSize: TSize;
4714begin
4715  Result := Types.Size(0, 0);
4716end;
4717
4718function TCustomTreeView.GetImageSize: TSize;
4719begin
4720  if FImages <> nil then
4721  begin
4722    Result := Images.SizeForPPI[ImagesWidth, Font.PixelsPerInch]
4723  end else
4724    Result := GetBuiltinIconSize;
4725end;
4726
4727procedure TCustomTreeView.UpdateInsertMark(X,Y: integer);
4728begin
4729  if (tvoAutoInsertMark in Options) and (not (csDesigning in ComponentState))
4730  then
4731    SetInsertMarkAt(X,Y)
4732  else
4733    SetInsertMark(nil,tvimNone);
4734end;
4735
4736procedure TCustomTreeView.DoSelectionChanged;
4737var
4738  lAccessibleObject: TLazAccessibleObject;
4739  lSelection: TTreeNode;
4740  lSelectedText: string;
4741begin
4742  // Update the accessibility information
4743  lAccessibleObject := GetAccessibleObject();
4744  lSelection := Self.Selected;
4745  if lSelection = nil then lSelectedText := ''
4746  else lSelectedText := lSelection.Text;
4747  lAccessibleObject.AccessibleValue := lSelectedText;
4748
4749  if Assigned(OnSelectionChanged) then OnSelectionChanged(Self);
4750end;
4751
4752function TCustomTreeView.IsInsertMarkVisible: boolean;
4753begin
4754  Result:=(FInsertMarkType<>tvimNone) and (FInsertMarkNode<>nil)
4755           and (FInsertMarkNode.IsVisible);
4756end;
4757
4758procedure TCustomTreeView.DoStartDrag(var DragObject: TDragObject);
4759var
4760  P: TPoint;
4761begin
4762  {$IFDEF VerboseDrag}
4763  DebugLn('TCustomTreeView.DoStartDrag A ',Name,':',ClassName);
4764  {$ENDIF}
4765  inherited DoStartDrag(DragObject);
4766  FLastDropTarget := nil;
4767  if FDragNode = nil then begin
4768    GetCursorPos(P);
4769    with ScreenToClient(P) do FDragNode := GetNodeAt(X, Y);
4770    {$IFDEF VerboseDrag}
4771    if FDragNode<>nil then
4772      DebugLn('TCustomTreeView.DoStartDrag DragNode=',FDragNode.Text)
4773    else
4774      DebugLn('TCustomTreeView.DoStartDrag DragNode=nil');
4775    {$ENDIF}
4776  end;
4777  FPrevToolTips := ToolTips;
4778  ToolTips := False;
4779  FDragScrollTimer.Enabled := true;
4780end;
4781
4782procedure TCustomTreeView.DoEndDrag(Target: TObject; X, Y: Integer);
4783begin
4784  {$IFDEF VerboseDrag}
4785  DebugLn('TCustomTreeView.DoEndDrag A ',Name,':',ClassName);
4786  {$ENDIF}
4787  inherited DoEndDrag(Target, X, Y);
4788  FLastDropTarget := nil;
4789  FDragScrollTimer.Enabled := false;
4790  ToolTips := FPrevToolTips;
4791end;
4792
4793function TCustomTreeView.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
4794  MousePos: TPoint): Boolean;
4795var
4796  NDelta: integer;
4797begin
4798  Result:=inherited DoMouseWheel(Shift, WheelDelta, MousePos);
4799  if not Result then
4800  begin
4801    NDelta := (WheelDelta * Mouse.WheelScrollLines * DefaultItemHeight) div 120;
4802    ScrolledTop := ScrolledTop - NDelta;
4803    Result := true;
4804    UpdateScrollbars;
4805  end;
4806  UpdateTooltip(MousePos.X, MousePos.Y);
4807end;
4808
4809function TCustomTreeView.DoMouseWheelHorz(Shift: TShiftState;
4810  WheelDelta: Integer; MousePos: TPoint): Boolean;
4811var
4812  NDelta: integer;
4813const
4814  cScrollStep = 50;
4815begin
4816  Result:=inherited DoMouseWheelHorz(Shift, WheelDelta, MousePos);
4817  if not Result then
4818  begin
4819    NDelta := (WheelDelta * cScrollStep) div 120;
4820    ScrolledLeft := ScrolledLeft + NDelta;
4821    Result := true;
4822  end;
4823  UpdateTooltip(MousePos.X, MousePos.Y);
4824end;
4825
4826function TCustomTreeView.DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint; ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean):LRESULT;
4827begin
4828  Result:=inherited;
4829  {$IFDEF VerboseDrag}
4830  DebugLn('TCustomTreeView.DoDragMsg ',Name,':',ClassName,' ',IntToStr(ord(ADragMessage)));
4831  {$ENDIF}
4832  case ADragMessage of
4833    {dmDragMove:
4834      begin
4835        P:=ScreenToClient(Pos);
4836        DoDragOver(Source, P.X, P.Y, AMessage.Result <> 0);
4837      end;}
4838    dmDragLeave:
4839      begin
4840        ADragObject.HideDragImage;
4841        FLastDropTarget := DropTarget;
4842        DropTarget := nil;
4843        ADragObject.ShowDragImage;
4844      end;
4845    dmDragDrop: FLastDropTarget := nil;
4846  end;
4847end;
4848
4849procedure TCustomTreeView.DragOver(Source: TObject; X,Y: Integer;
4850  State: TDragState; var Accept: Boolean);
4851var
4852  Node: TTreeNode;
4853begin
4854  inherited DragOver(Source,X,Y,State,Accept);
4855  Node := GetNodeAt(X, Y);
4856  {$IFDEF VerboseDrag}
4857  DebugLn(['TCustomTreeView.DragOver ',Name,':',ClassName,' ',Node<>nil,' ',Node <> DropTarget,' ',Node = FLastDropTarget]);
4858  DebugLn(['TCustomTreeView.DragOver Source ',Source,':',Source.ClassName]);
4859  {$ENDIF}
4860  if (Node <> nil)
4861  and ((Node <> DropTarget) or (Node = FLastDropTarget)) then
4862  begin
4863    FLastDropTarget := nil;
4864    Node.DropTarget := True;
4865  end;
4866end;
4867
4868procedure TCustomTreeView.DoPaint;
4869var
4870  a,HalfBorderWidth:integer;
4871  SpaceRect, DrawRect: TRect;
4872  Node: TTreeNode;
4873  InsertMarkRect: TRect;
4874begin
4875  if [tvsPainting] * FStates <> [] then Exit;
4876  Include(FStates, tvsPainting);
4877  try
4878    if Focused then
4879      Include(FStates,tvoFocusedPainting)
4880    else
4881      Exclude(FStates,tvoFocusedPainting);
4882    if (tvoAutoItemHeight in fOptions) then
4883      UpdateDefaultItemHeight;
4884    //UpdateScrollbars;
4885    with Canvas do
4886    begin
4887      if IsCustomDrawn(dtControl, cdPrePaint) then
4888      begin
4889        DrawRect := ClientRect;
4890        if not CustomDraw(DrawRect, cdPrePaint) then exit;
4891      end;
4892      // draw nodes
4893      Node := TopItem;
4894      //write('[TCustomTreeView.DoPaint] A Node=',DbgS(Node));
4895      //if Node<>nil then DebugLn(' Node.Text=',Node.Text) else DebugLn('');
4896      while Node <> nil do
4897      begin
4898        if Node.Visible then
4899          DoPaintNode(Node);
4900        Node := Node.GetNextVisible;
4901        //write('[TCustomTreeView.DoPaint] B Node=',DbgS(Node));
4902        //if Node<>nil then DebugLn(' Node.Text=',Node.Text) else DebugLn('');
4903      end;
4904      SpaceRect := Rect(BorderWidth, BorderWidth,
4905                        ClientWidth - BorderWidth,
4906                        ClientHeight - BorderWidth);
4907      // draw insert mark for new root node
4908      if (InsertMarkType = tvimAsFirstChild) and (Items.Count = 0) then
4909      begin
4910        Pen.Color := FTreeLineColor;
4911        Brush.Color := FSelectedColor;
4912        InsertMarkRect := SpaceRect;
4913        InsertMarkRect.Bottom := InsertMarkRect.Top + 2;
4914        Rectangle(InsertMarkRect);
4915        SpaceRect.Top := InsertMarkRect.Bottom;
4916      end;
4917      // draw unused space below nodes
4918      Node := BottomItem;
4919      if Node <> nil then
4920        SpaceRect.Top := Node.Top + Node.Height - FScrolledTop + BorderWidth;
4921      //if Node<>nil then DebugLn('BottomItem=',BottomItem.text) else DebugLn('NO BOTTOMITEM!!!!!!!!!');
4922      // TWinControl(Parent).InvalidateRect(Self,SpaceRect,true);
4923      if (Color <> clNone) and (SpaceRect.Top < SpaceRect.Bottom) then
4924      begin
4925        //DebugLn('  SpaceRect=',SpaceRect.Left,',',SpaceRect.Top,',',SpaceRect.Right,',',SpaceRect.Bottom);
4926        Brush.Color := Color;
4927        FillRect(SpaceRect);
4928      end;
4929      // draw border
4930      HalfBorderWidth := BorderWidth shr 1;
4931      Pen.Color := clGray;
4932      for a := 0 to BorderWidth - 1 do
4933      begin
4934        if a = HalfBorderWidth then
4935          Pen.Color := clBlack;
4936        MoveTo(a, ClientHeight - 1 - a);
4937        LineTo(a, a);
4938        LineTo(ClientWidth - 1 - a, a);
4939      end;
4940      Pen.Color := clWhite;
4941      for a := 0 to BorderWidth - 1 do
4942      begin
4943        if a = HalfBorderWidth then
4944          Pen.Color := clLtGray;
4945        MoveTo(ClientWidth - 1 - a, a);
4946        LineTo(ClientWidth - 1 - a, ClientHeight - 1 - a);
4947        LineTo(a, ClientHeight - 1 - a);
4948      end;
4949      if IsCustomDrawn(dtControl, cdPostPaint) then
4950      begin
4951        DrawRect := ClientRect;
4952        if not CustomDraw(DrawRect, cdPostPaint) then exit;
4953      end;
4954    end;
4955  finally
4956    Exclude(FStates, tvsPainting);
4957  end;
4958end;
4959
4960function InvertNdColor(AColor: TColor): TColor;
4961var
4962  Red, Green, Blue: integer;
4963begin
4964  if AColor<>clHighlight then begin
4965    Result:=clWhite;
4966    Red:=(AColor shr 16) and $ff;
4967    Green:=(AColor shr 8) and $ff;
4968    Blue:=AColor and $ff;
4969    if Red+Green+Blue>$180 then
4970      Result:=clBlack;
4971    //DebugLn(['[TCustomTreeView.DoPaintNode.InvertColor] Result=',Result,' ',Red,',',Green,',',Blue]);
4972  end
4973  else
4974    Result := clHighlightText;
4975end;
4976
4977procedure TCustomTreeView.DoPaintNode(Node: TTreeNode);
4978var
4979  NodeRect: TRect;
4980  VertMid, VertDelta, RealExpandSignSize, RealIndent: integer;
4981  NodeSelected, HasExpandSign: boolean;
4982
4983  procedure DrawVertLine(X, Y1, Y2: Integer);
4984  begin
4985    if Y1 > Y2 then
4986      Exit;
4987    if TreeLinePenStyle = psPattern then
4988    begin
4989      // TODO: implement psPattern support in the LCL
4990      Y1 := Y1 + VertDelta;
4991      while Y1 < Y2 do
4992      begin
4993        Canvas.Pixels[X, Y1] := TreeLineColor;
4994        inc(Y1, 2);
4995      end;
4996    end
4997    else
4998    begin
4999      Canvas.MoveTo(X, Y1);
5000      Canvas.LineTo(X, Y2);
5001    end;
5002  end;
5003
5004  procedure DrawHorzLine(Y, X1, X2: Integer);
5005  begin
5006    if X1 > X2 then
5007      Exit;
5008    if TreeLinePenStyle = psPattern then
5009    begin
5010      // TODO: implement psPattern support in the LCL
5011      while X1 < X2 do
5012      begin
5013        Canvas.Pixels[X1, Y] := TreeLineColor;
5014        inc(X1, 2);
5015      end;
5016    end
5017    else
5018    begin
5019      Canvas.MoveTo(X1, Y);
5020      Canvas.LineTo(X2, Y);
5021    end;
5022  end;
5023
5024  function DrawTreeLines(CurNode: TTreeNode): integer;
5025  // paints tree lines, returns indent
5026  var
5027    CurMid: integer;
5028  begin
5029    if (CurNode <> nil) and ((tvoShowRoot in Options) or (CurNode.Parent<>nil)) then
5030    begin
5031      Result := DrawTreeLines(CurNode.Parent);
5032      if ShowLines then
5033      begin
5034        CurMid := Result + (RealIndent shr 1);
5035        if CurNode = Node then
5036        begin
5037          // draw horizontal line
5038          if HasExpandSign then
5039            DrawHorzLine(VertMid, CurMid + RealExpandSignSize div 2, Result + RealIndent)
5040          else
5041            DrawHorzLine(VertMid, CurMid, Result + RealIndent);
5042        end;
5043
5044        if (CurNode.GetNextVisibleSibling <> nil) then
5045        begin
5046          // draw vertical line to next brother
5047          if (CurNode = Node) and HasExpandSign then
5048          begin
5049            if (Node.Parent = nil) and (Node.GetPrevSibling = nil) then
5050              DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2, NodeRect.Bottom)
5051            else
5052            begin
5053              DrawVertLine(CurMid, NodeRect.Top, VertMid);
5054              DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2 + VertDelta, NodeRect.Bottom);
5055            end;
5056          end
5057          else
5058          if (Node.Parent = nil) and (Node.GetPrevSibling = nil) then
5059            DrawVertLine(CurMid, VertMid + VertDelta, NodeRect.Bottom)
5060          else
5061            DrawVertLine(CurMid, NodeRect.Top, NodeRect.Bottom);
5062        end else
5063        if (CurNode = Node) then
5064        begin
5065          // draw vertical line from top to horizontal line
5066          if HasExpandSign then
5067          begin
5068            if ((InsertMarkNode = Node) and (InsertMarkType = tvimAsNextSibling)) then
5069            begin
5070              DrawVertLine(CurMid, NodeRect.Top, VertMid);
5071              DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2, NodeRect.Bottom - 1);
5072            end
5073            else
5074              DrawVertLine(CurMid, NodeRect.Top, VertMid);
5075          end
5076          else
5077          if ((InsertMarkNode = Node) and (InsertMarkType = tvimAsNextSibling)) then
5078            DrawVertLine(CurMid, NodeRect.Top, NodeRect.Bottom - 1)
5079          else
5080            DrawVertLine(CurMid, NodeRect.Top, VertMid);
5081        end;
5082      end;
5083      inc(Result, RealIndent);
5084    end else
5085    begin
5086      Result := BorderWidth - FScrolledLeft;
5087      if CurNode <> nil then // indent first level of tree with ShowRoot = false a bit
5088        inc(Result, RealIndent shr 2);
5089    end;
5090  end;
5091
5092  procedure DrawExpandSign(MidX, MidY: integer; CollapseSign: boolean);
5093  const
5094    PlusMinusDetail: array[Boolean {Hot}, Boolean {Expanded}] of TThemedTreeview =
5095    (
5096      (ttGlyphClosed, ttGlyphOpened),
5097      (ttHotGlyphClosed, ttHotGlyphOpened)
5098    );
5099  var
5100    HalfSize, ALeft, ATop, ARight, ABottom, SmallIndent: integer;
5101    Points: array[0..2] of TPoint; // for triangle
5102    Details: TThemedElementDetails;
5103    R: TRect;
5104    PrevColor: TColor;
5105  const
5106    cShiftHorzArrow = 2; //paint horz arrow N pixels upper than MidY
5107  begin
5108    HalfSize := RealExpandSignSize div 2;
5109    //if not Odd(RealExpandSignSize) then
5110    //  Dec(HalfSize);
5111    ALeft := MidX - HalfSize;
5112    ARight := MidX + HalfSize;
5113    ATop := MidY - HalfSize;
5114    ABottom := MidY + HalfSize;
5115
5116    if Assigned(FOnCustomDrawArrow) then
5117    begin
5118      FOnCustomDrawArrow(Self, Rect(ALeft, ATop, ARight, ABottom), not CollapseSign);
5119      Exit
5120    end;
5121
5122    with Canvas do
5123    begin
5124      Pen.Color := FExpandSignColor;
5125      Pen.Style := psSolid;
5126      case ExpandSignType of
5127      tvestTheme:
5128        begin
5129          // draw a themed expand sign. Todo: track hot
5130          R := Rect(ALeft, ATop, ARight, ABottom);
5131          Details := ThemeServices.GetElementDetails(PlusMinusDetail[False, CollapseSign]);
5132          ThemeServices.DrawElement(Canvas.Handle, Details, R, nil);
5133        end;
5134      tvestPlusMinus:
5135        begin
5136          // draw a plus or a minus sign
5137          R := Rect(ALeft, ATop, ARight+1, ABottom+1); //+1 for centering of line in square
5138          Rectangle(R);
5139          SmallIndent := Scale96ToFont(2);
5140          MoveTo(R.Left + SmallIndent, MidY);
5141          LineTo(R.Right - SmallIndent, MidY);
5142          if not CollapseSign then
5143          begin
5144            MoveTo(MidX, R.Top + SmallIndent);
5145            LineTo(MidX, R.Bottom - SmallIndent);
5146          end;
5147        end;
5148      tvestArrow,
5149      tvestArrowFill:
5150        begin
5151          // draw an arrow. down for collapse and right for expand
5152          R := Rect(ALeft, ATop, ARight+1, ABottom+1); //+1 for simmetry of arrow
5153          if CollapseSign then
5154          begin
5155            // draw an arrow down
5156            Points[0] := Point(R.Left, MidY - cShiftHorzArrow);
5157            Points[1] := Point(R.Right - 1, MidY - cShiftHorzArrow);
5158            Points[2] := Point(MidX, R.Bottom - 1 - cShiftHorzArrow);
5159          end else
5160          begin
5161            // draw an arrow right
5162            Points[0] := Point(MidX - 1, ATop);
5163            Points[1] := Point(R.Right - 2, MidY);
5164            Points[2] := Point(MidX - 1, R.Bottom - 1);
5165          end;
5166
5167          if ExpandSignType = tvestArrowFill then
5168          begin
5169            PrevColor := Brush.Color;
5170            Brush.Color := ExpandSignColor;
5171          end;
5172          Polygon(Points, 3, False);
5173          if ExpandSignType = tvestArrowFill then
5174          begin
5175            Brush.Color := PrevColor;
5176          end;
5177        end;
5178      end;
5179    end;
5180  end;
5181
5182  procedure DrawInsertMark;
5183  var
5184    InsertMarkRect: TRect;
5185    x: Integer;
5186  begin
5187    case InsertMarkType of
5188
5189    tvimAsFirstChild:
5190      if InsertMarkNode=Node then begin
5191        // draw insert mark for new first child
5192        with Canvas do begin
5193          // draw virtual tree line
5194          Pen.Color:=TreeLineColor;
5195          // Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
5196          x:=Node.DisplayExpandSignRight+RealIndent div 2;
5197          MoveTo(x,NodeRect.Bottom-3);
5198          LineTo(x,NodeRect.Bottom-2);
5199          x:=Node.DisplayExpandSignRight+RealIndent;
5200          LineTo(x,NodeRect.Bottom-2);
5201          Pen.Style:=psSolid;
5202
5203          // draw virtual rectangle
5204          Pen.Color:=TreeLineColor;
5205          Brush.Color:=FSelectedColor;
5206          InsertMarkRect:=Rect(x,NodeRect.Bottom-3,
5207                               NodeRect.Right,NodeRect.Bottom-1);
5208          Rectangle(InsertMarkRect);
5209        end;
5210      end;
5211
5212    tvimAsPrevSibling:
5213      if InsertMarkNode=Node then begin
5214        // draw insert mark for new previous sibling
5215        with Canvas do begin
5216          // draw virtual tree line
5217          Pen.Color:=TreeLineColor;
5218          //Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
5219          x:=Node.DisplayExpandSignLeft+RealIndent div 2;
5220          MoveTo(x,NodeRect.Top+1);
5221          x:=Node.DisplayExpandSignRight;
5222          LineTo(x,NodeRect.Top+1);
5223          Pen.Style:=psSolid;
5224
5225          // draw virtual rectangle
5226          Pen.Color:=TreeLineColor;
5227          Brush.Color:=FSelectedColor;
5228          InsertMarkRect:=Rect(x,NodeRect.Top,
5229                               NodeRect.Right,NodeRect.Top+2);
5230          Rectangle(InsertMarkRect);
5231        end;
5232      end;
5233
5234    tvimAsNextSibling:
5235      if InsertMarkNode=Node then begin
5236        // draw insert mark for new next sibling
5237        with Canvas do begin
5238          // draw virtual tree line
5239          Pen.Color:=TreeLineColor;
5240          //Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
5241          x:=Node.DisplayExpandSignLeft+RealIndent div 2;
5242          MoveTo(x,NodeRect.Bottom-3);
5243          LineTo(x,NodeRect.Bottom-2);
5244          x:=Node.DisplayExpandSignRight;
5245          LineTo(x,NodeRect.Bottom-2);
5246          Pen.Style:=psSolid;
5247
5248          // draw virtual rectangle
5249          Pen.Color:=TreeLineColor;
5250          Brush.Color:=FSelectedColor;
5251          InsertMarkRect:=Rect(x,NodeRect.Bottom-3,
5252                               NodeRect.Right,NodeRect.Bottom-1);
5253          Rectangle(InsertMarkRect);
5254        end;
5255      end;
5256
5257    end;
5258  end;
5259
5260  procedure DrawBackground(IsSelected: Boolean; ARect: TRect);
5261  var
5262    Details: TThemedElementDetails;
5263    CurBackgroundColor,bclr: TColor;
5264  begin
5265    bclr:=Canvas.Brush.Color;
5266    try
5267      if (tvoRowSelect in Options) and IsSelected then
5268        if tvoThemedDraw in Options then
5269        begin
5270          if tvoFocusedPainting in FStates then
5271            Details := ThemeServices.GetElementDetails(ttItemSelected)
5272          else
5273            Details := ThemeServices.GetElementDetails(ttItemSelectedNotFocus);
5274          if ThemeServices.HasTransparentParts(Details) then
5275          begin
5276            Canvas.Brush.Color := Color;
5277            Canvas.FillRect(ARect);
5278          end;
5279          ThemeServices.DrawElement(Canvas.Handle, Details, ARect, nil);
5280          Exit;
5281        end
5282        else
5283          CurBackgroundColor := FSelectedColor
5284      else
5285        CurBackgroundColor := Color;
5286      if CurBackgroundColor <> clNone then
5287      begin
5288        Canvas.Brush.Color := CurBackgroundColor;
5289        Canvas.FillRect(ARect);
5290      end;
5291    finally
5292      Canvas.Brush.Color := bclr;
5293    end;
5294  end;
5295
5296  procedure DrawNodeText(IsSelected: Boolean; NdRect: TRect; AText: String);
5297  var
5298    Details: TThemedElementDetails;
5299    NeedUnderline: Boolean;
5300    PrevFontStyle: TFontStyles;
5301    PrevFontColor: TColor;
5302  begin
5303    if IsSelected then
5304    begin
5305      if tvoFocusedPainting in FStates then
5306        Details := ThemeServices.GetElementDetails(ttItemSelected)
5307      else
5308        Details := ThemeServices.GetElementDetails(ttItemSelectedNotFocus);
5309      if not (tvoRowSelect in Options) then
5310        if (tvoThemedDraw in Options) then
5311          ThemeServices.DrawElement(Canvas.Handle, Details, NdRect, nil)
5312        else
5313        begin
5314          Canvas.Brush.Color := FSelectedColor;
5315          Canvas.Font.Color := IfThen(FSelectedFontColorUsed,
5316                                      FSelectedFontColor, InvertNdColor(FSelectedColor));
5317          Canvas.FillRect(NdRect);
5318        end
5319      else
5320      if not (tvoThemedDraw in Options) then
5321      begin
5322        Canvas.Brush.Color := FSelectedColor;
5323        Canvas.Font.Color := IfThen(FSelectedFontColorUsed,
5324                                    FSelectedFontColor, InvertNdColor(FSelectedColor));
5325        Canvas.FillRect(NdRect);
5326      end;
5327    end
5328    else
5329      Details := ThemeServices.GetElementDetails(ttItemNormal);
5330
5331    NeedUnderline := (tvoHotTrack in FOptions) and (Node=FNodeUnderCursor);
5332    if NeedUnderline then
5333    begin
5334      PrevFontStyle := Canvas.Font.Style;
5335      PrevFontColor := Canvas.Font.Color;
5336      Canvas.Font.Style := [fsUnderline];
5337      if FHotTrackColor<>clNone then
5338        Canvas.Font.Color := FHotTrackColor;
5339    end;
5340
5341    NdRect.Offset(ScaleX(2, 96), 0);
5342    if (tvoThemedDraw in Options) then
5343    begin
5344      if not Enabled then
5345        Details.State := 4; // TmSchema.TREIS_DISABLED = 4
5346      ThemeServices.DrawText(Canvas, Details, AText, NdRect, DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX, 0);
5347    end
5348    else
5349    begin
5350      if not Enabled and (FDisabledFontColor<>clNone) then
5351        Canvas.Font.Color := FDisabledFontColor;
5352      DrawText(Canvas.Handle, PChar(AText), -1, NdRect, DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);
5353    end;
5354
5355    if NeedUnderline then
5356    begin
5357      Canvas.Font.Style := PrevFontStyle;
5358      Canvas.Font.Color := PrevFontColor;
5359    end;
5360  end;
5361
5362
5363var
5364  x, ImgIndex: integer;
5365  CurTextRect, ImgRect: TRect;
5366  DrawState: TCustomDrawState;
5367  PaintImages: boolean;
5368  OverlayIndex: Integer;
5369  ImageRes, StateImageRes: TScaledImageListResolution;
5370begin
5371  if Assigned(FImages) then
5372    ImageRes := Images.ResolutionForPPI[ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
5373  if Assigned(FStateImages) then
5374    StateImageRes := StateImages.ResolutionForPPI[StateImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
5375  RealExpandSignSize := ExpandSignSize;
5376  RealIndent := Indent;
5377  NodeRect := Node.DisplayRect(False);
5378  if (NodeRect.Bottom < 0) or (NodeRect.Top >= ClientHeight) then
5379    Exit;
5380  NodeSelected := (Node.Selected) or (Node.MultiSelected);
5381  Canvas.Font.Color := Font.Color;
5382  Canvas.Brush.Color := Color;
5383  PaintImages := True;
5384  if IsCustomDrawn(dtItem, cdPrePaint) then
5385  begin
5386    DrawState := [];
5387    if NodeSelected then
5388      Include(DrawState, cdsSelected);
5389    if Node.Focused then
5390      Include(DrawState, cdsFocused);
5391    if Node.MultiSelected then
5392      Include(DrawState, cdsMarked);
5393    if not CustomDrawItem(Node, DrawState, cdPrePaint, PaintImages) then Exit;
5394  end;
5395
5396  VertMid := NodeRect.Top + (NodeRect.Bottom - NodeRect.Top) div 2;
5397  HasExpandSign := ShowButtons and Node.HasChildren and ((tvoShowRoot in Options) or (Node.Parent <> nil));
5398  VertDelta := Ord(FDefItemHeight and 3 = 2);
5399  //DebugLn(['[TCustomTreeView.DoPaintNode] Node=',DbgS(Node),' Node.Text=',Node.Text,' NodeRect=',NodeRect.Left,',',NodeRect.Top,',',NodeRect.Right,',',NodeRect.Bottom,' VertMid=',VertMid]);
5400  with Canvas do
5401  begin
5402    // draw background
5403    DrawBackground(NodeSelected, NodeRect);
5404
5405    // draw tree lines
5406    Pen.Color := TreeLineColor;
5407    Pen.Style := TreeLinePenStyle;
5408    if Pen.Style = psPattern then
5409      Pen.SetPattern(FTreeLinePenPattern);
5410    x := DrawTreeLines(Node);
5411    Pen.Style := psSolid;
5412
5413    // draw expand sign
5414    if HasExpandSign then
5415      DrawExpandSign(x - RealIndent + (RealIndent shr 1), VertMid, Node.Expanded);
5416
5417    // draw state icon
5418    if (StateImages <> nil) then
5419    begin
5420      if (Node.StateIndex >= 0) and (Node.StateIndex < StateImages.Count) then
5421      begin
5422        if PaintImages then
5423          StateImageRes.Draw(Canvas, x + 1, NodeRect.Top +(NodeRect.Bottom - NodeRect.Top - StateImageRes.Height) div 2,
5424            Node.StateIndex, True);
5425        Inc(x, StateImageRes.Width + FDefItemSpace);
5426      end;
5427    end;
5428
5429    // draw icon
5430    if (Images = nil) then
5431    begin
5432      imgRect := NodeRect;
5433      imgRect.Left := x+1;
5434      inc(x, DrawBuiltinIcon(Node, imgRect).CX + FDefItemSpace);
5435    end else
5436    begin
5437      if FSelectedNode <> Node then
5438      begin
5439      	GetImageIndex(Node);
5440        ImgIndex := Node.ImageIndex
5441      end
5442      else
5443      begin
5444      	GetSelectedIndex(Node);
5445        ImgIndex := Node.SelectedIndex;
5446      end;
5447      if (ImgIndex >= 0) and (ImgIndex < Images.Count) then
5448      begin
5449        if PaintImages then
5450        begin
5451      	  if (Node.OverlayIndex >= 0) then begin
5452            OverlayIndex:=Node.OverlayIndex;
5453            if Images.HasOverlays then begin
5454              ImageRes.DrawOverlay(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
5455                 ImgIndex, OverlayIndex, Node.FNodeEffect);
5456            end else begin
5457              // draw the Overlay using the image from the list
5458              // set an Overlay
5459              Images.OverLay(OverlayIndex,0);
5460              // draw overlay
5461              ImageRes.DrawOverlay(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
5462                 ImgIndex, 0, Node.FNodeEffect);
5463              // reset the Overlay
5464              Images.OverLay(-1,0);
5465            end;
5466          end
5467          else begin
5468            ImageRes.Draw(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
5469               ImgIndex, Node.FNodeEffect);
5470          end;
5471        end;
5472        Inc(x, ImageRes.Width + FDefItemSpace);
5473      end;
5474    end;
5475
5476    // draw text
5477    if Node.Text <> '' then
5478    begin
5479      CurTextRect := NodeRect;
5480      CurTextRect.Left := x;
5481      CurTextRect.Right := x + TextWidth(Node.Text) + (FDefItemSpace * 2);
5482      DrawNodeText(NodeSelected, CurTextRect, Node.Text);
5483    end;
5484
5485    // draw separator
5486    if (tvoShowSeparators in FOptions) then
5487    begin
5488      Pen.Color:=SeparatorColor;
5489      MoveTo(NodeRect.Left,NodeRect.Bottom-1);
5490      LineTo(NodeRect.Right,NodeRect.Bottom-1);
5491    end;
5492
5493    // draw insert mark
5494    DrawInsertMark;
5495  end;
5496  PaintImages := true;
5497  if IsCustomDrawn(dtItem, cdPostPaint) then
5498  begin
5499    DrawState:=[];
5500    if Node.Selected then
5501      Include(DrawState,cdsSelected);
5502    if Node.Focused then
5503      Include(DrawState,cdsFocused);
5504    if Node.MultiSelected then
5505      Include(DrawState,cdsMarked);
5506    if not CustomDrawItem(Node,DrawState,cdPostPaint,PaintImages) then exit;
5507  end;
5508end;
5509
5510function TCustomTreeView.DrawBuiltinIcon(ANode: TTreeNode; ARect: TRect): TSize;
5511begin
5512  Result := Size(0, 0);
5513end;
5514
5515procedure TCustomTreeView.GetImageIndex(Node: TTreeNode);
5516begin
5517  if Assigned(FOnGetImageIndex) then
5518    FOnGetImageIndex(Self, Node);
5519end;
5520
5521procedure TCustomTreeView.GetSelectedIndex(Node: TTreeNode);
5522begin
5523  if Assigned(FOnGetSelectedIndex) then FOnGetSelectedIndex(Self, Node);
5524end;
5525
5526function TCustomTreeView.CanChange(Node: TTreeNode): Boolean;
5527begin
5528  Result := True;
5529  if Assigned(Node) and Assigned(FOnChanging) then
5530    FOnChanging(Self, Node, Result);
5531end;
5532
5533procedure TCustomTreeView.Change(Node: TTreeNode);
5534begin
5535  if Assigned(FOnChange) then
5536    FOnChange(Self, Node);
5537end;
5538
5539procedure TCustomTreeView.Delete(Node: TTreeNode);
5540begin
5541  if Assigned(FOnDeletion) then FOnDeletion(Self, Node);
5542end;
5543
5544function TCustomTreeView.ExpandSignSizeIsStored: Boolean;
5545begin
5546  Result := FExpandSignSize >= 0;
5547end;
5548
5549procedure TCustomTreeView.Expand(Node: TTreeNode);
5550begin
5551  UpdateScrollbars;
5552  if Assigned(FOnExpanded) then
5553    FOnExpanded(Self, Node);
5554end;
5555
5556function TCustomTreeView.CanExpand(Node: TTreeNode): Boolean;
5557begin
5558  Result := True;
5559  if Assigned(FOnExpanding)then
5560    FOnExpanding(Self, Node, Result);
5561end;
5562
5563procedure TCustomTreeView.Collapse(Node: TTreeNode);
5564begin
5565  if csDestroying in ComponentState then
5566    exit;
5567  UpdateScrollbars;
5568  if Assigned(FOnCollapsed) then
5569    FOnCollapsed(Self, Node);
5570end;
5571
5572function TCustomTreeView.CanCollapse(Node: TTreeNode): Boolean;
5573begin
5574  Result := True;
5575  if csDestroying in ComponentState then
5576    exit;
5577  if Assigned(FOnCollapsing) then
5578    FOnCollapsing(Self, Node, Result);
5579end;
5580
5581function TCustomTreeView.CanEdit(Node: TTreeNode): Boolean;
5582begin
5583  Result := True;
5584  if Assigned(FOnEditing) then
5585    FOnEditing(Self, Node, Result);
5586end;
5587
5588procedure TCustomTreeView.EndEditing(Cancel: boolean);
5589var
5590  NewText: String;
5591  Node: TTreeNode;
5592begin
5593  //DebugLn(['TCustomTreeView.EndEditing ',DbgSName(Self),' ',tvsIsEditing in FStates,' ',DbgSName(FEditor)]);
5594  if not (tvsIsEditing in FStates) then exit;
5595  Exclude(FStates,tvsIsEditing);
5596  if FEditor<>nil then begin
5597    // get new value fom edit control and hide it
5598    NewText:='';
5599    if not Cancel then
5600      NewText:=FEditor.Text;
5601    FEditor.Parent:=nil;
5602
5603    // commit new value
5604    if not Cancel then begin
5605      Node:=FEditingItem;
5606      if (Node<>nil) then begin
5607        if Assigned(OnEdited) then
5608          OnEdited(Self,Node,NewText);
5609        Node.Text:=NewText;
5610      end;
5611    end;
5612    if Assigned(FOnEditingEnd) then FOnEditingEnd(Self, FEditingItem, Cancel);
5613  end;
5614  FEditingItem := nil;
5615  Invalidate;
5616end;
5617
5618procedure TCustomTreeView.EnsureNodeIsVisible(ANode: TTreeNode);
5619var b: integer;
5620begin
5621  if ANode=nil then exit;
5622  ANode.ExpandParents;
5623  if ANode.Top<ScrolledTop then
5624    ScrolledTop:=ANode.Top
5625  else begin
5626    b:=ANode.Top+ANode.Height-GetNodeDrawAreaHeight;
5627    if ScrolledTop<b then ScrolledTop:=b;
5628  end;
5629end;
5630
5631function TCustomTreeView.CreateNode: TTreeNode;
5632var
5633  NewNodeClass: TTreeNodeClass;
5634begin
5635  Result := nil;
5636  if Assigned(FOnCustomCreateItem) then
5637    FOnCustomCreateItem(Self, Result);
5638  if Result = nil then
5639  begin
5640    NewNodeClass:=TTreeNode;
5641    DoCreateNodeClass(NewNodeClass);
5642    Result := NewNodeClass.Create(Items);
5643  end;
5644end;
5645
5646function TCustomTreeView.CreateNodes: TTreeNodes;
5647begin
5648  Result := TTreeNodes.Create(Self);
5649end;
5650
5651procedure TCustomTreeView.ImageListChange(Sender: TObject);
5652begin
5653  Invalidate;
5654end;
5655
5656function TCustomTreeView.IndentIsStored: Boolean;
5657begin
5658  Result := FIndent >= 0;
5659end;
5660
5661function TCustomTreeView.NodeIsSelected(aNode: TTreeNode): Boolean;
5662begin
5663  Result := Assigned(aNode) and
5664    (aNode.Selected or ((tvoAllowMultiselect in Options) and aNode.MultiSelected));
5665end;
5666
5667procedure TCustomTreeView.MouseDown(Button: TMouseButton; Shift: TShiftState;
5668  X, Y: Integer);
5669var
5670  CursorNode: TTreeNode;
5671  CursorNdSelected: Boolean;
5672  LogicalX: Integer;
5673begin
5674  {$IFDEF VerboseDrag}
5675  DebugLn('TCustomTreeView.MouseDown A ',DbgSName(Self),' ');
5676  {$ENDIF}
5677  FMouseDownPos := Point(X,Y);
5678  FStates:=FStates-[tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
5679
5680  CursorNode := GetNodeAt(X, Y);
5681  CursorNdSelected := NodeIsSelected(CursorNode);
5682  LogicalX:=X;
5683
5684  //change selection on right click
5685  if (Button = mbRight) and RightClickSelect and //right click
5686     (([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
5687     not AllowMultiSelectWithCtrl(Shift) and //only when CTRL is not pressed
5688     (CursorNode <> nil)
5689  then
5690  begin
5691    if not (tvoRowSelect in Options) and
5692       (tvoEmptySpaceUnselect in Options) and
5693       (LogicalX >= CursorNode.DisplayStateIconLeft) and
5694       (LogicalX > CursorNode.DisplayTextRight) then
5695      ClearSelection
5696    else
5697    if not (tvoAllowMultiselect in Options) then
5698      Selected := CursorNode
5699    else
5700    if not CursorNdSelected then
5701      Items.SelectOnlyThis(CursorNode);
5702  end
5703  else // empty space below last node
5704  if (Button = mbRight) and RightClickSelect and (CursorNode = nil) and
5705     (tvoEmptySpaceUnselect in Options) then
5706    ClearSelection;
5707
5708  if not Focused and CanFocus then
5709    SetFocus;
5710
5711  inherited MouseDown(Button, Shift, X, Y);
5712
5713  //CursorNode must be reassigned again - e.g. in OnMouseDown the node can be deleted or moved.
5714  CursorNode := GetNodeWithExpandSignAt(LogicalX, Y);
5715  CursorNdSelected := NodeIsSelected(CursorNode);
5716
5717  //Flag is used for DblClick/TripleClick/QuadClick, so set it before testing ShiftState
5718  FMouseDownOnFoldingSign :=
5719    Assigned(CursorNode) and CursorNode.HasChildren and ShowButtons and
5720    (LogicalX >= CursorNode.DisplayExpandSignLeft) and
5721    (LogicalX < CursorNode.DisplayExpandSignRight);
5722
5723  //change selection on left click
5724  if (Button = mbLeft) and //left click
5725     (([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
5726     (CursorNode <> nil) then
5727  begin
5728    if FMouseDownOnFoldingSign then
5729      // mousedown occurred on expand sign -> expand/collapse
5730      CursorNode.Expanded := not CursorNode.Expanded
5731    else if (LogicalX >= CursorNode.DisplayStateIconLeft) or (tvoRowSelect in Options) then
5732    begin
5733      // mousedown occurred in text or icon -> select node and begin drag operation
5734      {$IFDEF VerboseDrag}
5735      DebugLn(['TCustomTreeView.MouseDown In Text ',DbgSName(Self),' MouseCapture=',MouseCapture]);
5736      {$ENDIF}
5737      if (Selected = CursorNode) and (LogicalX >= CursorNode.DisplayTextLeft) then
5738        Include(FStates, tvsEditOnMouseUp);
5739      if not (tvoAllowMultiselect in Options) then
5740        Selected := CursorNode
5741      else
5742      begin
5743        if AllowMultiSelectWithShift(Shift) then
5744        begin
5745          Exclude(FStates,tvsEditOnMouseUp);
5746          LockSelectionChangeEvent;
5747          try
5748            Items.MultiSelect(CursorNode, not AllowMultiSelectWithCtrl(Shift));
5749          finally
5750            UnlockSelectionChangeEvent;
5751          end;
5752        end
5753        else if AllowMultiSelectWithCtrl(Shift) then
5754        begin
5755          Exclude(FStates,tvsEditOnMouseUp);
5756          CursorNode.MultiSelected:=not CursorNode.MultiSelected;
5757          if CursorNode.MultiSelected then
5758            FTreeNodes.FStartMultiSelected := CursorNode;
5759        end
5760        else
5761        begin
5762          if not CursorNdSelected then
5763            Items.SelectOnlyThis(CursorNode)
5764          else
5765            Include(FStates, tvsSingleSelectOnMouseUp);
5766        end;
5767      end;
5768    end
5769    else if tvoEmptySpaceUnselect in Options then
5770      ClearSelection;
5771  end
5772  else// multi click
5773  if not (tvoNoDoubleClickExpand in Options) and (ssDouble in Shift)
5774  and (Button = mbLeft) and (CursorNode<>nil) then
5775    CursorNode.Expanded := not CursorNode.Expanded
5776  else  // empty space below last node
5777  if (Button = mbLeft) and (CursorNode = nil) and (tvoEmptySpaceUnselect in Options) and
5778     not AllowMultiSelectWithShift(Shift) and not AllowMultiSelectWithCtrl(Shift) then
5779    ClearSelection;
5780end;
5781
5782procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer);
5783begin
5784  inherited MouseMove(Shift, x, y);
5785  if (tvoAutoInsertMark in FOptions) then
5786    UpdateInsertMark(X,Y);
5787  UpdateTooltip(X, Y);
5788  UpdateHotTrack(X, Y);
5789end;
5790
5791procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
5792begin
5793  FNodeUnderCursor := nil;
5794  if Cursor = crHandPoint then
5795    Cursor := crDefault;
5796  if not (tvoHotTrack in FOptions) then Exit;
5797
5798  FNodeUnderCursor := GetNodeAt(X, Y);
5799  if Assigned(FNodeUnderCursor) then
5800    Cursor := crHandPoint;
5801  Invalidate;
5802end;
5803
5804procedure TCustomTreeView.MouseUp(Button: TMouseButton; Shift: TShiftState;
5805  X, Y: Integer);
5806var
5807  aMouseDownNode, aMouseUpNode: TTreeNode;
5808begin
5809  // must hide hint window in mouse up to receive redirected mouse up messages
5810  if (FHintWnd<>nil) and FHintWnd.Visible then
5811    FHintWnd.Hide;
5812  inherited MouseUp(Button, Shift, X, Y);
5813  if (Button = mbRight) and (Shift = [ssRight]) and Assigned(PopupMenu) then
5814    exit;
5815  if Button=mbLeft then
5816  begin
5817    MouseCapture := False;
5818    if FStates * [tvsDblClicked, tvsTripleClicked, tvsQuadClicked] = [] then
5819    begin
5820      //AquirePrimarySelection;
5821      aMouseDownNode:=GetNodeAt(FMouseDownPos.X,FMouseDownPos.Y);
5822      aMouseUpNode:=GetNodeAt(X,Y);
5823      if (abs(FMouseDownPos.X-X)+abs(FMouseDownPos.Y-Y)<10)
5824      and (aMouseDownNode=aMouseUpNode) then
5825      begin
5826        // mouse up on mouse-down node
5827        if (tvsEditOnMouseUp in FStates) and (not ReadOnly) then
5828          BeginEditing(Selected)
5829        else if (tvsSingleSelectOnMouseUp in FStates) then
5830          Items.SelectOnlyThis(aMouseUpNode);
5831      end;
5832    end;
5833  end;
5834  FStates:=FStates-[tvsDblClicked,tvsTripleClicked,tvsQuadClicked,
5835                    tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
5836end;
5837
5838procedure TCustomTreeView.MoveEnd(ASelect: Boolean);
5839var
5840  lNode: TTreeNode;
5841begin
5842  lNode := Items.GetLastExpandedSubNode;
5843  if lNode <> nil then
5844    MoveSelection(lNode, ASelect);
5845end;
5846
5847procedure TCustomTreeView.MoveHome(ASelect: Boolean);
5848var
5849  lNode: TTreeNode;
5850begin
5851  lNode := Items.GetFirstVisibleNode;
5852  if lNode <> nil then
5853    MoveSelection(lNode, ASelect);
5854end;
5855
5856procedure TCustomTreeView.MovePageDown(ASelect: Boolean);
5857var
5858  I: Integer;
5859  lNode: TTreeNode;
5860begin
5861  if tvoAllowMultiSelect in FOptions then
5862    lNode := FTreeNodes.FLastMultiSelected
5863  else
5864    lNode := Selected;
5865  if lNode = nil then
5866    lNode := Items.GetFirstVisibleNode;
5867
5868  if lNode <> nil then
5869  begin
5870    I := Pred(ClientHeight div DefaultItemHeight);
5871
5872    while (I > 0) do
5873      if lNode.GetNextExpanded <> nil then
5874      begin
5875        lNode := lNode.GetNextExpanded;
5876        Dec(I);
5877      end
5878      else Break;
5879
5880  end;
5881  if lNode <> nil then
5882    MoveSelection(lNode, ASelect);
5883end;
5884
5885procedure TCustomTreeView.MovePageUp(ASelect: Boolean);
5886var
5887  I: Integer;
5888  lNode: TTreeNode;
5889begin
5890  if tvoAllowMultiSelect in FOptions then
5891    lNode := FTreeNodes.FLastMultiSelected
5892  else
5893    lNode := Selected;
5894  if lNode = nil then
5895    lNode := Items.GetFirstVisibleNode;
5896
5897  if lNode <> nil then
5898  begin
5899    I := Pred(ClientHeight div DefaultItemHeight);
5900
5901    while (I > 0) do
5902      if lNode.GetPrevExpanded <> nil then
5903      begin
5904        lNode := lNode.GetPrevExpanded;
5905        Dec(I);
5906      end
5907      else Break;
5908
5909  end;
5910  if lNode <> nil then
5911    MoveSelection(lNode, ASelect);
5912end;
5913
5914procedure TCustomTreeView.MoveSelection(ANewNode: TTreeNode; ASelect: Boolean);
5915begin
5916  if tvoAllowMultiSelect in FOptions then
5917  begin
5918    if ASelect then
5919      FTreeNodes.MultiSelect(ANewNode, False)
5920    else begin
5921      FTreeNodes.SelectOnlyThis(ANewNode);
5922    end;
5923  end else
5924    Selected := ANewNode;
5925  ANewNode.MakeVisible;
5926
5927  UpdateScrollbars;
5928end;
5929
5930procedure TCustomTreeView.MouseLeave;
5931begin
5932  FStates:=FStates-[tvsDblClicked,tvsTripleClicked,tvsQuadClicked,
5933                    tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
5934  if Assigned(FHintWnd) and FHintWnd.Visible
5935  and ((WidgetSet.GetLCLCapability(lcTransparentWindow) = LCL_CAPABILITY_YES)
5936       or not PtInRect(ClientRect, ScreenToClient(Mouse.CursorPos))) then
5937    FHintWnd.Hide;
5938
5939  if tvoHotTrack in FOptions then
5940  begin
5941    FNodeUnderCursor:=nil;
5942    Cursor:=crDefault;
5943    Invalidate;
5944  end;
5945
5946  inherited MouseLeave;
5947end;
5948
5949procedure TCustomTreeView.NodeChanged(Node: TTreeNode; ChangeReason: TTreeNodeChangeReason);
5950begin
5951  if assigned(FOnNodeChanged) then
5952     OnNodeChanged(self,Node,ChangeReason);
5953end;
5954
5955function TCustomTreeView.NodeHasChildren(Node: TTreeNode): Boolean;
5956begin
5957  if Assigned(FOnHasChildren) then
5958    Result := FOnHasChildren(Self, Node)
5959  else
5960    Result := false;
5961end;
5962
5963procedure TCustomTreeView.Notification(AComponent: TComponent; Operation: TOperation);
5964begin
5965  inherited Notification(AComponent, Operation);
5966  if Operation = opRemove then begin
5967    if AComponent = Images then Images := nil;
5968    if AComponent = StateImages then StateImages := nil;
5969  end;
5970end;
5971
5972procedure TCustomTreeView.SetImages(Value: TCustomImageList);
5973var
5974  AImageHeight: Integer;
5975begin
5976  if Images = Value then exit;
5977  if Images <> nil then
5978    Images.UnRegisterChanges(FImageChangeLink);
5979  FImages := Value;
5980  if Images <> nil then begin
5981    Images.RegisterChanges(FImageChangeLink);
5982    Images.FreeNotification(Self);
5983    AImageHeight := Images.HeightForPPI[ImagesWidth, Font.PixelsPerInch];
5984    if DefaultItemHeight<AImageHeight+FDefItemSpace then
5985      DefaultItemHeight:=AImageHeight+FDefItemSpace;
5986  end;
5987  Invalidate;
5988end;
5989
5990procedure TCustomTreeView.SetImagesWidth(const aImagesWidth: Integer);
5991begin
5992  if FImagesWidth = aImagesWidth then Exit;
5993  FImagesWidth := aImagesWidth;
5994  Invalidate;
5995end;
5996
5997procedure TCustomTreeView.SetInsertMarkNode(const AValue: TTreeNode);
5998var
5999  InvalidateNeeded: Boolean;
6000begin
6001  if FInsertMarkNode=AValue then exit;
6002  InvalidateNeeded:=IsInsertMarkVisible;
6003  FInsertMarkNode:=AValue;
6004  InvalidateNeeded:=InvalidateNeeded or IsInsertMarkVisible;
6005  if InvalidateNeeded then Invalidate;
6006end;
6007
6008procedure TCustomTreeView.SetInsertMarkType(
6009  const AValue: TTreeViewInsertMarkType);
6010var
6011  InvalidateNeeded: Boolean;
6012begin
6013  if FInsertMarkType=AValue then exit;
6014  InvalidateNeeded:=IsInsertMarkVisible;
6015  FInsertMarkType:=AValue;
6016  InvalidateNeeded:=InvalidateNeeded or IsInsertMarkVisible;
6017  if InvalidateNeeded then Invalidate;
6018end;
6019
6020procedure TCustomTreeView.SetStateImages(Value: TCustomImageList);
6021var
6022  AStateImageHeight: Integer;
6023begin
6024  if FStateImages=Value then exit;
6025  if StateImages <> nil then
6026    StateImages.UnRegisterChanges(FStateChangeLink);
6027  FStateImages := Value;
6028  if StateImages <> nil then begin
6029    StateImages.RegisterChanges(FStateChangeLink);
6030    StateImages.FreeNotification(Self);
6031    AStateImageHeight := StateImages.HeightForPPI[StateImagesWidth, Font.PixelsPerInch];
6032    if DefaultItemHeight<AStateImageHeight+FDefItemSpace then
6033      DefaultItemHeight:=AStateImageHeight+FDefItemSpace;
6034  end;
6035  Invalidate;
6036end;
6037
6038procedure TCustomTreeView.SetStateImagesWidth(const aStateImagesWidth: Integer);
6039begin
6040  if FStateImagesWidth = aStateImagesWidth then Exit;
6041  FStateImagesWidth := aStateImagesWidth;
6042  Invalidate;
6043end;
6044
6045procedure TCustomTreeView.LoadFromFile(const FileName: string);
6046var
6047  Stream: TStream;
6048begin
6049  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
6050  try
6051    LoadFromStream(Stream);
6052  finally
6053    Stream.Free;
6054  end;
6055end;
6056
6057procedure TCustomTreeView.LoadFromStream(Stream: TStream);
6058begin
6059  with TTreeStrings.Create(Items) do
6060    try
6061      LoadTreeFromStream(Stream);
6062    finally
6063      Free;
6064  end;
6065end;
6066
6067procedure TCustomTreeView.SaveToFile(const FileName: string);
6068var
6069  Stream: TStream;
6070begin
6071  Stream := TFileStream.Create(FileName, fmCreate);
6072  try
6073    SaveToStream(Stream);
6074  finally
6075    Stream.Free;
6076  end;
6077end;
6078
6079procedure TCustomTreeView.SaveToStream(Stream: TStream);
6080begin
6081  with TTreeStrings.Create(Items) do
6082    try
6083      SaveTreeToStream(Stream);
6084    finally
6085      Free;
6086  end;
6087end;
6088
6089procedure TCustomTreeView.ScrollView(DeltaX, DeltaY: Integer);
6090var
6091  ScrollArea: TRect;
6092  ScrollFlags: Integer;
6093begin
6094  if (DeltaX=0) and (DeltaY=0) then
6095    Exit;
6096
6097  Include(FStates,tvsScrollbarChanged);
6098  ScrollFlags := SW_INVALIDATE or SW_ERASE;
6099  ScrollArea := ClientRect;
6100  InflateRect(ScrollArea, -BorderWidth, -BorderWidth);
6101  ScrollWindowEx(Handle, DeltaX, DeltaY, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
6102  UpdateScrollbars;
6103end;
6104
6105procedure TCustomTreeView.WMVScroll(var Msg: TLMScroll);
6106begin
6107  case Msg.ScrollCode of
6108      // Scrolls to start / end of the text
6109    SB_TOP:        ScrolledTop := 0;
6110    SB_BOTTOM:     ScrolledTop := GetMaxScrollTop;
6111      // Scrolls one line up / down
6112    SB_LINEDOWN:   ScrolledTop := ScrolledTop + FDefItemHeight;
6113    SB_LINEUP:     ScrolledTop := ScrolledTop - FDefItemHeight;
6114      // Scrolls one page of lines up / down
6115    SB_PAGEDOWN:   ScrolledTop := ScrolledTop + ClientHeight
6116                                     - FDefItemHeight;
6117    SB_PAGEUP:     ScrolledTop := ScrolledTop - ClientHeight
6118                                     + FDefItemHeight;
6119      // Scrolls to the current scroll bar position
6120    SB_THUMBPOSITION,
6121    SB_THUMBTRACK: ScrolledTop := Msg.Pos;
6122
6123    SB_ENDSCROLL: ; // Ends scrolling
6124  end;
6125end;
6126
6127procedure TCustomTreeView.WMHScroll(var Msg: TLMScroll);
6128begin
6129  case Msg.ScrollCode of
6130      // Scrolls to start / end of the text
6131    SB_LEFT:       ScrolledLeft := 0;
6132    SB_RIGHT:      ScrolledLeft := GetMaxScrollLeft;
6133      // Scrolls one line left / right
6134    SB_LINERIGHT:  ScrolledLeft := ScrolledLeft + FDefItemHeight div 2;
6135    SB_LINELEFT:   ScrolledLeft := ScrolledLeft - FDefItemHeight div 2;
6136      // Scrolls one page of lines left / right
6137    SB_PAGERIGHT:  ScrolledLeft := ScrolledLeft + ClientHeight
6138                                       - FDefItemHeight;
6139    SB_PAGELEFT:   ScrolledLeft := ScrolledLeft - ClientHeight
6140                                       + FDefItemHeight;
6141      // Scrolls to the current scroll bar position
6142    SB_THUMBPOSITION,
6143    SB_THUMBTRACK: ScrolledLeft := Msg.Pos;
6144
6145    SB_ENDSCROLL: ;// Ends scrolling
6146  end;
6147end;
6148
6149procedure TCustomTreeView.WMLButtonDown(var AMessage: TLMLButtonDown);
6150begin
6151  {$IFDEF VerboseDrag}
6152  DebugLn('TCustomTreeView.WMLButtonDown A ',Name,':',ClassName,' ');
6153  {$ENDIF}
6154  Exclude(FStates,tvsDragged);
6155  inherited WMLButtonDown(AMessage);
6156  {$IFDEF VerboseDrag}
6157  DebugLn('TCustomTreeView.WMLButtonDown END ',Name,':',ClassName,' ');
6158  {$ENDIF}
6159end;
6160
6161procedure TCustomTreeView.WMSetFocus(var Message: TLMSetFocus);
6162begin
6163  Invalidate;
6164  inherited;
6165end;
6166
6167procedure TCustomTreeView.WMKillFocus(var Message: TLMKillFocus);
6168begin
6169  Invalidate;
6170  inherited;
6171end;
6172
6173procedure TCustomTreeView.Resize;
6174begin
6175  FStates:=FStates+[tvsScrollbarChanged,tvsBottomItemNeedsUpdate];
6176  inherited Resize;
6177  UpdateScrollbars;
6178end;
6179
6180function TCustomTreeView.GetSelectedChildAccessibleObject: TLazAccessibleObject;
6181var
6182  lNode: TTreeNode;
6183begin
6184  Result := nil;
6185  lNode := GetSelection();
6186  if lNode = nil then Exit;
6187  Result := FAccessibleObject.GetChildAccessibleObjectWithDataObject(lNode);
6188end;
6189
6190function TCustomTreeView.GetChildAccessibleObjectAtPos(APos: TPoint): TLazAccessibleObject;
6191var
6192  lNode: TTreeNode;
6193begin
6194  Result := nil;
6195  lNode := GetNodeAt(APos.X, APos.Y);
6196  //if lNode = nil then DebugLn('[TCustomTreeView.GetChildAccessibleObjectAtPos] lNode=nil')
6197  //else DebugLn('[TCustomTreeView.GetChildAccessibleObjectAtPos] lNode=' + lNode.Text);
6198  if lNode = nil then Exit;
6199  Result := FAccessibleObject.GetChildAccessibleObjectWithDataObject(lNode);
6200end;
6201
6202procedure TCustomTreeView.InternalSelectionChanged;
6203begin
6204  if FSelectionChangeEventLock > 0 then
6205    Include(FStates, tvsSelectionChanged)
6206  else
6207  begin
6208    Exclude(FStates, tvsSelectionChanged);
6209    DoSelectionChanged;
6210    FChangeTimer.Enabled := False;
6211    if not FCallingChange then  // Prevent recursive loop from OnChange handler.
6212      FChangeTimer.Enabled := True;
6213  end;
6214end;
6215
6216function TCustomTreeView.AllowMultiSelectWithCtrl(AState: TShiftState): Boolean;
6217begin
6218  Result := (ssCtrl in AState) and (msControlSelect in FMultiSelectStyle);
6219end;
6220
6221function TCustomTreeView.AllowMultiSelectWithShift(AState: TShiftState): Boolean;
6222begin
6223  Result := (ssShift in AState) and (msShiftSelect in FMultiSelectStyle);
6224end;
6225
6226class procedure TCustomTreeView.WSRegisterClass;
6227begin
6228  inherited WSRegisterClass;
6229  RegisterCustomTreeView;
6230end;
6231
6232class function TCustomTreeView.GetControlClassDefaultSize: TSize;
6233begin
6234  Result.CX := 121;
6235  Result.CY := 97;
6236end;
6237
6238procedure TCustomTreeView.Added(Node: TTreeNode);
6239begin
6240  if Assigned(OnAddition) then OnAddition(Self,Node);
6241end;
6242
6243{ CustomDraw support }
6244
6245procedure TCustomTreeView.EditorEditingDone(Sender: TObject);
6246var
6247  WasFocused: Boolean;
6248begin
6249  WasFocused := (FEditor<>nil) and FEditor.Focused;
6250  EndEditing;
6251  if WasFocused then
6252    SetFocus;
6253end;
6254
6255procedure TCustomTreeView.EditorKeyDown(Sender: TObject; var Key: Word;
6256  Shift: TShiftState);
6257var
6258  WasFocused: Boolean;
6259begin
6260  if (Key = VK_ESCAPE) or (Key = VK_RETURN) then
6261  begin
6262    WasFocused := Assigned(FEditor) and FEditor.Focused;
6263    EndEditing(Key = VK_ESCAPE);
6264    if WasFocused then
6265      SetFocus;
6266    Key := 0; // key was handled
6267  end;
6268end;
6269
6270procedure TCustomTreeView.CanvasChanged(Sender: TObject);
6271begin
6272  Include(FStates,tvsCanvasChanged);
6273end;
6274
6275procedure TCustomTreeView.DragScrollTimerTick(Sender: TObject);
6276const
6277  cScrollDelta = 10;
6278var
6279  Pnt: TPoint;
6280begin
6281  Pnt := ScreenToClient(Mouse.CursorPos);
6282  if (Pnt.X >= 0) and (Pnt.X < ClientWidth) then
6283  begin
6284    if (Pnt.Y >= 0) and (Pnt.Y < FDragScrollMargin) then
6285      ScrolledTop := ScrolledTop - cScrollDelta
6286    else
6287    if (Pnt.Y >= ClientHeight-FDragScrollMargin) and (Pnt.Y < ClientHeight) then
6288      ScrolledTop := ScrolledTop + cScrollDelta;
6289  end;
6290end;
6291
6292procedure TCustomTreeView.ClearSelection(KeepPrimary: Boolean);
6293begin
6294 if tvoAllowMultiSelect in FOptions then
6295   Items.ClearMultiSelection(not KeepPrimary)
6296 else
6297   if not KeepPrimary then Selected := nil;
6298end;
6299
6300function TCustomTreeView.IsCustomDrawn(Target: TCustomDrawTarget;
6301  Stage: TCustomDrawStage): Boolean;
6302begin
6303  { Tree view doesn't support erase notifications }
6304  if Stage = cdPrePaint then begin
6305    if Target = dtItem then
6306      Result := Assigned(FOnCustomDrawItem)
6307                or Assigned(FOnAdvancedCustomDrawItem)
6308    else if Target = dtControl then
6309      Result := Assigned(FOnCustomDraw) or Assigned(FOnAdvancedCustomDraw) or
6310              Assigned(FOnCustomDrawItem) or Assigned(FOnAdvancedCustomDrawItem)
6311    else
6312      Result := False;
6313  end else begin
6314    if Target = dtItem then
6315      Result := Assigned(FOnAdvancedCustomDrawItem)
6316    else if Target = dtControl then
6317      Result := Assigned(FOnAdvancedCustomDraw)
6318                or Assigned(FOnAdvancedCustomDrawItem)
6319    else
6320      Result := False;
6321  end;
6322end;
6323
6324function TCustomTreeView.CustomDraw(const ARect: TRect;
6325  Stage: TCustomDrawStage): Boolean;
6326begin
6327  Result := True;
6328  if (Stage = cdPrePaint) and Assigned(FOnCustomDraw) then
6329    FOnCustomDraw(Self, ARect, Result);
6330  if Assigned(FOnAdvancedCustomDraw) then
6331    FOnAdvancedCustomDraw(Self, ARect, Stage, Result);
6332end;
6333
6334function TCustomTreeView.CustomDrawItem(Node: TTreeNode;
6335  State: TCustomDrawState;
6336  Stage: TCustomDrawStage; var PaintImages: Boolean): Boolean;
6337begin
6338  Result := True;
6339  PaintImages := True;
6340  if (Stage = cdPrePaint) and Assigned(FOnCustomDrawItem) then
6341    FOnCustomDrawItem(Self, Node, State, Result);
6342  if Assigned(FOnAdvancedCustomDrawItem) then
6343    FOnAdvancedCustomDrawItem(Self, Node, State, Stage, PaintImages, Result);
6344end;
6345
6346procedure TCustomTreeView.ConsistencyCheck;
6347var OldMaxRight, OldLastTop, OldMaxLvl: integer;
6348  OldTopItem, OldBottomItem: TTreeNode;
6349begin
6350  if Canvas=nil then
6351    RaiseGDBException('Canvas=nil');
6352  if FDefItemHeight<0 then
6353    RaiseGDBException('FDefItemHeight='+IntToStr(FDefItemHeight));
6354  if FIndent<0 then
6355    RaiseGDBException('FIndent='+IntToStr(FIndent));
6356  if FMaxRight<0 then
6357    RaiseGDBException('FMaxRight='+IntToStr(FMaxRight));
6358  if FTreeNodes=nil then
6359    RaiseGDBException('FTreeNodes=nil');
6360  FTreeNodes.ConsistencyCheck;
6361  if Items.FUpdateCount<0 then
6362    RaiseGDBException('FUpdateCount='+IntToStr(Items.FUpdateCount));
6363  if (not (tvsTopsNeedsUpdate in FStates)) then begin
6364    if Items.GetLastSubNode<>nil then begin
6365      OldLastTop:=Items.GetLastSubNode.Top;
6366      Include(FStates,tvsTopsNeedsUpdate);
6367      UpdateAllTops;
6368      if OldLastTop<>Items.GetLastSubNode.Top then
6369        RaiseGDBException('OldLastTop='+DbgS(OldLastTop)
6370        +'<>Items.GetLastSubNode.Top='+DbgS(Items.GetLastSubNode.Top));
6371    end;
6372  end;
6373  if not (tvsMaxRightNeedsUpdate in FStates) then begin
6374    OldMaxRight:=FMaxRight;
6375    Include(FStates,tvsMaxRightNeedsUpdate);
6376    UpdateMaxRight;
6377    if OldMaxRight<>FMaxRight then
6378      RaiseGDBException('OldMaxRight<>FMaxRight');
6379  end;
6380  if not (tvsMaxLvlNeedsUpdate in FStates) then begin
6381    OldMaxLvl:=FMaxLvl;
6382    Include(FStates,tvsMaxLvlNeedsUpdate);
6383    UpdateMaxLvl;
6384    if OldMaxLvl<>FMaxLvl then
6385      RaiseGDBException('OldMaxLvl<>FMaxLvl');
6386  end;
6387  if (tvsIsEditing in FStates) and (FSelectedNode=nil) then
6388    RaiseGDBException('');
6389  if (FSelectedNode<>nil) then begin
6390    if not FSelectedNode.IsVisible then
6391      RaiseGDBException('not FSelectedNode.IsVisible');
6392  end;
6393  if not (tvsTopItemNeedsUpdate in FStates) then begin
6394    OldTopItem:=FTopItem;
6395    UpdateTopItem;
6396    if FTopItem<>OldTopItem then
6397      RaiseGDBException('FTopItem<>OldTopItem');
6398  end;
6399  if not (tvsBottomItemNeedsUpdate in FStates) then begin
6400    OldBottomItem:=FBottomItem;
6401    UpdateBottomItem;
6402    if FBottomItem<>OldBottomItem then
6403      RaiseGDBException('FBottomItem<>OldBottomItem');
6404  end;
6405end;
6406
6407procedure TCustomTreeView.WriteDebugReport(const Prefix: string; AllNodes: boolean);
6408begin
6409  DbgOut('%s%s.WriteDebugReport Self=%p', [Prefix, ClassName, Pointer(Self)]);
6410  ConsistencyCheck;
6411  DebugLn('');
6412  if AllNodes then begin
6413    Items.WriteDebugReport(Prefix+'  ',true);
6414  end;
6415end;
6416
6417procedure TCustomTreeView.LockSelectionChangeEvent;
6418begin
6419  inc(FSelectionChangeEventLock);
6420end;
6421
6422procedure TCustomTreeView.UnlockSelectionChangeEvent;
6423begin
6424  dec(FSelectionChangeEventLock);
6425  if FSelectionChangeEventLock<0 then
6426    RaiseGDBException('TCustomTreeView.UnlockSelectionChangeEvent');
6427  if (FSelectionChangeEventLock=0) and (tvsSelectionChanged in FStates) then
6428    InternalSelectionChanged;
6429end;
6430
6431function TCustomTreeView.GetFirstMultiSelected: TTreeNode;
6432begin
6433  Result := Items.FFirstMultiSelected;
6434end;
6435
6436function TCustomTreeView.GetLastMultiSelected: TTreeNode;
6437begin
6438  Result := Items.FLastMultiSelected;
6439end;
6440
6441procedure TCustomTreeView.Select(Node: TTreeNode; ShiftState: TShiftState = []);
6442begin
6443  if (tvoAllowMultiSelect in FOptions) and AllowMultiSelectWithCtrl(ShiftState) then
6444    Node.Selected := True
6445  else begin
6446    ClearSelection;
6447    Selected := Node;
6448    if (tvoAllowMultiSelect in FOptions) then
6449      Node.Selected := True;
6450  end;
6451end;
6452
6453procedure TCustomTreeView.Select(const Nodes: array of TTreeNode);
6454var
6455  I: Integer;
6456begin
6457  ClearSelection;
6458  if Length(Nodes)>0 then begin
6459    Selected := Nodes[0];
6460    if tvoAllowMultiSelect in FOptions then
6461      for I := Low(Nodes) to High(Nodes) do
6462        Nodes[I].Selected := True;
6463  end;
6464end;
6465
6466procedure TCustomTreeView.Select(Nodes: TList);
6467var
6468  I: Integer;
6469begin
6470  ClearSelection;
6471  if Nodes.Count>0 then begin
6472    Selected := TTreeNode(Nodes[0]);
6473    if tvoAllowMultiSelect in FOptions then
6474      for I := 0 to Nodes.Count - 1 do
6475        TTreeNode(Nodes[I]).Selected := True;
6476  end;
6477end;
6478
6479function TCustomTreeView.SelectionVisible: boolean;
6480var
6481  ANode: TTreeNode;
6482begin
6483  ANode:=GetFirstMultiSelected;
6484  if (ANode<>nil) and (ANode.GetNextMultiSelected<>nil) then begin
6485    // 2 or more elements => a real multi selection =>
6486    // is visible if even one of its nodes is partly visible
6487    while (ANode<>nil) do begin
6488      if ANode.IsVisible then begin
6489        Result:=true;
6490        exit;
6491      end;
6492      ANode:=ANode.GetNextMultiSelected;
6493    end;
6494    Result:=false;
6495  end else begin
6496    if ANode=nil then
6497      ANode:=Selected;
6498    Result:=(ANode<>nil) and (ANode.IsFullHeightVisible);
6499  end;
6500end;
6501
6502procedure TCustomTreeView.MakeSelectionVisible;
6503var
6504  ANode: TTreeNode;
6505begin
6506  if SelectionVisible then exit;
6507  ANode:=GetFirstMultiSelected;
6508  if (ANode=nil) then
6509    ANode:=Selected;
6510  if ANode=nil then exit;
6511  ANode.MakeVisible;
6512end;
6513
6514procedure TCustomTreeView.ClearInvisibleSelection;
6515var
6516  ANode: TTreeNode;
6517begin
6518  if tvoAllowMultiSelect in FOptions then begin
6519    Items.ClearMultiSelection(True);       // Now clears all multi-selected
6520  end
6521  else begin
6522    ANode := Selected;            // Clear a single selection only if not visible
6523    if Assigned(ANode) and not ANode.Visible then
6524      ANode.Selected:=False;       // Selected := nil;
6525  end;
6526end;
6527
6528procedure TCustomTreeView.MoveToNextNode(ASelect: Boolean);
6529var
6530  ANode: TTreeNode;
6531begin
6532  if tvoAllowMultiSelect in FOptions then
6533    ANode := FTreeNodes.FLastMultiSelected
6534  else
6535    ANode := Selected;
6536  if ANode <> nil then
6537    ANode := ANode.GetNextVisible
6538  else
6539    ANode := FTreeNodes.GetFirstVisibleNode;
6540  if ANode <> nil then
6541    MoveSelection(ANode, ASelect);
6542end;
6543
6544procedure TCustomTreeView.MoveToPrevNode(ASelect: Boolean);
6545var
6546  ANode: TTreeNode;
6547begin
6548  if tvoAllowMultiSelect in FOptions then
6549    ANode := FTreeNodes.FLastMultiSelected
6550  else
6551    ANode := Selected;
6552  if ANode <> nil then
6553    ANode := ANode.GetPrevVisible
6554  else
6555    ANode := Items.GetFirstVisibleNode;
6556  if ANode <> nil then
6557    MoveSelection(ANode, ASelect);
6558end;
6559
6560function TCustomTreeView.StoreCurrentSelection: TStringList;
6561var
6562  ANode: TTreeNode;
6563begin
6564  Result:=TStringList.Create;
6565  ANode:=Selected;
6566  while ANode<>nil do begin
6567    Result.Insert(0,ANode.Text);
6568    ANode:=ANode.Parent;
6569  end;
6570end;
6571
6572procedure TCustomTreeView.ApplyStoredSelection(ASelection: TStringList; FreeList: boolean);
6573var
6574  ANode: TTreeNode;
6575  CurText: string;
6576begin
6577  ANode:=nil;
6578  while ASelection.Count>0 do begin
6579    CurText:=ASelection[0];
6580    if ANode=nil then
6581      ANode:=Items.GetFirstNode
6582    else
6583      ANode:=ANode.GetFirstChild;
6584    while (ANode<>nil) and (ANode.Text<>CurText) do
6585      ANode:=ANode.GetNextSibling;
6586    if ANode=nil then break;
6587    ASelection.Delete(0);
6588  end;
6589  if ANode<>nil then
6590    Selected:=ANode;
6591  if FreeList then
6592    ASelection.Free;
6593end;
6594
6595// back to comctrls.pp
6596