mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-30 19:03:41 +02:00
6966 lines
189 KiB
PHP
6966 lines
189 KiB
PHP
{%MainUnit ../comctrls.pp}
|
|
|
|
{******************************************************************************
|
|
TTreeView
|
|
******************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
|
|
*****************************************************************************
|
|
This file is part of the Lazarus Component Library (LCL)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Abstract:
|
|
TTreeView for LCL
|
|
}
|
|
|
|
|
|
{ $DEFINE TREEVIEW_DEBUG}
|
|
|
|
const
|
|
TTreeNodeWithPointerStreamVersion : word = 1;
|
|
TTreeNodeStreamVersion : word = 2;
|
|
TVAutoHeightString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789jgpq|\()^';
|
|
MinNodeCapacity = 10;
|
|
|
|
function CompareExpandedNodes(Data1, Data2: Pointer): integer;
|
|
var
|
|
Node1: TTreeNodeExpandedState;
|
|
Node2: TTreeNodeExpandedState;
|
|
begin
|
|
Node1:=TTreeNodeExpandedState(Data1);
|
|
Node2:=TTreeNodeExpandedState(Data2);
|
|
Result:=AnsiCompareText(Node1.NodeText,Node2.NodeText);
|
|
end;
|
|
|
|
function CompareTextWithExpandedNode(Key, Data: Pointer): integer;
|
|
var
|
|
NodeText: String;
|
|
Node: TTreeNodeExpandedState;
|
|
begin
|
|
NodeText:=String(Key);
|
|
Node:=TTreeNodeExpandedState(Data);
|
|
Result:=AnsiCompareText(NodeText,Node.NodeText);
|
|
end;
|
|
|
|
procedure TreeViewError(const Msg: string);
|
|
begin
|
|
raise ETreeViewError.Create(Msg);
|
|
end;
|
|
|
|
procedure TreeNodeError(const Msg: string);
|
|
begin
|
|
raise ETreeNodeError.Create(Msg);
|
|
end;
|
|
|
|
procedure TreeNodeErrorFmt(const Msg: string; Format: array of const);
|
|
begin
|
|
raise ETreeNodeError.CreateFmt(Msg, Format);
|
|
end;
|
|
|
|
function IndexOfNodeAtTop(NodeArray: TTreeNodeArray; Count, y: integer): integer;
|
|
// NodeArray must be sorted via Top
|
|
// returns index of Node with Node.Top <= y < Node[+1].Top
|
|
var
|
|
l, m, r, VisibleCount: integer;
|
|
VisibleNodesAlloc: Boolean;
|
|
VisibleNodes: TTreeNodeArray;
|
|
begin
|
|
if (Count = 0) or (NodeArray = nil) then
|
|
Exit(-1);
|
|
// Count the visible nodes
|
|
VisibleCount := 0;
|
|
VisibleNodesAlloc := False;
|
|
for l := 0 to Count-1 do
|
|
if NodeArray[l].Visible then
|
|
Inc(VisibleCount);
|
|
try
|
|
// Make a temporary array of visible nodes if there are hidden nodes
|
|
if VisibleCount < Count then begin
|
|
GetMem(VisibleNodes,SizeOf(Pointer)*VisibleCount);
|
|
m := 0;
|
|
for l := 0 to Count-1 do
|
|
if NodeArray[l].Visible then begin
|
|
VisibleNodes[m] := NodeArray[l];
|
|
Inc(m);
|
|
end;
|
|
Count := VisibleCount;
|
|
VisibleNodesAlloc := True;
|
|
end
|
|
else
|
|
VisibleNodes := NodeArray;
|
|
// Binary search for the Y coordinate
|
|
l := 0;
|
|
r := Count - 1;
|
|
while (l <= r) do
|
|
begin
|
|
m := (l + r) shr 1;
|
|
//DebugLn(':0 [IndexOfNodeAtTop] m=',m,' y=',y,' ',NodeArray[m].Text,' NodeArray[m].Top=',NodeArray[m].Top,' NodeArray[m].BottomExpanded=',NodeArray[m].BottomExpanded);
|
|
if VisibleNodes[m].Top > y then
|
|
r := m - 1
|
|
else if VisibleNodes[m].BottomExpanded <= y then
|
|
l := m + 1
|
|
else
|
|
Exit(VisibleNodes[m].Index);
|
|
end;
|
|
Result := -1;
|
|
finally
|
|
if VisibleNodesAlloc then
|
|
Freemem(VisibleNodes);
|
|
end;
|
|
end;
|
|
|
|
// procedure for sorting a TTreeNodeArray
|
|
procedure Sort(Nodes: TTreeNodeArray; Count: integer;
|
|
SortProc: TTreeNodeCompare; UpdateIndex: Boolean);
|
|
// Sorts the nodes using merge sort and updates the sibling links
|
|
var
|
|
Buffer: TTreeNodeArray;
|
|
i: Integer;
|
|
|
|
procedure MergeNodeArrays(Pos1, Pos2, Pos3: integer);
|
|
// merge two sorted arrays (result is in Src)
|
|
// the first array ranges Pos1..Pos2-1, the second ranges Pos2..Pos3
|
|
var Src1Pos,Src2Pos,DestPos,cmp,a:integer;
|
|
begin
|
|
if (Pos1>=Pos2) or (Pos2>Pos3) then exit;
|
|
Src1Pos:=Pos2-1;
|
|
Src2Pos:=Pos3;
|
|
DestPos:=Pos3;
|
|
while (Src2Pos>=Pos2) and (Src1Pos>=Pos1) do begin
|
|
cmp:=SortProc(Nodes[Src1Pos],Nodes[Src2Pos]);
|
|
if cmp>0 then begin
|
|
Buffer[DestPos]:=Nodes[Src1Pos];
|
|
dec(Src1Pos);
|
|
end else begin
|
|
Buffer[DestPos]:=Nodes[Src2Pos];
|
|
dec(Src2Pos);
|
|
end;
|
|
dec(DestPos);
|
|
end;
|
|
while Src2Pos>=Pos2 do begin
|
|
Buffer[DestPos]:=Nodes[Src2Pos];
|
|
dec(Src2Pos);
|
|
dec(DestPos);
|
|
end;
|
|
for a:=DestPos+1 to Pos3 do
|
|
Nodes[a]:=Buffer[a];
|
|
end;
|
|
|
|
procedure MergeSort(StartPos, EndPos: integer);
|
|
// sort Src from Position StartPos to EndPos (both included)
|
|
var cmp,mid:integer;
|
|
begin
|
|
if StartPos>=EndPos then begin
|
|
// sort one element -> very easy :)
|
|
end else if StartPos+1=EndPos then begin
|
|
// sort two elements -> quite easy :)
|
|
cmp:=SortProc(Nodes[StartPos],Nodes[EndPos]);
|
|
if cmp>0 then begin
|
|
Buffer[StartPos]:=Nodes[StartPos];
|
|
Nodes[StartPos]:=Nodes[EndPos];
|
|
Nodes[EndPos]:=Buffer[StartPos];
|
|
end;
|
|
end else begin
|
|
// sort more than two elements -> Mergesort
|
|
mid:=(StartPos+EndPos) shr 1;
|
|
MergeSort(StartPos,mid);
|
|
MergeSort(mid+1,EndPos);
|
|
MergeNodeArrays(StartPos,mid+1,EndPos);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if Count>0 then begin
|
|
Buffer := GetMem(SizeOf(Pointer)*Count);
|
|
MergeSort(0,Count-1);
|
|
FreeMem(Buffer);
|
|
// update sibling links
|
|
Nodes[0].FPrevBrother := nil;
|
|
Nodes[Count-1].FNextBrother := nil;
|
|
if UpdateIndex then Nodes[0].FIndex:=0;
|
|
for i:= 1 to Count-1 do begin
|
|
Nodes[i].FPrevBrother := Nodes[i-1];
|
|
Nodes[i-1].FNextBrother := Nodes[i];
|
|
if UpdateIndex then Nodes[i].FIndex:=i;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TTreeNodeExpandedState }
|
|
|
|
function TTreeNodeExpandedState.DefaultGetNodeText(Node: TTreeNode): string;
|
|
begin
|
|
Result:=Node.Text;
|
|
end;
|
|
|
|
constructor TTreeNodeExpandedState.Create(FirstTreeNode: TTreeNode;
|
|
const GetNodeTextEvent: TTVGetNodeText);
|
|
begin
|
|
if GetNodeTextEvent<>nil then
|
|
FOnGetNodeText:=GetNodeTextEvent
|
|
else
|
|
FOnGetNodeText:=@DefaultGetNodeText;
|
|
CreateChildNodes(FirstTreeNode);
|
|
end;
|
|
|
|
constructor TTreeNodeExpandedState.Create(TreeView: TCustomTreeView;
|
|
const GetNodeTextEvent: TTVGetNodeText);
|
|
begin
|
|
if GetNodeTextEvent<>nil then
|
|
FOnGetNodeText:=GetNodeTextEvent
|
|
else
|
|
FOnGetNodeText:=@DefaultGetNodeText;
|
|
CreateChildNodes(TreeView.Items.GetFirstNode);
|
|
end;
|
|
|
|
destructor TTreeNodeExpandedState.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TTreeNodeExpandedState.Clear;
|
|
begin
|
|
if Children<>nil then begin
|
|
Children.FreeAndClear;
|
|
FreeThenNil(Children);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodeExpandedState.CreateChildNodes(FirstTreeNode: TTreeNode);
|
|
var
|
|
ChildNode: TTreeNode;
|
|
NewExpandedNode: TTreeNodeExpandedState;
|
|
begin
|
|
Clear;
|
|
if (FirstTreeNode=nil) then exit;
|
|
if (FirstTreeNode.Parent<>nil) then
|
|
NodeText:=OnGetNodeText(FirstTreeNode.Parent)
|
|
else
|
|
NodeText:='';
|
|
ChildNode:=FirstTreeNode;
|
|
while ChildNode<>nil do begin
|
|
if ChildNode.Expanded then begin
|
|
if Children=nil then Children:=TAvlTree.Create(@CompareExpandedNodes);
|
|
NewExpandedNode:=TTreeNodeExpandedState.Create(ChildNode.GetFirstChild,OnGetNodeText);
|
|
if ChildNode.GetFirstChild=nil then
|
|
NewExpandedNode.NodeText:=OnGetNodeText(ChildNode);
|
|
Children.Add(NewExpandedNode);
|
|
end;
|
|
ChildNode:=ChildNode.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodeExpandedState.Apply(FirstTreeNode: TTreeNode; CollapseToo: boolean);
|
|
var
|
|
ChildNode: TTreeNode;
|
|
ANode: TAvlTreeNode;
|
|
ChildNodeText: String;
|
|
begin
|
|
if Children=nil then exit;
|
|
ChildNode:=FirstTreeNode;
|
|
while ChildNode<>nil do begin
|
|
ChildNodeText:=OnGetNodeText(ChildNode);
|
|
ANode:=Children.FindKey(PChar(ChildNodeText),@CompareTextWithExpandedNode);
|
|
if ChildNodeText='' then
|
|
debugln(['TTreeNodeExpandedState.Apply ',ChildNode.GetTextPath,' ChildNodeText="',ChildNodeText,'"']);
|
|
if ANode<>nil then
|
|
ChildNode.Expanded:=true
|
|
else if CollapseToo then
|
|
ChildNode.Expanded:=false;
|
|
if ANode<>nil then
|
|
TTreeNodeExpandedState(ANode.Data).Apply(ChildNode.GetFirstChild,CollapseToo);
|
|
ChildNode:=ChildNode.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodeExpandedState.Apply(TreeView: TCustomTreeView; CollapseToo: boolean);
|
|
begin
|
|
Apply(TreeView.Items.GetFirstNode,CollapseToo);
|
|
end;
|
|
|
|
{ TTreeNode }
|
|
|
|
function TTreeNode.DefaultTreeViewSort(Node1, Node2: TTreeNode): Integer;
|
|
begin
|
|
if (Node1.TreeView<>nil) and Assigned(Node1.TreeView.OnCompare) then begin
|
|
Result:=0;
|
|
Node1.TreeView.OnCompare(Node1.TreeView,Node1, Node2, Result);
|
|
end else
|
|
{$IFDEF UNIX}
|
|
Result := CompareStr(Node1.Text, Node2.Text);
|
|
{$ELSE}
|
|
Result := AnsiCompareStr(Node1.Text,Node2.Text);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TTreeNode.Create(AnOwner: TTreeNodes);
|
|
begin
|
|
inherited Create;
|
|
FNodeEffect := gdeNormal;
|
|
FImageIndex := -1;
|
|
FOverlayIndex := -1;
|
|
FSelectedIndex := -1;
|
|
FStateIndex := -1;
|
|
FStates := [nsVisible, nsEnabled];
|
|
FOwner := AnOwner;
|
|
FSubTreeCount := 1;
|
|
FIndex := -1;
|
|
end;
|
|
|
|
destructor TTreeNode.Destroy;
|
|
var
|
|
lOwnerAccessibleObject, lAccessibleObject: TLazAccessibleObject;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DebugLn('[TTreeNode.Destroy] Self=',DbgS(Self),' Self.Text=',Text);
|
|
{$ENDIF}
|
|
Include(FStates,nsDeleting);
|
|
|
|
// we must trigger TCustomTreeView.OnDeletion event before
|
|
// unbinding. See issue #17832.
|
|
if Assigned(Owner) and Assigned(Owner.Owner) then
|
|
begin
|
|
Owner.Owner.Delete(Self);
|
|
Include(Owner.Owner.FStates, tvsScrollbarChanged);;
|
|
Owner.Owner.UpdateScrollbars;
|
|
end;
|
|
|
|
// Remove the accessibility object too
|
|
if Assigned(Owner) and Assigned(Owner.Owner) then
|
|
begin
|
|
lOwnerAccessibleObject := Owner.Owner.GetAccessibleObject();
|
|
if lOwnerAccessibleObject<>nil then
|
|
begin
|
|
lAccessibleObject := lOwnerAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
|
|
if lAccessibleObject <> nil then
|
|
lOwnerAccessibleObject.RemoveChildAccessibleObject(lAccessibleObject);
|
|
end;
|
|
end;
|
|
|
|
// delete children
|
|
HasChildren := false;
|
|
// unbind all references
|
|
Unbind;
|
|
if Assigned(Owner) then begin
|
|
if FStates * [nsSelected, nsMultiSelected] <> [] then
|
|
Owner.FSelection.Remove(Self);
|
|
end;
|
|
|
|
Data := nil;
|
|
// free data
|
|
if Assigned(FItems) then
|
|
begin
|
|
FreeMem(FItems);
|
|
FItems := nil;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TTreeNode.GetHandle: TLCLHandle;
|
|
begin
|
|
if TreeView<>nil then
|
|
Result := TreeView.Handle
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TTreeNode.GetParentNodeOfAbsoluteLevel(
|
|
TheAbsoluteLevel: integer): TTreeNode;
|
|
var
|
|
i: integer;
|
|
l: LongInt;
|
|
begin
|
|
l:=Level;
|
|
if (TheAbsoluteLevel > l) or (TheAbsoluteLevel < 0) then
|
|
Result := nil
|
|
else
|
|
begin
|
|
Result := Self;
|
|
for i := TheAbsoluteLevel to l-1 do
|
|
Result := Result.Parent;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetTreeNodes: TTreeNodes;
|
|
begin
|
|
if Owner is TTreeNodes then
|
|
Result:=TTreeNodes(Owner)
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetTreeView: TCustomTreeView;
|
|
begin
|
|
if Owner <> nil then
|
|
Result := Owner.Owner
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetTop: integer;
|
|
begin
|
|
if TreeView <> nil then
|
|
TreeView.UpdateAllTops;
|
|
Result := FTop;
|
|
end;
|
|
|
|
function TTreeNode.GetVisible: Boolean;
|
|
begin
|
|
Result:=nsVisible in FStates;
|
|
end;
|
|
|
|
function TTreeNode.GetEnabled: Boolean;
|
|
begin
|
|
Result:=nsEnabled in FStates;
|
|
end;
|
|
|
|
function TTreeNode.HasStates(NodeStates: TNodeStates): Boolean;
|
|
begin
|
|
Result:=FStates*NodeStates=NodeStates;
|
|
end;
|
|
|
|
function TTreeNode.HasAsParent(AValue: TTreeNode): Boolean;
|
|
begin
|
|
if AValue<>nil then begin
|
|
if Parent=nil then Result := False
|
|
else if Parent=AValue then Result := True
|
|
else Result := Parent.HasAsParent(AValue);
|
|
end
|
|
else Result := True;
|
|
end;
|
|
|
|
procedure TTreeNode.SetText(const S: string);
|
|
var
|
|
lSelfAX: TLazAccessibleObject;
|
|
begin
|
|
if S=FText then exit;
|
|
FText := S;
|
|
if TreeView=nil then exit;
|
|
Include(TreeView.FStates,tvsMaxRightNeedsUpdate);
|
|
if (TreeView.SortType in [stText, stBoth])
|
|
and (nsBound in FStates) then begin
|
|
if (Parent <> nil) then Parent.AlphaSort
|
|
else TreeView.AlphaSort;
|
|
end;
|
|
Update;
|
|
Changed(ncTextChanged);
|
|
// Update accessibility information
|
|
lSelfAX := TreeView.GetAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
|
|
if lSelfAX <> nil then
|
|
lSelfAX.AccessibleValue := S;
|
|
end;
|
|
|
|
procedure TTreeNode.SetData(AValue: Pointer);
|
|
begin
|
|
if FData=AValue then exit;
|
|
FData := AValue;
|
|
if (TreeView<>nil)
|
|
and (TreeView.SortType in [stData, stBoth]) and Assigned(TreeView.OnCompare)
|
|
and (not Deleting)
|
|
and (nsBound in FStates) then
|
|
begin
|
|
if Parent <> nil then
|
|
Parent.AlphaSort
|
|
else
|
|
TreeView.AlphaSort;
|
|
end;
|
|
Changed(ncDataChanged);
|
|
end;
|
|
|
|
function TTreeNode.GetState(NodeState: TNodeState): Boolean;
|
|
begin
|
|
Result:=NodeState in FStates;
|
|
end;
|
|
|
|
procedure TTreeNode.SetHeight(AValue: integer);
|
|
begin
|
|
if AValue<0 then AValue:=0;
|
|
if AValue=FHeight then exit;
|
|
FHeight:=AValue;
|
|
if TreeView<>nil then
|
|
TreeView.FStates:=TreeView.FStates+[tvsScrollbarChanged,tvsTopsNeedsUpdate];
|
|
Update;
|
|
Changed(ncHeightChanged);
|
|
end;
|
|
|
|
procedure TTreeNode.SetImageEffect(AValue: TGraphicsDrawEffect);
|
|
begin
|
|
if FNodeEffect=AValue then exit;
|
|
FNodeEffect := AValue;
|
|
Update;
|
|
Changed(ncImageEffect);
|
|
end;
|
|
|
|
procedure TTreeNode.SetImageIndex(AValue: TImageIndex);
|
|
begin
|
|
if FImageIndex=AValue then exit;
|
|
FImageIndex := AValue;
|
|
Update;
|
|
Changed(ncImageIndex);
|
|
end;
|
|
|
|
procedure TTreeNode.SetIndex(const AValue: Integer);
|
|
|
|
procedure RaiseMissingTreeNodes;
|
|
begin
|
|
TreeViewError('TTreeNode.SetIndex missing Owner');
|
|
end;
|
|
|
|
var
|
|
OldIndex: LongInt;
|
|
begin
|
|
OldIndex:=Index;
|
|
if OldIndex=AValue then exit;
|
|
if Parent=nil then begin
|
|
// moving a top level node
|
|
if Owner=nil then RaiseMissingTreeNodes;
|
|
if AValue=0 then
|
|
MoveTo(Owner.GetFirstNode,naInsert)
|
|
else if AValue<OldIndex then
|
|
MoveTo(Owner.TopLvlItems[AValue-1],naInsertBehind)
|
|
else
|
|
MoveTo(Owner.TopLvlItems[AValue],naInsertBehind);
|
|
end else begin
|
|
// moving a normal node
|
|
if AValue=0 then
|
|
MoveTo(Parent.GetFirstChild,naInsert)
|
|
else if AValue<OldIndex then
|
|
MoveTo(Parent.Items[AValue-1],naInsertBehind)
|
|
else
|
|
MoveTo(Parent.Items[AValue],naInsertBehind);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.SetSelectedIndex(AValue: Integer);
|
|
begin
|
|
if FSelectedIndex = AValue then exit;
|
|
FSelectedIndex := AValue;
|
|
Update;
|
|
Changed(ncSelectedIndex);
|
|
end;
|
|
|
|
procedure TTreeNode.SetVisible(const AValue: Boolean);
|
|
begin
|
|
if Visible = AValue then exit;
|
|
if AValue then
|
|
Include(FStates,nsVisible)
|
|
else
|
|
Exclude(FStates,nsVisible);
|
|
Selected := False;
|
|
MultiSelected := False;
|
|
if TreeView<>nil then
|
|
TreeView.FStates:=TreeView.FStates+[tvsScrollbarChanged,tvsTopsNeedsUpdate,
|
|
tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
|
|
tvsMaxLvlNeedsUpdate,tvsMaxRightNeedsUpdate];
|
|
Update;
|
|
Changed(ncVisibility);
|
|
end;
|
|
|
|
procedure TTreeNode.SetEnabled(const AValue: Boolean);
|
|
begin
|
|
if Enabled = AValue then exit;
|
|
if AValue then
|
|
Include(FStates,nsEnabled)
|
|
else
|
|
Exclude(FStates,nsEnabled);
|
|
Selected := False;
|
|
MultiSelected := False;
|
|
{
|
|
if TreeView<>nil then
|
|
TreeView.FStates:=TreeView.FStates+[tvsTopsNeedsUpdate,
|
|
tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate
|
|
];
|
|
}
|
|
Update;
|
|
Changed(ncEnablement);
|
|
end;
|
|
|
|
procedure TTreeNode.SetOverlayIndex(AValue: Integer);
|
|
begin
|
|
if FOverlayIndex = AValue then exit;
|
|
FOverlayIndex := AValue;
|
|
Update;
|
|
Changed(ncOverlayIndex)
|
|
end;
|
|
|
|
procedure TTreeNode.SetStateIndex(AValue: Integer);
|
|
begin
|
|
if FStateIndex = AValue then exit;
|
|
FStateIndex := AValue;
|
|
Update;
|
|
Changed(ncStateIndex);
|
|
end;
|
|
|
|
function TTreeNode.ParentsExpandedVisible(aEnabledOnly: boolean = false): Boolean;
|
|
var
|
|
lNode: TTreeNode;
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsExpanded,nsVisible,nsEnabled]
|
|
else lStates := [nsExpanded,nsVisible];
|
|
|
|
lNode:=Parent;
|
|
while lNode<>nil do begin
|
|
if not lNode.HasStates(lStates) then
|
|
exit(false);
|
|
lNode:=lNode.Parent;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TTreeNode.BindToMultiSelected;
|
|
var
|
|
TheTreeNodes: TTreeNodes;
|
|
CurNode: TTreeNode;
|
|
begin
|
|
TheTreeNodes:=TreeNodes;
|
|
if TheTreeNodes=nil then exit;
|
|
|
|
// Get the first selected node of the tree
|
|
CurNode := TheTreeNodes.FFirstMultiSelected;
|
|
|
|
// Initialize self unbinded
|
|
Self.FPrevMultiSelected := nil;
|
|
Self.FNextMultiSelected := nil;
|
|
|
|
// If there isn't any selected node, set self as first
|
|
if CurNode = nil then
|
|
begin
|
|
TheTreeNodes.FFirstMultiSelected := Self;
|
|
TheTreeNodes.FStartMultiSelected := Self;
|
|
end
|
|
else
|
|
begin
|
|
|
|
// if last selected node was the previous one
|
|
if (TheTreeNodes.FLastMultiSelected.AbsoluteIndex+1=Self.AbsoluteIndex) and (TheTreeNodes.FLastMultiSelected.FNextMultiSelected=nil) then
|
|
begin
|
|
TheTreeNodes.FLastMultiSelected.FNextMultiSelected := Self;
|
|
Self.FPrevMultiSelected := TheTreeNodes.FLastMultiSelected;
|
|
end
|
|
else
|
|
begin
|
|
// if last selected node was the next one
|
|
if (TheTreeNodes.FLastMultiSelected.AbsoluteIndex=Self.AbsoluteIndex+1) and (TheTreeNodes.FLastMultiSelected.FPrevMultiSelected=nil) then
|
|
begin
|
|
TheTreeNodes.FLastMultiSelected.FPrevMultiSelected := Self;
|
|
Self.FNextMultiSelected := TheTreeNodes.FLastMultiSelected;
|
|
TheTreeNodes.FFirstMultiSelected := Self
|
|
end
|
|
else
|
|
begin
|
|
|
|
// Scan linked list of selected nodes until one has a lower absolute index or we reach the end
|
|
While (CurNode.GetNextMultiSelected<>Nil) and (CurNode.AbsoluteIndex<Self.AbsoluteIndex) do
|
|
CurNode := CurNode.GetNextMultiSelected;
|
|
|
|
// last of the list
|
|
if CurNode.AbsoluteIndex < Self.AbsoluteIndex then
|
|
begin
|
|
CurNode.FNextMultiSelected := Self;
|
|
Self.FPrevMultiSelected := CurNode;
|
|
end
|
|
else
|
|
// insert between two nodes
|
|
begin
|
|
Self.FPrevMultiSelected := CurNode.FPrevMultiSelected;
|
|
Self.FNextMultiSelected := CurNode;
|
|
if CurNode.FPrevMultiSelected <> nil then
|
|
CurNode.FPrevMultiSelected.FNextMultiSelected := Self;
|
|
CurNode.FPrevMultiSelected := Self;
|
|
end;
|
|
// Set self as head of the list if needed
|
|
if Self.FPrevMultiSelected = nil then
|
|
TheTreeNodes.FFirstMultiSelected := Self;
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
// Set self as last selected node
|
|
TheTreeNodes.FLastMultiSelected := Self;
|
|
end;
|
|
|
|
function TTreeNode.CompareCount(CompareMe: Integer): Boolean;
|
|
Begin
|
|
Result:=(CompareMe=Count);
|
|
end;
|
|
|
|
function TTreeNode.DoCanExpand(ExpandIt: Boolean): Boolean;
|
|
begin
|
|
Result := False;
|
|
if (TreeView<>nil) and HasChildren then begin
|
|
if ExpandIt then
|
|
Result := TreeView.CanExpand(Self)
|
|
else
|
|
Result := TreeView.CanCollapse(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.DoExpand(ExpandIt: Boolean);
|
|
begin
|
|
//DebugLn('[TTreeNode.DoExpand] Self=',DbgS(Self),' Text=',Text,
|
|
//' HasChildren=',HasChildren,' ExpandIt=',ExpandIt,' Expanded=',Expanded);
|
|
if HasChildren and (Expanded<>ExpandIt) then begin
|
|
if ExpandIt then
|
|
Include(FStates,nsExpanded)
|
|
else begin
|
|
Exclude(FStates,nsExpanded);
|
|
if (not Owner.KeepCollapsedNodes) then begin
|
|
while GetLastChild<>nil do
|
|
GetLastChild.Free;
|
|
end;
|
|
end;
|
|
if TreeView<>nil then begin
|
|
TreeView.FStates:=(TreeView.FStates+[tvsTopsNeedsUpdate,
|
|
tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate,
|
|
tvsScrollbarChanged,tvsMaxRightNeedsUpdate]);
|
|
Update;
|
|
if ExpandIt then
|
|
TreeView.Expand(Self)
|
|
else
|
|
TreeView.Collapse(Self);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.ExpandItem(ExpandIt, Recurse: Boolean);
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if TreeView <> nil then
|
|
TreeView.BeginUpdate;
|
|
try
|
|
if Recurse then
|
|
begin
|
|
ExpandItem(ExpandIt, False);
|
|
ANode := GetFirstChild;
|
|
while ANode <> nil do
|
|
begin
|
|
ANode.ExpandItem(ExpandIt, true);
|
|
ANode := ANode.FNextBrother;
|
|
end;
|
|
end else begin
|
|
if TreeView <> nil then
|
|
Include(TreeView.FStates, tvsManualNotify);
|
|
try
|
|
if DoCanExpand(ExpandIt) then
|
|
DoExpand(ExpandIt);
|
|
finally
|
|
if TreeView <> nil then
|
|
Exclude(TreeView.FStates, tvsManualNotify);
|
|
end;
|
|
end;
|
|
finally
|
|
if TreeView <> nil then
|
|
TreeView.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.Expand(Recurse: Boolean);
|
|
begin
|
|
ExpandItem(True, Recurse);
|
|
end;
|
|
|
|
procedure TTreeNode.ExpandParents;
|
|
var ANode: TTreeNode;
|
|
begin
|
|
ANode:=Parent;
|
|
while ANode<>nil do begin
|
|
ANode.Expanded:=true;
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.FreeAllNodeData;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
FreeAndNil(FData);
|
|
for i:=0 to Count-1 do
|
|
Items[i].FreeAllNodeData;
|
|
end;
|
|
|
|
procedure TTreeNode.Collapse(Recurse: Boolean);
|
|
begin
|
|
ExpandItem(False, Recurse);
|
|
end;
|
|
|
|
function TTreeNode.GetExpanded: Boolean;
|
|
begin
|
|
Result := GetState(nsExpanded);
|
|
end;
|
|
|
|
procedure TTreeNode.SetExpanded(AValue: Boolean);
|
|
begin
|
|
if AValue=Expanded then exit;
|
|
if AValue then
|
|
Expand(False)
|
|
else
|
|
Collapse(False);
|
|
end;
|
|
|
|
function TTreeNode.GetSelected: Boolean;
|
|
begin
|
|
Result := GetState(nsSelected);
|
|
if (not Result) and (tvoAllowMultiSelect in TreeView.Options) then
|
|
Result := GetState(nsMultiSelected);
|
|
end;
|
|
|
|
procedure TTreeNode.SetSelected(AValue: Boolean);
|
|
var
|
|
MultiSelect: Boolean;
|
|
TV: TCustomTreeView;
|
|
begin
|
|
if not Enabled then AValue:=False;
|
|
if AValue=GetSelected then exit;
|
|
TV:=TreeView;
|
|
if AValue then
|
|
begin
|
|
Include(FStates,nsSelected);
|
|
if (TV<>nil) then
|
|
begin
|
|
TV.EndEditing(true);
|
|
MultiSelect:=tvoAllowMultiselect in TV.Options;
|
|
|
|
if not MultiSelect and Assigned(FOwner) then
|
|
FOwner.SelectionsChanged(Self, True);
|
|
|
|
if MultiSelect then TV.LockSelectionChangeEvent;
|
|
try
|
|
TV.Selected:=Self;
|
|
Assert(TV.Selected=Self, 'Selected<>Self');
|
|
//if TV.Selected<>Self then
|
|
// Exclude(FStates,nsSelected);
|
|
if (nsSelected in FStates) and MultiSelect then
|
|
MultiSelected:=true;
|
|
finally
|
|
if MultiSelect then TV.UnlockSelectionChangeEvent;
|
|
end;
|
|
end;
|
|
end else
|
|
begin
|
|
if not MultiSelected and Assigned(FOwner) then
|
|
FOwner.SelectionsChanged(Self, False);
|
|
Exclude(FStates,nsSelected);
|
|
if (TV<>nil) and (TV.Selected=Self) then
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DebugLn('TTreeNode.SetSelected: Removing selection from Node (but it does not work): ', Text);
|
|
{$ENDIF}
|
|
// TV.EndEditing(true); // Done in TV.SetSelection
|
|
TV.Selected:=nil;
|
|
Assert(TV.Selected<>Self, 'Should not happen');
|
|
//if TV.Selected=Self then
|
|
// Include(FStates,nsSelected);
|
|
end;
|
|
end;
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.GetCut: boolean;
|
|
begin
|
|
Result := GetState(nsCut);
|
|
end;
|
|
|
|
procedure TTreeNode.SetCut(AValue: Boolean);
|
|
begin
|
|
if AValue=Cut then exit;
|
|
if AValue then
|
|
Include(FStates,nsCut)
|
|
else
|
|
Exclude(FStates,nsCut);
|
|
end;
|
|
|
|
function TTreeNode.GetDropTarget: Boolean;
|
|
begin
|
|
Result := GetState(nsDropHilited);
|
|
end;
|
|
|
|
procedure TTreeNode.SetDropTarget(AValue: Boolean);
|
|
begin
|
|
if AValue then begin
|
|
Include(FStates,nsDropHilited);
|
|
if TreeView<>nil then
|
|
TreeView.FLastDropTarget:=Self;
|
|
end else begin
|
|
Exclude(FStates,nsDropHilited);
|
|
if TreeView<>nil then
|
|
TreeView.FLastDropTarget:=nil;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetHasChildren: Boolean;
|
|
begin
|
|
if not GetState(nsValidHasChildren) then
|
|
begin
|
|
if Owner.Owner.NodeHasChildren(Self) then
|
|
Include(FStates, nsHasChildren)
|
|
else
|
|
Exclude(FStates, nsHasChildren);
|
|
Include(FStates, nsValidHasChildren);
|
|
end;
|
|
Result := GetState(nsHasChildren);
|
|
end;
|
|
|
|
procedure TTreeNode.SetFocused(AValue: Boolean);
|
|
begin
|
|
if AValue=GetFocused then exit;
|
|
if AValue then
|
|
Include(FStates,nsFocused)
|
|
else
|
|
Exclude(FStates,nsFocused);
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.Bottom: integer;
|
|
begin
|
|
Result := Top + Height;
|
|
end;
|
|
|
|
function TTreeNode.BottomExpanded: integer;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Node := GetNextVisibleSibling;
|
|
if Node <> nil then
|
|
Result := Node.Top
|
|
else begin
|
|
Node := GetLastVisibleChild;
|
|
if Expanded and (Node <> nil) then
|
|
Result := Node.BottomExpanded
|
|
else
|
|
Result := Bottom;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetFocused: Boolean;
|
|
begin
|
|
Result := GetState(nsFocused);
|
|
end;
|
|
|
|
procedure TTreeNode.SetHasChildren(AValue: Boolean);
|
|
begin
|
|
if GetState(nsValidHasChildren) and (AValue=HasChildren) then exit;
|
|
//DebugLn('[TTreeNode.SetHasChildren] Self=',DbgS(Self),' Self.Text=',Text,' AValue=',AValue);
|
|
if AValue then
|
|
Include(FStates,nsHasChildren)
|
|
else begin
|
|
while GetLastChild<>nil do
|
|
GetLastChild.Free;
|
|
Exclude(FStates,nsHasChildren)
|
|
end;
|
|
Include(FStates, nsValidHasChildren);
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.GetNextSibling: TTreeNode;
|
|
begin
|
|
Result := FNextBrother;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevSibling: TTreeNode;
|
|
begin
|
|
Result := FPrevBrother;
|
|
end;
|
|
|
|
function TTreeNode.GetNextVisible(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lFirstChild: TTreeNode;
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsExpanded,nsVisible,nsEnabled]
|
|
else lStates := [nsExpanded,nsVisible];
|
|
|
|
lFirstChild:=GetFirstVisibleChild(aEnabledOnly);
|
|
if (lFirstChild<>nil) and HasStates(lStates) then
|
|
Result:=lFirstChild
|
|
else begin
|
|
Result:=Self;
|
|
while (Result<>nil) and (Result.GetNextVisibleSibling(aEnabledOnly)=nil) do
|
|
Result:=Result.Parent;
|
|
if Result<>nil then
|
|
Result:=Result.GetNextVisibleSibling(aEnabledOnly);
|
|
end;
|
|
|
|
if Result <> nil then
|
|
if (not Result.HasStates(lStates-[nsExpanded])) or
|
|
(not ParentsExpandedVisible(aEnabledOnly)) then
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetNextVisibleSibling(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsVisible,nsEnabled]
|
|
else lStates := [nsVisible];
|
|
|
|
Result := Self;
|
|
repeat
|
|
Result := Result.GetNextSibling;
|
|
until (Result=nil) or Result.HasStates(lStates);
|
|
end;
|
|
|
|
function TTreeNode.GetPrevVisible(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lNode: TTreeNode;
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsExpanded,nsVisible,nsEnabled]
|
|
else lStates := [nsExpanded,nsVisible];
|
|
|
|
Result:=GetPrevVisibleSibling(aEnabledOnly);
|
|
if Result <> nil then begin
|
|
while Result.HasStates(lStates) do begin
|
|
lNode:=Result.GetLastVisibleChild(aEnabledOnly);
|
|
if lNode=nil then break;
|
|
Result:=lNode;
|
|
end;
|
|
end
|
|
else
|
|
Result := Parent;
|
|
|
|
if Result <> nil then
|
|
if (not Result.HasStates(lStates-[nsExpanded])) or
|
|
(not ParentsExpandedVisible(aEnabledOnly)) then
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevVisibleSibling(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsVisible,nsEnabled]
|
|
else lStates := [nsVisible];
|
|
|
|
Result := Self;
|
|
repeat
|
|
Result := Result.GetPrevSibling;
|
|
until (Result=nil) or Result.HasStates(lStates);
|
|
end;
|
|
|
|
function TTreeNode.GetPrevExpanded(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lNode: TTreeNode;
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsExpanded,nsVisible,nsEnabled]
|
|
else lStates := [nsExpanded,nsVisible];
|
|
|
|
Result:=GetPrevVisibleSibling(aEnabledOnly);
|
|
if Result <> nil then begin
|
|
while Result.HasStates(lStates) do begin
|
|
lNode:=Result.GetLastVisibleChild(aEnabledOnly);
|
|
if lNode=nil then break;
|
|
Result:=lNode;
|
|
end;
|
|
end
|
|
else
|
|
Result:=Parent;
|
|
end;
|
|
|
|
function TTreeNode.GetNextChild(AValue: TTreeNode): TTreeNode;
|
|
begin
|
|
if AValue <> nil then
|
|
Result := AValue.GetNextSibling
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetNextExpanded(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
lNode := GetFirstVisibleChild(aEnabledOnly);
|
|
if Expanded and (lNode<>nil) then
|
|
Result:=lNode
|
|
else begin
|
|
Result:=Self;
|
|
while (Result<>nil) and (Result.GetNextVisibleSibling(aEnabledOnly)=nil) do
|
|
Result:=Result.Parent;
|
|
if Result<>nil then
|
|
Result:=Result.GetNextVisibleSibling(aEnabledOnly);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetNextMultiSelected: TTreeNode;
|
|
begin
|
|
Result:=FNextMultiSelected;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevChild(AValue: TTreeNode): TTreeNode;
|
|
begin
|
|
if AValue <> nil then
|
|
Result := AValue.GetPrevSibling
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetPrevMultiSelected: TTreeNode;
|
|
begin
|
|
Result:=FPrevMultiSelected;
|
|
end;
|
|
|
|
function TTreeNode.GetFirstChild: TTreeNode;
|
|
begin
|
|
if Count>0 then
|
|
Result:=FItems[0]
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetFirstSibling: TTreeNode;
|
|
begin
|
|
if Parent<>nil then
|
|
Result:=Parent.GetFirstChild
|
|
else begin
|
|
Result:=Self;
|
|
while Result.FPrevBrother<>nil do
|
|
Result:=Result.FPrevBrother;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetFirstVisibleChild(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsVisible,nsEnabled]
|
|
else lStates := [nsVisible];
|
|
|
|
Result := GetFirstChild;
|
|
if (Result<>nil) and (not Result.HasStates(lStates)) then
|
|
Result := Result.GetNextVisibleSibling(aEnabledOnly);
|
|
end;
|
|
|
|
function TTreeNode.GetLastSibling: TTreeNode;
|
|
begin
|
|
if Parent<>nil then
|
|
Result:=Parent.GetLastChild
|
|
else begin
|
|
Result:=Self;
|
|
while Result.FNextBrother<>nil do
|
|
Result:=Result.FNextBrother;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetLastChild: TTreeNode;
|
|
begin
|
|
if Count > 0 then
|
|
Result := FItems[Count - 1]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNode.GetLastVisibleChild(aEnabledOnly: boolean = false): TTreeNode;
|
|
var
|
|
lStates: TNodeStates;
|
|
begin
|
|
if aEnabledOnly
|
|
then lStates := [nsVisible,nsEnabled]
|
|
else lStates := [nsVisible];
|
|
|
|
Result := GetLastChild;
|
|
if Assigned(Result) and (not Result.HasStates(lStates)) then begin
|
|
Result := Result.GetPrevVisible(aEnabledOnly);
|
|
if Result = Self then
|
|
Result := Nil;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetLastSubChild: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Result:=GetLastChild;
|
|
if Result<>nil then begin
|
|
Node:=Result.GetLastSubChild;
|
|
if Node<>nil then
|
|
Result:=Node;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetNext: TTreeNode;
|
|
begin
|
|
Result:=GetFirstChild;
|
|
if Result=nil then
|
|
Result:=GetNextSkipChildren;
|
|
end;
|
|
|
|
function TTreeNode.GetNextSkipChildren: TTreeNode;
|
|
begin
|
|
Result:=Self;
|
|
while (Result<>nil) and (Result.FNextBrother=nil) do
|
|
Result:=Result.Parent;
|
|
if Result<>nil then
|
|
Result:=Result.FNextBrother;
|
|
end;
|
|
|
|
function TTreeNode.GetPrev: TTreeNode;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result := GetPrevSibling;
|
|
if Result <> nil then begin
|
|
ANode := Result;
|
|
repeat
|
|
Result := ANode;
|
|
ANode := Result.GetLastChild;
|
|
until ANode = nil;
|
|
end else
|
|
Result := Parent;
|
|
end;
|
|
|
|
function TTreeNode.GetAbsoluteIndex: Integer;
|
|
// - first node has index 0
|
|
// - the first child of a node has an index one bigger than its parent
|
|
// - a node without children has an index one bigger than its previous brother
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result:=-1;
|
|
ANode:=Self;
|
|
repeat
|
|
inc(Result);
|
|
while ANode.FPrevBrother<>nil do begin
|
|
ANode:=ANode.FPrevBrother;
|
|
inc(Result,ANode.FSubTreeCount);
|
|
end;
|
|
ANode:=ANode.Parent;
|
|
until ANode=nil;
|
|
end;
|
|
|
|
function TTreeNode.GetDeleting: Boolean;
|
|
begin
|
|
Result := nsDeleting in FStates;
|
|
end;
|
|
|
|
function TTreeNode.GetHeight: integer;
|
|
begin
|
|
if FHeight <= 0 then
|
|
begin
|
|
if TreeView <> nil then
|
|
Result := TreeView.FDefItemHeight
|
|
else
|
|
Result := DefaultTreeNodeHeight;
|
|
end
|
|
else
|
|
Result := FHeight;
|
|
end;
|
|
|
|
function TTreeNode.GetIndex: Integer;
|
|
// returns number of previous siblings (nodes on same lvl with same parent)
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if self = nil then
|
|
exit(-1);
|
|
if FIndex>=0 then
|
|
exit(FIndex);
|
|
// many algorithms uses the last sibling, so we check that first for speed
|
|
if (Parent<>nil) and (Parent[Parent.Count-1]=Self) then begin
|
|
Result:=Parent.Count-1;
|
|
FIndex:=Result;
|
|
exit;
|
|
end;
|
|
// count previous siblings
|
|
Result := -1;
|
|
ANode := Self;
|
|
while ANode <> nil do begin
|
|
Inc(Result);
|
|
if ANode.FIndex>=0 then begin
|
|
inc(Result,ANode.FIndex);
|
|
break;
|
|
end;
|
|
ANode := ANode.GetPrevSibling;
|
|
end;
|
|
FIndex:=Result;
|
|
end;
|
|
|
|
function TTreeNode.GetItems(AnIndex: Integer): TTreeNode;
|
|
begin
|
|
if (AnIndex<0) or (AnIndex>=Count) then
|
|
TreeNodeErrorFmt(rsIndexOutOfBounds,[ClassName, AnIndex, Count-1]);
|
|
Result:=FItems[AnIndex];
|
|
end;
|
|
|
|
procedure TTreeNode.SetItems(AnIndex: Integer; AValue: TTreeNode);
|
|
begin
|
|
if (AnIndex<0) or (AnIndex>=Count) then
|
|
TreeNodeErrorFmt(rsIndexOutOfBounds, [ClassName, AnIndex, Count-1]);
|
|
Items[AnIndex].Assign(AValue);
|
|
end;
|
|
|
|
procedure TTreeNode.SetMultiSelected(const AValue: Boolean);
|
|
begin
|
|
if AValue=GetMultiSelected then exit;
|
|
if AValue then begin
|
|
if (Treeview<>nil) and not (tvoAllowMultiselect in TreeView.Options) then
|
|
exit;
|
|
if Assigned(FOwner) then
|
|
FOwner.SelectionsChanged(Self, True);
|
|
Include(FStates,nsMultiSelected);
|
|
if TreeNodes<>nil then BindToMultiSelected;
|
|
end else begin
|
|
if Assigned(FOwner) then
|
|
FOwner.SelectionsChanged(Self, False);
|
|
Exclude(FStates,nsMultiSelected);
|
|
if TreeNodes<>nil then UnbindFromMultiSelected;
|
|
end;
|
|
if TreeView<>nil then TreeView.InternalSelectionChanged;
|
|
Update;
|
|
end;
|
|
|
|
function TTreeNode.IndexOf(AValue: TTreeNode): Integer;
|
|
begin
|
|
if (AValue = nil) or (AValue.FParent <> Self) then
|
|
begin
|
|
Result:=-1;
|
|
exit;
|
|
end;
|
|
Result := AValue.GetIndex;
|
|
end;
|
|
|
|
function TTreeNode.IndexOfText(const NodeText: string): Integer;
|
|
begin
|
|
Result := Count - 1;
|
|
while Result >= 0 do
|
|
begin
|
|
if FItems[Result].Text = NodeText then exit;
|
|
dec(Result);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.FindNode(const NodeText: string): TTreeNode;
|
|
begin
|
|
Result:=GetFirstChild;
|
|
if (foFindIgnoresCase in FOwner.FOwner.FFindOptions) then
|
|
begin
|
|
while (Result <> nil) and not SameText(Result.Text, NodeText) do
|
|
Result := Result.GetNextSibling;
|
|
end else
|
|
begin
|
|
while (Result<>nil) and (Result.Text<>NodeText) do
|
|
Result:=Result.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetTextPath: string;
|
|
var
|
|
Node: TTreeNode;
|
|
PDelim: String;
|
|
begin
|
|
PDelim := FOwner.FOwner.FPathDelimiter;
|
|
Result := '';
|
|
Node := Self;
|
|
while Assigned(Node) do
|
|
begin
|
|
if (Result <> '') and (Node.Text <> PDelim) then
|
|
Result := PDelim + Result;
|
|
Result := Node.Text + Result;
|
|
Node := Node.Parent;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetCount: Integer;
|
|
begin
|
|
Result := FCount;
|
|
end;
|
|
|
|
procedure TTreeNode.EndEdit(Cancel: Boolean);
|
|
var
|
|
TV: TCustomTreeView;
|
|
begin
|
|
TV:=TreeView;
|
|
if TV=nil then exit;
|
|
TV.EndEditing(Cancel);
|
|
end;
|
|
|
|
procedure TTreeNode.Unbind;
|
|
// unbind from parent and neighbor siblings, but not from owner
|
|
var
|
|
OldIndex, i: integer;
|
|
HigherNode: TTreeNode;
|
|
TheTreeView: TCustomTreeView;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DebugLn('[TTreeNode.Unbind] Self=',DbgS(Self),' Self.Text=',Text);
|
|
{$ENDIF}
|
|
if not (nsBound in FStates) then exit;
|
|
Exclude(FStates,nsBound);
|
|
if Owner<>nil then dec(Owner.FCount);
|
|
|
|
// remove single select
|
|
Selected:=false;
|
|
TheTreeView:=Nil;
|
|
// invalidate caches of TreeView and if root item, remove from TreeView.Items
|
|
if Owner<>nil then begin
|
|
Owner.ClearCache;
|
|
if FParent=nil then
|
|
Owner.MoveTopLvlNode(Owner.IndexOfTopLvlItem(Self),-1,Self);
|
|
TheTreeView:=Owner.Owner;
|
|
if TheTreeView<>nil then begin
|
|
TheTreeView.FStates:=TheTreeView.FStates+[tvsMaxRightNeedsUpdate,
|
|
tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
if TheTreeView.FLastDropTarget=Self then
|
|
TheTreeView.FLastDropTarget:=nil;
|
|
if TheTreeView.FInsertMarkNode=Self then
|
|
TheTreeView.FInsertMarkNode:=nil;
|
|
end;
|
|
end;
|
|
// unmultiselect (keeping MultiSelected, but leaving multiselection list)
|
|
UnbindFromMultiSelected;
|
|
// remove from sibling list
|
|
if FPrevBrother<>nil then FPrevBrother.FNextBrother:=FNextBrother;
|
|
if FNextBrother<>nil then FNextBrother.FPrevBrother:=FPrevBrother;
|
|
FPrevBrother:=nil;
|
|
FNextBrother:=nil;
|
|
// remove from parent
|
|
if FParent<>nil then begin
|
|
// update all FSubTreeCount
|
|
HigherNode:=FParent;
|
|
while HigherNode<>nil do begin
|
|
dec(HigherNode.FSubTreeCount,FSubTreeCount);
|
|
HigherNode:=HigherNode.Parent;
|
|
end;
|
|
//if TreeNodes<>nil then Dec(TreeNodes.FCount,FSubTreeCount);
|
|
// remove from parents list
|
|
OldIndex:=FIndex;
|
|
if OldIndex<0 then
|
|
LazTracer.RaiseGDBException('');
|
|
for i:=OldIndex to FParent.FCount-2 do begin
|
|
FParent.FItems[i]:=FParent.FItems[i+1];
|
|
FParent.FItems[i].FIndex:=i;
|
|
end;
|
|
dec(FParent.FCount);
|
|
if (FParent.FCapacity>15) and (FParent.FCount<(FParent.FCapacity shr 2))
|
|
then begin
|
|
// shrink FParent.FItems
|
|
FParent.FCapacity:=FParent.FCapacity shr 1;
|
|
ReAllocMem(FParent.FItems,SizeOf(Pointer)*FParent.FCapacity);
|
|
end;
|
|
if FParent.Count=0 then begin
|
|
Assert(Assigned(TheTreeView), 'TTreeNode.Unbind: TheTreeView=Nil.');
|
|
TheTreeView.BeginUpdate;
|
|
try
|
|
FParent.Expanded:=false;
|
|
FParent.HasChildren:=false;
|
|
finally
|
|
TheTreeView.EndUpdate;
|
|
end;
|
|
end;
|
|
FParent:=nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.UnbindFromMultiSelected;
|
|
var
|
|
TheTreeNodes: TTreeNodes;
|
|
begin
|
|
TheTreeNodes:=TreeNodes;
|
|
if TheTreeNodes=nil then exit;
|
|
if TheTreeNodes.FFirstMultiSelected=Self then
|
|
TheTreeNodes.FFirstMultiSelected:=FNextMultiSelected;
|
|
// Reset last multiselected node
|
|
if TheTreeNodes.FLastMultiSelected=Self then
|
|
begin
|
|
if Self.FNextMultiSelected <> nil then
|
|
TheTreeNodes.FLastMultiSelected := Self.FNextMultiSelected
|
|
else
|
|
if Self.FPrevMultiSelected <> nil then
|
|
TheTreeNodes.FLastMultiSelected := Self.FPrevMultiSelected
|
|
else
|
|
TheTreeNodes.FLastMultiSelected := nil;
|
|
end;
|
|
if FNextMultiSelected<>nil then
|
|
FNextMultiSelected.FPrevMultiSelected:=FPrevMultiSelected;
|
|
if FPrevMultiSelected<>nil then
|
|
FPrevMultiSelected.FNextMultiSelected:=FNextMultiSelected;
|
|
FNextMultiSelected:=nil;
|
|
FPrevMultiSelected:=nil;
|
|
end;
|
|
|
|
function AddModeStr(AddMode: TAddMode): string;
|
|
begin
|
|
WriteStr(Result, AddMode);
|
|
end;
|
|
|
|
procedure TTreeNode.InternalMove(ANode: TTreeNode; AddMode: TAddMode);
|
|
{
|
|
TAddMode = (taAddFirst, taAdd, taInsert);
|
|
|
|
taAdd: add Self as last child of ANode
|
|
taAddFirst: add Self as first child of ANode
|
|
taInsert: add Self in front of ANode
|
|
}
|
|
var
|
|
HigherNode: TTreeNode;
|
|
NewIndex, NewParentItemSize, i: integer;
|
|
WasSelected: Boolean;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DbgOut('[TTreeNode.InternalMove] Self=',DbgS(Self),' Self.Text=',Text
|
|
,' ANode=',ANode<>nil,' AddMode=', AddModeStr(AddMode));
|
|
if ANode<>nil then
|
|
DbgOut(' ANode.Text=',ANode.Text);
|
|
DebugLn('');
|
|
{$ENDIF}
|
|
if TreeView<>nil then
|
|
TreeView.BeginUpdate;
|
|
try
|
|
WasSelected:=Selected;
|
|
Unbind;
|
|
if Owner<>nil then
|
|
Owner.ClearCache;
|
|
Include(FStates,nsBound);
|
|
if Owner<>nil then inc(Owner.FCount);
|
|
// set parent
|
|
if AddMode in [taAdd, taAddFirst] then
|
|
FParent:=ANode
|
|
else begin // taInsert
|
|
if (ANode=nil) then
|
|
TreeNodeError('TTreeNode.InternalMove AddMode=taInsert but ANode=nil');
|
|
FParent:=ANode.Parent;
|
|
FPrevBrother:=ANode.FPrevBrother;
|
|
FNextBrother:=ANode;
|
|
end;
|
|
if FParent<>nil then begin
|
|
FParent.HasChildren:=true;
|
|
if (FParent.FCount=FParent.FCapacity) then begin
|
|
// grow FParent.FItems
|
|
if FParent.FCapacity=0 then
|
|
FParent.FCapacity:=5
|
|
else
|
|
FParent.FCapacity:=FParent.FCapacity shl 1;
|
|
NewParentItemSize:=SizeOf(Pointer)*FParent.FCapacity;
|
|
if FParent.FItems=nil then
|
|
GetMem(FParent.FItems,NewParentItemSize)
|
|
else
|
|
ReAllocMem(FParent.FItems,NewParentItemSize);
|
|
end;
|
|
inc(FParent.FCount);
|
|
// calculate new Index
|
|
case AddMode of
|
|
taAdd: NewIndex:=FParent.Count-1;
|
|
taAddFirst: NewIndex:=0;
|
|
else
|
|
// taInsert
|
|
NewIndex:=ANode.Index;
|
|
end;
|
|
// move next siblings
|
|
for i:=FParent.FCount-1 downto NewIndex+1 do begin
|
|
FParent.FItems[i]:=FParent.FItems[i-1];
|
|
FParent.FItems[i].FIndex:=i;
|
|
end;
|
|
// insert this node to parent's items
|
|
FParent.FItems[NewIndex]:=Self;
|
|
FIndex:=NewIndex;
|
|
// set Next and Prev sibling
|
|
if NewIndex>0 then
|
|
FPrevBrother:=FParent.FItems[NewIndex-1]
|
|
else
|
|
FPrevBrother:=nil;
|
|
if NewIndex<FParent.Count-1 then
|
|
FNextBrother:=FParent.FItems[NewIndex+1]
|
|
else
|
|
FNextBrother:=nil;
|
|
// update total node count of all parents
|
|
HigherNode:=FParent;
|
|
while HigherNode<>nil do begin
|
|
inc(HigherNode.FSubTreeCount,FSubTreeCount);
|
|
HigherNode:=HigherNode.Parent;
|
|
end;
|
|
//if TreeNodes<>nil then inc(TreeNodes.FCount,FSubTreeCount);
|
|
end else begin
|
|
// add as top level node
|
|
case AddMode of
|
|
taAdd:
|
|
begin
|
|
// add as last top level node
|
|
if Owner<>nil then begin
|
|
FPrevBrother:=Owner.GetLastNode;
|
|
Owner.MoveTopLvlNode(-1,Owner.FTopLvlCount,Self);
|
|
end;
|
|
end;
|
|
taAddFirst:
|
|
begin
|
|
// add as first top level node = root node
|
|
if Owner<>nil then begin
|
|
FNextBrother:=Owner.GetFirstNode;
|
|
Owner.MoveTopLvlNode(-1,0,Self);
|
|
end;
|
|
end;
|
|
taInsert:
|
|
begin
|
|
// insert node in front of ANode
|
|
//DebugLn('[TTreeNode.InternalMove] ANode.Index=',ANode.Index,' ANode=',DbgS(ANode));
|
|
FNextBrother:=ANode;
|
|
FPrevBrother:=ANode.GetPrevSibling;
|
|
if Owner<>nil then begin
|
|
Owner.MoveTopLvlNode(-1,ANode.Index,Self);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
// connect Next and Prev sibling
|
|
if FPrevBrother<>nil then FPrevBrother.FNextBrother:=Self;
|
|
if FNextBrother<>nil then FNextBrother.FPrevBrother:=Self;
|
|
if Owner.Owner<>nil then
|
|
Owner.Owner.FStates:=Owner.Owner.FStates+[tvsMaxRightNeedsUpdate,
|
|
tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
// re-add to multiselection list
|
|
if MultiSelected then
|
|
BindToMultiSelected;
|
|
if WasSelected then
|
|
Selected:=true;
|
|
finally
|
|
if TreeView<>nil then TreeView.EndUpdate;
|
|
end;
|
|
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DbgOut('[TTreeNode.InternalMove] END Self=',DbgS(Self),' Self.Text=',Text
|
|
,' ANode=',DbgS(ANode<>nil),' AddMode=',AddModeStr(AddMode));
|
|
if ANode<>nil then
|
|
DbgOut(' ANode.Text=',ANode.Text);
|
|
DebugLn('');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TTreeNode.MoveTo(Destination: TTreeNode; Mode: TNodeAttachMode);
|
|
{
|
|
TNodeAttachMode = (naAdd, naAddFirst, naAddChild, naAddChildFirst, naInsert);
|
|
|
|
naAdd: add as last sibling of Destination
|
|
naAddFirst: add as first sibling of Destnation
|
|
naAddChild: add as last child of Destination
|
|
naAddChildFirst: add as first child of Destination
|
|
naInsert: insert in front of Destination
|
|
naInsertBehind: insert behind Destination
|
|
}
|
|
var
|
|
AddMode: TAddMode;
|
|
//ANode: TTreeNode;
|
|
//HItem: HTreeItem;
|
|
OldOnChanging: TTVChangingEvent;
|
|
OldOnChange: TTVChangedEvent;
|
|
begin
|
|
if (Destination=nil) and not(Mode in [naAdd,naAddFirst]) then
|
|
TreeNodeError('TTreeNode.MoveTo Destination=nil');
|
|
if Mode=naInsertBehind then begin // convert naInsertBehind
|
|
if Destination.GetNextSibling=nil then
|
|
Mode:=naAdd
|
|
else begin
|
|
Mode:=naInsert;
|
|
Destination:=Destination.GetNextSibling;
|
|
end;
|
|
end;
|
|
if (Destination = nil) or not Destination.HasAsParent(Self) then begin
|
|
OldOnChanging := TreeView.OnChanging;
|
|
OldOnChange := TreeView.OnChange;
|
|
TreeView.OnChanging := nil;
|
|
TreeView.OnChange := nil;
|
|
try
|
|
if (Destination <> nil) and (Mode in [naAdd, naAddFirst]) then
|
|
Destination := Destination.Parent;
|
|
case Mode of
|
|
naAddFirst,
|
|
naAddChildFirst: AddMode := taAddFirst;
|
|
naInsert: AddMode := taInsert;
|
|
else
|
|
AddMode:=taAdd;
|
|
end;
|
|
if (Destination <> Self) then
|
|
InternalMove(Destination, AddMode);
|
|
finally
|
|
TreeView.OnChanging := OldOnChanging;
|
|
TreeView.OnChange := OldOnChange;
|
|
end;
|
|
end;
|
|
Changed(ncParentChanged)
|
|
end;
|
|
|
|
procedure TTreeNode.MultiSelectGroup;
|
|
var
|
|
FirstNode, LastNode, ANode: TTreeNode;
|
|
begin
|
|
if Assigned(TreeView) and not (tvoAllowMultiselect in TreeView.Options) then
|
|
Exit;
|
|
if Assigned(TreeView) then TreeView.LockSelectionChangeEvent;
|
|
try
|
|
// We need to select the nodes between the selected node and the current node
|
|
FirstNode := GetPrevSibling;
|
|
while Assigned(FirstNode) and not FirstNode.Selected do
|
|
FirstNode := FirstNode.GetPrevSibling;
|
|
if not Assigned(FirstNode) then FirstNode := Self;
|
|
LastNode := GetNextSibling;
|
|
while Assigned(LastNode) and not LastNode.Selected do
|
|
LastNode := LastNode.GetNextSibling;
|
|
if not Assigned(LastNode) then LastNode := Self;
|
|
ANode := FirstNode;
|
|
while Assigned(ANode) do
|
|
begin
|
|
ANode.MultiSelected := True;
|
|
if ANode = LastNode then Break;
|
|
ANode := ANode.GetNextSibling;
|
|
end;
|
|
finally
|
|
if Assigned(TreeView) then TreeView.UnlockSelectionChangeEvent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.MakeVisible;
|
|
begin
|
|
if Assigned(TreeView) then
|
|
TreeView.EnsureNodeIsVisible(Self)
|
|
else
|
|
ExpandParents;
|
|
end;
|
|
|
|
function TTreeNode.GetLevel: Integer;
|
|
// root is on level 0
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result := 0;
|
|
ANode := Parent;
|
|
while Assigned(ANode) do
|
|
begin
|
|
Inc(Result);
|
|
ANode := ANode.Parent;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.GetMultiSelected: Boolean;
|
|
begin
|
|
Result := GetState(nsMultiSelected);
|
|
end;
|
|
|
|
function TTreeNode.IsNodeVisible: Boolean;
|
|
begin
|
|
if Assigned(TreeView) then
|
|
Result := TreeView.IsNodeVisible(Self)
|
|
else
|
|
Result := ParentsExpandedVisible;
|
|
end;
|
|
|
|
function TTreeNode.IsNodeHeightFullVisible: Boolean;
|
|
begin
|
|
if Assigned(TreeView) then
|
|
Result := TreeView.IsNodeHeightFullVisible(Self)
|
|
else
|
|
Result := ParentsExpandedVisible;
|
|
end;
|
|
|
|
procedure TTreeNode.Update;
|
|
var
|
|
TV: TCustomTreeView;
|
|
begin
|
|
TV := TreeView;
|
|
if Assigned(TV) and (Owner.FUpdateCount = 0) and (not (csLoading in TV.ComponentState)) then
|
|
TV.Invalidate;
|
|
end;
|
|
|
|
function TTreeNode.EditText: Boolean;
|
|
var
|
|
TV: TCustomTreeView;
|
|
begin
|
|
TV := TreeView;
|
|
Result := Assigned(TV) and (tvsIsEditing in TreeView.FStates);
|
|
TV.BeginEditing(Self);
|
|
end;
|
|
|
|
function TTreeNode.DisplayRect(TextOnly: Boolean): TRect;
|
|
begin
|
|
FillChar(Result, SizeOf(Result), 0);
|
|
if TreeView <> nil then
|
|
begin
|
|
Result.Left := TreeView.BorderWidth;
|
|
Result.Top := Top - TreeView.ScrolledTop + TreeView.BorderWidth;
|
|
Result.Right := TreeView.ClientWidth - TreeView.BorderWidth;
|
|
Result.Bottom := Result.Top + Height;
|
|
if TextOnly then
|
|
begin
|
|
Result.Left := DisplayTextLeft;
|
|
if Result.Left > Result.Right then
|
|
Result.Left := Result.Right;
|
|
Result.Right := DisplayTextRight;
|
|
if Result.Right < Result.Left then
|
|
Result.Right := Result.Left;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignLeft: integer;
|
|
var
|
|
TV: TCustomTreeView;
|
|
l: LongInt;
|
|
RealIndent: Integer;
|
|
begin
|
|
Result := 0;
|
|
TV := TreeView;
|
|
RealIndent := TV.Indent;
|
|
if TV = nil then Exit;
|
|
l := Level;
|
|
if not (tvoShowRoot in TV.Options) then
|
|
inc(Result, RealIndent * (l - 1) + (RealIndent shr 2) + TV.BorderWidth - TV.FScrolledLeft)
|
|
else
|
|
inc(Result, RealIndent * l + TV.BorderWidth - TV.FScrolledLeft);
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignRect: TRect;
|
|
begin
|
|
FillChar(Result, SizeOf(Result), 0);
|
|
if TreeView <> nil then
|
|
begin
|
|
Result.Left := DisplayExpandSignLeft;
|
|
Result.Top := Top;
|
|
Result.Right := Result.Left + TreeView.Indent;
|
|
Result.Bottom := Top + Height;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNode.DisplayExpandSignRight: integer;
|
|
begin
|
|
Result := DisplayExpandSignLeft;
|
|
if TreeView <> nil then
|
|
inc(Result, TreeView.Indent);
|
|
end;
|
|
|
|
function TTreeNode.DisplayIconLeft: integer;
|
|
var
|
|
TV: TCustomTreeView;
|
|
begin
|
|
Result := DisplayStateIconLeft;
|
|
TV := TreeView;
|
|
if (TV = nil) or (TV.StateImages = nil) then Exit;
|
|
if (StateIndex < 0) or (StateIndex >= TV.StateImages.Count) then Exit;
|
|
Inc(Result, TV.StateImages.WidthForPPI[TV.StateImagesWidth, TV.Font.PixelsPerInch] + TV.FDefItemSpace);
|
|
end;
|
|
|
|
function TTreeNode.DisplayStateIconLeft: integer;
|
|
begin
|
|
Result := DisplayExpandSignRight;
|
|
end;
|
|
|
|
function TTreeNode.DisplayTextLeft: integer;
|
|
var
|
|
TV: TCustomTreeView;
|
|
ImgIndex: TImageIndex;
|
|
sz: TSize;
|
|
begin
|
|
Result := DisplayIconLeft;
|
|
TV := TreeView;
|
|
if TV = nil then
|
|
exit;
|
|
sz := TV.GetImageSize;
|
|
if (TV.Images = nil) then
|
|
begin
|
|
inc(Result, sz.CX);
|
|
exit;
|
|
end;
|
|
if (TV.Selected = Self) then
|
|
ImgIndex:=SelectedIndex
|
|
else
|
|
ImgIndex:=ImageIndex;
|
|
if (ImgIndex<0) or (ImgIndex>=TV.Images.Count) then Exit;
|
|
Inc(Result, sz.CX + TV.FDefItemSpace);
|
|
end;
|
|
|
|
function TTreeNode.DisplayTextRight: integer;
|
|
var
|
|
TV: TCustomTreeView;
|
|
begin
|
|
Result := DisplayTextLeft;
|
|
TV := TreeView;
|
|
if TV <> nil then
|
|
Inc(Result, TV.Canvas.TextWidth(Text) + TV.Indent div 2);
|
|
end;
|
|
|
|
function TTreeNode.AlphaSort: Boolean;
|
|
begin
|
|
Result := CustomSort(nil);
|
|
end;
|
|
|
|
function TTreeNode.CustomSort(SortProc: TTreeNodeCompare): Boolean;
|
|
begin
|
|
if FCount>0 then begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
if not Assigned(SortProc) then SortProc:=@DefaultTreeViewSort;
|
|
Sort(FItems, FCount, SortProc, true);
|
|
end;
|
|
if (TreeView <> nil) then
|
|
Include(TreeView.FStates, tvsTopsNeedsUpdate);
|
|
Result:=true;
|
|
end;
|
|
|
|
procedure TTreeNode.Delete;
|
|
begin
|
|
if not Deleting then Free;
|
|
end;
|
|
|
|
procedure TTreeNode.DeleteChildren;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
Collapse(true);
|
|
HasChildren := False;
|
|
end;
|
|
|
|
procedure TTreeNode.Assign(Source: TPersistent);
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
if Source is TTreeNode then
|
|
begin
|
|
ANode := TTreeNode(Source);
|
|
Text := ANode.Text;
|
|
Data := ANode.Data;
|
|
ImageIndex := ANode.ImageIndex;
|
|
SelectedIndex := ANode.SelectedIndex;
|
|
StateIndex := ANode.StateIndex;
|
|
OverlayIndex := ANode.OverlayIndex;
|
|
Height := ANode.Height;
|
|
Focused := ANode.Focused;
|
|
//DropTarget := ANode.DropTarget;
|
|
Cut := ANode.Cut;
|
|
HasChildren := ANode.HasChildren;
|
|
end
|
|
else inherited Assign(Source);
|
|
end;
|
|
|
|
function TTreeNode.IsEqual(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := (Text = Node.Text) and (Data = Node.Data);
|
|
end;
|
|
|
|
procedure TTreeNode.ReadData(Stream: TStream; StreamVersion: integer);
|
|
var
|
|
I, ItemCount: Integer;
|
|
NewExpanded: boolean;
|
|
OldInfo: TOldTreeNodeInfo;
|
|
Info: TTreeNodeInfo;
|
|
Node: TTreeNode;
|
|
lSelfAX: TLazAccessibleObject;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
if StreamVersion=TTreeNodeWithPointerStreamVersion then
|
|
begin
|
|
Stream.ReadBuffer(OldInfo, SizeOf(TOldTreeNodeInfo));
|
|
ImageIndex := OldInfo.ImageIndex;
|
|
SelectedIndex := OldInfo.SelectedIndex;
|
|
StateIndex := OldInfo.StateIndex;
|
|
OverlayIndex := OldInfo.OverlayIndex;
|
|
Data := Pointer(OldInfo.Data);
|
|
Height := OldInfo.Height;
|
|
NewExpanded := OldInfo.Expanded;
|
|
ItemCount := OldInfo.Count;
|
|
SetLength(FText,OldInfo.TextLen)
|
|
end
|
|
else
|
|
begin
|
|
Stream.ReadBuffer(Info, SizeOf(TTreeNodeInfo));
|
|
ImageIndex := Info.ImageIndex;
|
|
SelectedIndex := Info.SelectedIndex;
|
|
StateIndex := Info.StateIndex;
|
|
OverlayIndex := Info.OverlayIndex;
|
|
Height := Info.Height;
|
|
NewExpanded := Info.Expanded;
|
|
ItemCount := Info.Count;
|
|
SetLength(FText,Info.TextLen);
|
|
end;
|
|
if FText<>'' then
|
|
begin
|
|
Stream.Read(FText[1],length(FText));
|
|
// Update accessibility information
|
|
if TreeView<>nil then
|
|
begin
|
|
lSelfAX := TreeView.GetAccessibleObject.GetChildAccessibleObjectWithDataObject(Self);
|
|
if lSelfAX <> nil then
|
|
lSelfAX.AccessibleValue := FText;
|
|
end;
|
|
end;
|
|
if Owner<>nil then begin
|
|
for I := 0 to ItemCount - 1 do begin
|
|
Node:=Owner.AddChild(Self, '');
|
|
Node.ReadData(Stream, StreamVersion);
|
|
Owner.Owner.Added(Node);
|
|
end;
|
|
end;
|
|
Expanded := NewExpanded;
|
|
end;
|
|
|
|
procedure TTreeNode.ReadDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
|
|
var
|
|
I, Size, ItemCount: Integer;
|
|
begin
|
|
if Owner<>nil then Owner.ClearCache;
|
|
Stream.ReadBuffer(Size, SizeOf(Size));
|
|
Stream.ReadBuffer(Info^, Size);
|
|
Text := Info^.Text;
|
|
ImageIndex := Info^.ImageIndex;
|
|
SelectedIndex := Info^.SelectedIndex;
|
|
StateIndex := Info^.StateIndex;
|
|
OverlayIndex := Info^.OverlayIndex;
|
|
Data := Pointer(PtrUInt(Info^.Data));
|
|
if Owner<>nil then begin
|
|
ItemCount := Info^.Count;
|
|
for I := 0 to ItemCount - 1 do
|
|
Owner.AddChild(Self, '').ReadDelphiData(Stream, Info);
|
|
end;
|
|
end;
|
|
|
|
{ Reads a node (and its children) from the Items.NodeData block written
|
|
by more recent versions of Delphi. See issue #41303. }
|
|
procedure TTreeNode.ReadDelphiNodeData(Stream: TStream; ASignature: Byte);
|
|
var
|
|
I, Size, ItemCount: Integer;
|
|
Info: TDelphiNodeDataInfo;
|
|
s: UnicodeString;
|
|
begin
|
|
if ASignature <> 3 then
|
|
exit;
|
|
if Owner <> nil then Owner.ClearCache;
|
|
Stream.ReadBuffer(Size, SizeOf(Size));
|
|
Stream.ReadBuffer(Info, SizeOf(TDelphiNodeDataInfo));
|
|
SetLength(s, info.TextLen);
|
|
Stream.ReadBuffer(s[1], info.TextLen * SizeOf(WideChar));
|
|
Text := UTF8Encode(s);
|
|
ImageIndex := Info.ImageIndex;
|
|
SelectedIndex := Info.SelectedIndex;
|
|
StateIndex := Info.StateIndex;
|
|
OverlayIndex := Info.OverlayIndex;
|
|
ItemCount := Info.ChildCount;
|
|
Enabled := (Info.Enabled = 1);
|
|
if Owner <> nil then
|
|
begin
|
|
for I := 0 to ItemCount - 1 do
|
|
Owner.AddChild(Self, '').ReadDelphiNodeData(Stream, ASignature);
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.WriteData(Stream: TStream; StreamVersion: integer);
|
|
var
|
|
i: integer;
|
|
OldInfo: TOldTreeNodeInfo;
|
|
Info: TTreeNodeInfo;
|
|
begin
|
|
if StreamVersion=TTreeNodeWithPointerStreamVersion then
|
|
begin
|
|
OldInfo.ImageIndex := ImageIndex;
|
|
OldInfo.SelectedIndex := SelectedIndex;
|
|
OldInfo.OverlayIndex := OverlayIndex;
|
|
OldInfo.StateIndex := StateIndex;
|
|
OldInfo.Height := FHeight;
|
|
OldInfo.Data := PtrUInt(Data);
|
|
OldInfo.Count := Count;
|
|
OldInfo.Expanded := Expanded;
|
|
OldInfo.TextLen := Length(Text);
|
|
Stream.WriteBuffer(OldInfo, SizeOf(TOldTreeNodeInfo));
|
|
end
|
|
else
|
|
begin
|
|
Info.ImageIndex := ImageIndex;
|
|
Info.SelectedIndex := SelectedIndex;
|
|
Info.OverlayIndex := OverlayIndex;
|
|
Info.StateIndex := StateIndex;
|
|
Info.Height := FHeight;
|
|
Info.Count := Count;
|
|
Info.Expanded := Expanded;
|
|
Info.TextLen := Length(Text);
|
|
Stream.WriteBuffer(Info, SizeOf(TTreeNodeInfo));
|
|
end;
|
|
if Text<>'' then
|
|
Stream.Write(FText[1],length(Text));
|
|
for i := 0 to Count - 1 do
|
|
Items[i].WriteData(Stream, StreamVersion);
|
|
end;
|
|
|
|
procedure TTreeNode.WriteDelphiData(Stream: TStream; Info: PDelphiNodeInfo);
|
|
var
|
|
I, Size, L, ItemCount: Integer;
|
|
begin
|
|
L := Length(Text);
|
|
if L > 255 then L := 255;
|
|
Size := SizeOf(TDelphiNodeInfo) + L - 255;
|
|
Info^.Text := Text;
|
|
Info^.ImageIndex := ImageIndex;
|
|
Info^.SelectedIndex := SelectedIndex;
|
|
Info^.OverlayIndex := OverlayIndex;
|
|
Info^.StateIndex := StateIndex;
|
|
Info^.Data := {%H-}Cardinal(PtrUInt(Data));
|
|
ItemCount := Count;
|
|
Info^.Count := ItemCount;
|
|
Stream.WriteBuffer(Size, SizeOf(Size));
|
|
Stream.WriteBuffer(Info^, Size);
|
|
for I := 0 to ItemCount - 1 do
|
|
Items[I].WriteDelphiData(Stream, Info);
|
|
end;
|
|
|
|
procedure TTreeNode.Changed(ChangeReason: TTreeNodeChangeReason);
|
|
begin
|
|
TreeView.NodeChanged(self,ChangeReason);
|
|
end;
|
|
|
|
function TTreeNode.GetOwner: TPersistent;
|
|
begin
|
|
Result := FOwner;
|
|
end;
|
|
|
|
procedure TTreeNode.ConsistencyCheck;
|
|
var
|
|
RealSubTreeCount: integer;
|
|
i: integer;
|
|
Node1: TTreeNode;
|
|
begin
|
|
if FCapacity<0 then
|
|
LazTracer.RaiseGDBException('');
|
|
if FCapacity<FCount then
|
|
LazTracer.RaiseGDBException('');
|
|
if FCount<0 then
|
|
LazTracer.RaiseGDBException('');
|
|
if FHeight<0 then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FItems<>nil) and (FCapacity<=0) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FCapacity>0) and (FItems=nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FNextBrother<>nil) and (FNextBrother.FPrevBrother<>Self) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FPrevBrother<>nil) and (FPrevBrother.FNextBrother<>Self) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FNextMultiSelected<>nil) and (FNextMultiSelected.FPrevMultiSelected<>Self) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FPrevMultiSelected<>nil) and (FPrevMultiSelected.FNextMultiSelected<>Self) then
|
|
LazTracer.RaiseGDBException('');
|
|
if MultiSelected then begin
|
|
Node1:=TreeView.GetFirstMultiSelected;
|
|
while (Node1<>nil) and (Node1<>Self) do Node1:=Node1.FNextMultiSelected;
|
|
if Node1=nil then
|
|
LazTracer.RaiseGDBException('');
|
|
end;
|
|
if Selected and (TreeView<>nil) and (tvoAllowMultiselect in TreeView.Options)
|
|
and (not MultiSelected) then
|
|
LazTracer.RaiseGDBException('');// selected, but not multiselected
|
|
|
|
// check children
|
|
RealSubTreeCount:=1;
|
|
for i:=0 to FCount-1 do begin
|
|
if (Items[i]=nil) then LazTracer.RaiseGDBException('');
|
|
Node1:=Items[i];
|
|
if Node1.FParent<>Self then LazTracer.RaiseGDBException('');
|
|
if (i=0) and (Node1.FPrevBrother<>nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i>0) and (Node1.FPrevBrother=nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i>0) and (Node1.FPrevBrother<>Items[i-1]) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i<FCount-1) and (Node1.FNextBrother=nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i<FCount-1) and (Node1.FNextBrother<>Items[i+1]) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i=FCount-1) and (Node1.FNextBrother<>nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if Node1.FIndex<>i then
|
|
LazTracer.RaiseGDBException('');
|
|
Node1.ConsistencyCheck;
|
|
inc(RealSubTreeCount,Node1.SubTreeCount);
|
|
end;
|
|
if FParent<>nil then begin
|
|
if FParent.IndexOf(Self)<0 then LazTracer.RaiseGDBException('');
|
|
end;
|
|
if RealSubTreeCount<>SubTreeCount then LazTracer.RaiseGDBException('');
|
|
if FTop<0 then LazTracer.RaiseGDBException('');
|
|
// check for circles
|
|
if FNextBrother=Self then LazTracer.RaiseGDBException('');
|
|
if FPrevBrother=Self then LazTracer.RaiseGDBException('');
|
|
if FParent=Self then LazTracer.RaiseGDBException('');
|
|
Node1:=FParent;
|
|
while Node1<>nil do begin
|
|
if (Node1=Self) then LazTracer.RaiseGDBException('');
|
|
Node1:=Node1.FParent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNode.WriteDebugReport(const Prefix: string; Recurse: boolean);
|
|
var i: integer;
|
|
begin
|
|
DbgOut('%s%s.WriteDebugReport Self=%p',[Prefix, ClassName, Pointer(Self)]);
|
|
ConsistencyCheck;
|
|
DebugLn(' Text=',Text);
|
|
if Recurse then begin
|
|
for i:=0 to FCount-1 do
|
|
Items[i].WriteDebugReport(Prefix+' ',true);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TTreeNodes }
|
|
|
|
constructor TTreeNodes.Create(AnOwner: TCustomTreeView);
|
|
begin
|
|
inherited Create;
|
|
FSelection := TFPList.Create;
|
|
FOwner := AnOwner;
|
|
end;
|
|
|
|
destructor TTreeNodes.Destroy;
|
|
begin
|
|
Clear;
|
|
FreeThenNil(FSelection);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TTreeNodes.GetCount: Integer;
|
|
begin
|
|
Result:=FCount;
|
|
end;
|
|
|
|
function TTreeNodes.GetOwner: TPersistent;
|
|
begin
|
|
Result := FOwner;
|
|
end;
|
|
|
|
function TTreeNodes.GetHandle: TLCLHandle;
|
|
begin
|
|
if Owner<>nil then
|
|
Result:=Owner.Handle
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeNodes.Delete(Node: TTreeNode);
|
|
begin
|
|
Node.Delete;
|
|
if (FUpdateCount=0) and (Owner<>nil) then
|
|
Owner.Invalidate;
|
|
end;
|
|
|
|
procedure TTreeNodes.Clear;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
BeginUpdate;
|
|
ClearCache;
|
|
Node := GetLastNode;
|
|
if Assigned(Node) then
|
|
begin
|
|
while Assigned(Node) do
|
|
begin
|
|
Node.Delete;
|
|
Node := GetLastNode;
|
|
end;
|
|
end;
|
|
FSelection.Clear;
|
|
if (FOwner <> nil) then
|
|
FOwner.GetAccessibleObject().ClearChildAccessibleObjects();
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TTreeNodes.ClearMultiSelection(ClearSelected: boolean = false);
|
|
var
|
|
ANode, OldNode: TTreeNode;
|
|
begin
|
|
if Assigned(Owner) then Owner.LockSelectionChangeEvent;
|
|
try
|
|
ANode := FFirstMultiSelected;
|
|
while Assigned(ANode) do
|
|
begin
|
|
OldNode := ANode;
|
|
ANode := ANode.GetNextMultiSelected;
|
|
OldNode.MultiSelected := false;
|
|
end;
|
|
if ClearSelected then
|
|
Owner.Selected := nil;
|
|
finally
|
|
if Assigned(Owner) then Owner.UnlockSelectionChangeEvent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.SelectOnlyThis(Node: TTreeNode);
|
|
begin
|
|
if Owner<>nil then Owner.LockSelectionChangeEvent;
|
|
try
|
|
ClearMultiSelection(true);
|
|
Node.Selected:=true;
|
|
finally
|
|
if Owner<>nil then Owner.UnlockSelectionChangeEvent;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.IsMultiSelection: boolean;
|
|
begin
|
|
Result:=(FFirstMultiSelected<>nil)
|
|
and (FFirstMultiSelected.GetNextMultiSelected<>nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddChildFirst(ParentNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddChildObjectFirst(ParentNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddChildObjectFirst(ParentNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
begin
|
|
Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
|
|
end;
|
|
|
|
function TTreeNodes.AddChild(ParentNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddChildObject(ParentNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddChildObject(ParentNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
begin
|
|
Result := InternalAddObject(ParentNode, S, Data, taAdd);
|
|
end;
|
|
|
|
function TTreeNodes.AddFirst(SiblingNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddObjectFirst(SiblingNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.AddObjectFirst(SiblingNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
var ParentNode: TTreeNode;
|
|
begin
|
|
if SiblingNode <> nil then
|
|
ParentNode := SiblingNode.Parent
|
|
else
|
|
ParentNode := nil;
|
|
Result := InternalAddObject(ParentNode, S, Data, taAddFirst);
|
|
end;
|
|
|
|
function TTreeNodes.AddNode(Node: TTreeNode; Relative: TTreeNode;
|
|
const S: string; Ptr: Pointer; Method: TNodeAttachMode): TTreeNode;
|
|
var
|
|
AddMode: TAddMode;
|
|
begin
|
|
if (Relative=nil) and not (Method in [naAdd,naAddFirst]) then
|
|
TreeNodeError('TTreeNode.AddNode Relative=nil');
|
|
if Method=naInsertBehind then begin // convert naInsertBehind
|
|
if Relative.GetNextSibling=nil then
|
|
Method:=naAdd
|
|
else begin
|
|
Method:=naInsert;
|
|
Relative:=Relative.GetNextSibling;
|
|
end;
|
|
end;
|
|
if (Relative <> nil) and (Method in [naAdd, naAddFirst]) then
|
|
Relative := Relative.Parent;
|
|
// Convert TNodeAttachMode to TAddMode
|
|
case Method of
|
|
naAddFirst,naAddChildFirst: AddMode := taAddFirst;
|
|
naInsert: AddMode := taInsert;
|
|
else
|
|
AddMode:=taAdd;
|
|
end;
|
|
fNewNodeToBeAdded := Node;
|
|
Result := InternalAddObject(Relative, S, Ptr, AddMode);
|
|
end;
|
|
|
|
procedure TTreeNodes.SelectionsChanged(ANode: TTreeNode; const AIsSelected: Boolean);
|
|
begin
|
|
if ANode <> nil then
|
|
begin
|
|
if AIsSelected then
|
|
FSelection.Add(ANode)
|
|
else
|
|
FSelection.Remove(ANode);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetSelections(const AIndex: Integer): TTreeNode;
|
|
|
|
procedure RaiseOutOfBounds;
|
|
begin
|
|
TreeNodeError(Format(
|
|
'TTreeNodes.GetSelections Index %d out of bounds (Count=%d)',
|
|
[AIndex, FSelection.Count]));
|
|
end;
|
|
|
|
begin
|
|
if (AIndex < 0) or (AIndex > FSelection.Count - 1) then
|
|
RaiseOutOfBounds;
|
|
Result := TTreeNode(FSelection.Items[AIndex]);
|
|
end;
|
|
|
|
function TTreeNodes.Add(SiblingNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := AddObject(SiblingNode, S, nil);
|
|
end;
|
|
|
|
procedure TTreeNodes.Repaint(ANode: TTreeNode);
|
|
var
|
|
R: TRect;
|
|
begin
|
|
if (FUpdateCount < 1) and (Owner<>nil) then begin
|
|
while (ANode <> nil) and not ANode.IsVisible do ANode := ANode.Parent;
|
|
if ANode <> nil then begin
|
|
R := ANode.DisplayRect(False);
|
|
InvalidateRect(Owner.Handle, @R, True);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.AddObject(SiblingNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
var ParentNode: TTreeNode;
|
|
begin
|
|
if SiblingNode <> nil then
|
|
ParentNode := SiblingNode.Parent
|
|
else
|
|
ParentNode := nil;
|
|
Result := InternalAddObject(ParentNode, S, Data, taAdd);
|
|
end;
|
|
|
|
function TTreeNodes.Insert(NextNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := InsertObject(NextNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.InsertObject(NextNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
// create a new node with Text=S and Data=Data and insert in front of
|
|
// NextNode (as sibling with same parent).
|
|
begin
|
|
Result:=InternalAddObject(NextNode,S,Data,taInsert);
|
|
end;
|
|
|
|
function TTreeNodes.InsertBehind(PrevNode: TTreeNode; const S: string): TTreeNode;
|
|
begin
|
|
Result := InsertObjectBehind(PrevNode, S, nil);
|
|
end;
|
|
|
|
function TTreeNodes.InsertObjectBehind(PrevNode: TTreeNode; const S: string;
|
|
Data: Pointer): TTreeNode;
|
|
// create a new node with Text=S and Data=Data and insert in front of
|
|
// NextNode (as sibling with same parent).
|
|
begin
|
|
if (PrevNode<>nil) and (PrevNode.GetNextSibling<>nil) then
|
|
Result:=InternalAddObject(PrevNode.GetNextSibling,S,Data,taInsert)
|
|
else
|
|
Result:=AddObject(PrevNode,S,Data);
|
|
end;
|
|
|
|
procedure TTreeNodes.SortTopLevelNodes(SortProc: TTreeNodeCompare);
|
|
begin
|
|
Sort(FTopLvlItems, FTopLvlCount, SortProc, true);
|
|
end;
|
|
|
|
function TTreeNodes.InternalAddObject(Node: TTreeNode; const S: string;
|
|
Data: Pointer; AddMode: TAddMode): TTreeNode;
|
|
{
|
|
TAddMode = (taAddFirst, taAdd, taInsert);
|
|
|
|
taAdd: add Result as last child of Node
|
|
taAddFirst: add Result as first child of Node
|
|
taInsert: add Result in front of Node
|
|
}
|
|
var
|
|
ok: boolean;
|
|
// Item: HTreeItem;
|
|
lAccessibleObject: TLazAccessibleObject;
|
|
begin
|
|
if Owner=nil then
|
|
TreeNodeError('TTreeNodes.InternalAddObject Owner=nil');
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
write('[TTreeNodes.InternalAddObject] Node=',Node<>nil,' S=',S,
|
|
' AddMode=',AddModeStr(AddMode));
|
|
if Node<>nil then
|
|
DbgOut(' Node.Text=',Node.Text);
|
|
DebugLn('');
|
|
{$ENDIF}
|
|
Result := fNewNodeToBeAdded; // Used by AddNode to pass an existing node.
|
|
if Result = Nil then
|
|
Result := Owner.CreateNode;
|
|
fNewNodeToBeAdded := nil;
|
|
ok:=false;
|
|
try
|
|
Result.Data := Data;
|
|
Result.Text := S;
|
|
// move node in tree (tree of TTreeNode)
|
|
Result.InternalMove(Node,AddMode);
|
|
if (Owner<>nil) and Owner.AutoExpand and (Result.Parent<>nil) then
|
|
Result.Parent.Expanded:=true;
|
|
if (Owner<>nil) and (not (csReading in Owner.ComponentState)) then
|
|
Owner.Added(Result);
|
|
ok:=true;
|
|
if ok and (Owner<>nil) and (Owner.AccessibilityOn) then
|
|
begin
|
|
lAccessibleObject := FOwner.GetAccessibleObject().AddChildAccessibleObject(Result);
|
|
lAccessibleObject.AccessibleDescription := 'Item';
|
|
lAccessibleObject.AccessibleValue := S;
|
|
lAccessibleObject.AccessibleRole := larTreeItem;
|
|
end;
|
|
finally
|
|
Include(FOwner.FStates, tvsScrollbarChanged);
|
|
FOwner.UpdateScrollbars;
|
|
// this construction creates nicer exception output
|
|
if not ok then
|
|
Result.Free;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetFirstNode: TTreeNode;
|
|
begin
|
|
if Assigned(FTopLvlItems) then
|
|
Result := FTopLvlItems[0]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNodes.GetFirstVisibleNode: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
if Assigned(FTopLvlItems) then
|
|
for i := 0 to FTopLvlCount-1 do begin
|
|
Node := FTopLvlItems[i];
|
|
if Node.Visible then
|
|
exit(Node);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetFirstVisibleEnabledNode: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
if Assigned(FTopLvlItems) then
|
|
for i := 0 to FTopLvlCount-1 do begin
|
|
Node := FTopLvlItems[i];
|
|
if Node.HasStates([nsVisible,nsEnabled]) then
|
|
exit(Node);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastNode: TTreeNode;
|
|
begin
|
|
if Assigned(FTopLvlItems) then
|
|
Result := FTopLvlItems[FTopLvlCount - 1]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastVisibleNode: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
if Assigned(FTopLvlItems) then
|
|
for i := FTopLvlCount-1 downto 0 do
|
|
begin
|
|
Node := FTopLvlItems[i];
|
|
if Node.Visible then
|
|
exit(Node);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastVisibleEnabledNode: TTreeNode;
|
|
var
|
|
Node: TTreeNode;
|
|
i: Integer;
|
|
begin
|
|
Result := nil;
|
|
if Assigned(FTopLvlItems) then
|
|
for i := FTopLvlCount-1 downto 0 do
|
|
begin
|
|
Node := FTopLvlItems[i];
|
|
if Node.HasStates([nsVisible,nsEnabled]) then
|
|
exit(Node);
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastSubNode: TTreeNode;
|
|
// absolute last node
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := GetLastNode;
|
|
if Assigned(Result) then
|
|
begin
|
|
Node := Result.GetLastSubChild;
|
|
if Assigned(Node) then
|
|
Result := Node;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.GetLastExpandedSubNode: TTreeNode;
|
|
// absolute last expanded visible enabled node
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := GetLastVisibleEnabledNode;
|
|
while Assigned(Result) and (Result.Expanded) do
|
|
begin
|
|
Node := Result.GetLastVisibleChild;
|
|
if Assigned(Node) then
|
|
Result := Node
|
|
else
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.FindTopLvlNode(const NodeText: string): TTreeNode;
|
|
begin
|
|
Result := GetFirstNode;
|
|
if (foFindIgnoresCase in FOwner.FFindOptions) then
|
|
begin
|
|
while Assigned(Result) and not SameText(Result.Text, NodeText) do
|
|
Result := Result.GetNextSibling;
|
|
end else
|
|
begin
|
|
while Assigned(Result) and (Result.Text <> NodeText) do
|
|
Result := Result.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.FindNodeWithText(const NodeText: string): TTreeNode;
|
|
begin
|
|
Result := GetFirstNode;
|
|
if (foFindIgnoresCase in FOwner.FFindOptions) then
|
|
begin
|
|
while Assigned(Result) and not SameText(Result.Text, NodeText) do
|
|
Result := Result.GetNext;
|
|
end else
|
|
begin
|
|
while Assigned(Result) and (Result.Text <> NodeText) do
|
|
Result := Result.GetNext;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.FindNodeWithTextPath(TextPath: string): TTreeNode;
|
|
var
|
|
CurText: String;
|
|
begin
|
|
Result := nil;
|
|
for CurText in TextPath.Split(FOwner.FPathDelimiter) do
|
|
begin
|
|
if Result = nil then
|
|
begin
|
|
if CurText = '' then
|
|
Result := FindTopLvlNode(FOwner.FPathDelimiter)
|
|
else
|
|
Result := FindTopLvlNode(CurText);
|
|
end else begin
|
|
if (foFindExpands in FOwner.FFindOptions) then
|
|
Result.Expanded := true;
|
|
Result := Result.FindNode(CurText);
|
|
if Result = nil then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.FindNodeWithData(const NodeData: Pointer): TTreeNode;
|
|
begin
|
|
Result := GetFirstNode;
|
|
while Assigned(Result) and (Result.Data <> NodeData) do
|
|
Result := Result.GetNext;
|
|
end;
|
|
|
|
function TTreeNodes.GetNodeFromIndex(Index: Integer): TTreeNode;
|
|
// find node with absolute index in ALL nodes (even collapsed)
|
|
|
|
procedure RaiseIndexOutOfBounds;
|
|
begin
|
|
TreeNodeError(Format(
|
|
'TTreeNodes.GetNodeFromIndex Index %d out of bounds (Count=%d)',
|
|
[Index, FCount]));
|
|
end;
|
|
|
|
procedure RaiseSubTreeCount0;
|
|
begin
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency error - SubTreeCount=0');
|
|
end;
|
|
|
|
procedure RaiseSubTreeCountTooBig;
|
|
begin
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency error - invalid SubTreeCount');
|
|
end;
|
|
|
|
procedure RaiseCountTooBig;
|
|
begin
|
|
TreeNodeError(
|
|
'TTreeNodes.GetNodeFromIndex: Consistency Error - Count too big');
|
|
end;
|
|
|
|
var
|
|
I, J: Integer;
|
|
begin
|
|
if (Index < 0) or (Index >= FCount) then
|
|
RaiseIndexOutOfBounds;
|
|
if (FNodeCache.CacheNode <> nil) and (Abs(FNodeCache.CacheIndex - Index) <= 1)
|
|
then begin
|
|
with FNodeCache do
|
|
begin
|
|
if Index = CacheIndex then Result := CacheNode
|
|
else if Index < CacheIndex then Result := CacheNode.GetPrev
|
|
else Result := CacheNode.GetNext;
|
|
end;
|
|
end
|
|
else if Index>Count-5 then begin
|
|
// optimization for the last nodes
|
|
Result:=GetLastSubNode;
|
|
i:=Count-1;
|
|
while (Index<i) do begin
|
|
Result:=Result.GetPrev;
|
|
dec(i);
|
|
end;
|
|
end
|
|
else begin
|
|
Result := GetFirstNode;
|
|
I:=0;
|
|
while (Result<>nil) and (Index>I) do begin
|
|
Repeat
|
|
// calculate the absolute index of the next sibling
|
|
J:=I+Result.FSubTreeCount;
|
|
if J=I then RaiseSubTreeCount0;
|
|
if J<=Index then begin
|
|
// Index > absolute index of next sibling -> search in next sibling
|
|
Result:=Result.GetNextSibling;
|
|
I:=J;
|
|
end else
|
|
break;
|
|
until false;
|
|
if (Result<>nil) and (Index>I) then begin
|
|
// Index is somewhere in subtree of Result
|
|
Result:=Result.GetFirstChild;
|
|
if Result=nil then RaiseSubTreeCountTooBig;
|
|
inc(I);
|
|
end;
|
|
end;
|
|
end;
|
|
if Result = nil then RaiseCountTooBig;
|
|
FNodeCache.CacheNode := Result;
|
|
FNodeCache.CacheIndex := Index;
|
|
end;
|
|
|
|
function TTreeNodes.GetSelectionCount: Cardinal;
|
|
begin
|
|
Result := Cardinal(FSelection.Count);
|
|
end;
|
|
|
|
procedure TTreeNodes.SetItem(Index: Integer; AValue: TTreeNode);
|
|
begin
|
|
GetNodeFromIndex(Index).Assign(AValue);
|
|
end;
|
|
|
|
procedure TTreeNodes.SetTopLvlItems(Index: integer; AValue: TTreeNode);
|
|
begin
|
|
GetTopLvlItems(Index).Assign(AValue);
|
|
end;
|
|
|
|
procedure TTreeNodes.BeginUpdate;
|
|
begin
|
|
Inc(FUpdateCount);
|
|
end;
|
|
|
|
procedure TTreeNodes.EndUpdate;
|
|
begin
|
|
Dec(FUpdateCount);
|
|
if FUpdateCount = 0 then
|
|
begin
|
|
Include(Owner.FStates,tvsScrollbarChanged);
|
|
Owner.UpdateScrollbars;
|
|
Owner.Invalidate;
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.IsUpdating: boolean;
|
|
begin
|
|
Result:=FUpdateCount>0;
|
|
end;
|
|
|
|
procedure TTreeNodes.FreeAllNodeData;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
BeginUpdate;
|
|
for i:=0 to TopLvlCount-1 do
|
|
TopLvlItems[i].FreeAllNodeData;
|
|
EndUpdate;
|
|
end;
|
|
|
|
function TTreeNodes.GetEnumerator: TTreeNodesEnumerator;
|
|
begin
|
|
Result := TTreeNodesEnumerator.Create(Self);
|
|
end;
|
|
|
|
procedure TTreeNodes.GrowTopLvlItems;
|
|
begin
|
|
if FTopLvlItems<>nil then begin
|
|
FTopLvlCapacity:=FTopLvlCapacity shl 1;
|
|
ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
end else begin
|
|
FTopLvlCapacity:=MinNodeCapacity;
|
|
GetMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
end;
|
|
//debugln('TTreeNodes.GrowTopLvlItems END FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
|
|
end;
|
|
|
|
function TTreeNodes.GetTopLvlItems(Index: integer): TTreeNode;
|
|
begin
|
|
Result:=FTopLvlItems[Index];
|
|
end;
|
|
|
|
procedure TTreeNodes.ShrinkTopLvlItems;
|
|
var
|
|
OldCapacity: LongInt;
|
|
begin
|
|
if FTopLvlItems<>nil then begin
|
|
OldCapacity:=FTopLvlCapacity;
|
|
FTopLvlCapacity:=FTopLvlCapacity shr 1;
|
|
if FTopLvlCapacity<FTopLvlCount then FTopLvlCapacity:=FTopLvlCount;
|
|
if (FTopLvlCapacity<MinNodeCapacity) then begin
|
|
if (FTopLvlCount>0) then
|
|
FTopLvlCapacity:=MinNodeCapacity
|
|
else
|
|
FTopLvlCapacity:=0;
|
|
end;
|
|
if OldCapacity=FTopLvlCapacity then exit;
|
|
//debugln('TTreeNodes.ShrinkTopLvlItems A FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
|
|
ReAllocMem(FTopLvlItems,SizeOf(Pointer)*FTopLvlCapacity);
|
|
//debugln('TTreeNodes.ShrinkTopLvlItems B FTopLvlCapacity=',FTopLvlCapacity,' FTopLvlCount=',FTopLvlCount,' ',FTopLvlItems<>nil);
|
|
end else begin
|
|
if (FTopLvlCapacity>0) then
|
|
TreeNodeError('TTreeNodes.ShrinkTopLvlItems FTopLvlCapacity>0');
|
|
end;
|
|
end;
|
|
|
|
function TTreeNodes.IndexOfTopLvlItem(Node: TTreeNode): integer;
|
|
begin
|
|
if (Node<>nil) and (Node.Owner=Self) then
|
|
Result:=Node.FIndex
|
|
else
|
|
Result:=-1;
|
|
end;
|
|
|
|
procedure TTreeNodes.MoveTopLvlNode(TopLvlFromIndex, TopLvlToIndex: integer;
|
|
Node: TTreeNode);
|
|
// TopLvlFromIndex = -1 and is insert
|
|
// TopLvlToIndex = -1 is remove
|
|
var i: integer;
|
|
begin
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DebugLn('[TTreeNodes.MoveTopLvlNode] TopLvlFromIndex=',TopLvlFromIndex,
|
|
' TopLvlToIndex=',TopLvlToIndex,' Node.Text=',Node.Text);
|
|
{$ENDIF}
|
|
if TopLvlFromIndex=TopLvlToIndex then exit;
|
|
if (TopLvlFromIndex>=FTopLvlCount) then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
|
|
if (TopLvlToIndex>FTopLvlCount) then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode TopLvlFromIndex>FTopLvlCount');
|
|
if (TopLvlFromIndex>=0) then begin
|
|
Node:=FTopLvlItems[TopLvlFromIndex];
|
|
if (TopLvlToIndex>=0) then begin
|
|
// move node
|
|
if TopLvlFromIndex<TopLvlToIndex then begin
|
|
// move forward
|
|
for i:=TopLvlFromIndex to TopLvlToIndex-1 do begin
|
|
FTopLvlItems[i]:=FTopLvlItems[i+1];
|
|
FTopLvlItems[i].FIndex:=i;
|
|
end;
|
|
end else begin
|
|
// move backward
|
|
for i:=TopLvlToIndex downto TopLvlFromIndex+1 do begin
|
|
FTopLvlItems[i]:=FTopLvlItems[i-1];
|
|
FTopLvlItems[i].FIndex:=i;
|
|
end;
|
|
end;
|
|
FTopLvlItems[TopLvlToIndex]:=Node;
|
|
FTopLvlItems[TopLvlToIndex].FIndex:=TopLvlToIndex;
|
|
end else begin
|
|
// remove node
|
|
if FTopLvlItems<>nil then begin
|
|
for i:=TopLvlFromIndex to FTopLvlCount-2 do begin
|
|
FTopLvlItems[i]:=FTopLvlItems[i+1];
|
|
FTopLvlItems[i].FIndex:=i;
|
|
end;
|
|
end;
|
|
Dec(FTopLvlCount);
|
|
if FTopLvlCount<0 then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode FTopLvlCount<0');
|
|
if FTopLvlCount<(FTopLvlCapacity shr 2) then ShrinkTopLvlItems;
|
|
end;
|
|
end else begin
|
|
if (TopLvlToIndex>=0) then begin
|
|
if Node=nil then
|
|
TreeNodeError('TTreeNodes.MoveTopLvlNode inserting nil');
|
|
// insert node
|
|
if (FTopLvlCount=FTopLvlCapacity) then GrowTopLvlItems;
|
|
inc(FTopLvlCount);
|
|
if FTopLvlItems<>nil then begin
|
|
for i:=FTopLvlCount-1 downto TopLvlToIndex+1 do begin
|
|
FTopLvlItems[i]:=FTopLvlItems[i-1];
|
|
FTopLvlItems[i].FIndex:=i;
|
|
end;
|
|
FTopLvlItems[TopLvlToIndex]:=Node;
|
|
FTopLvlItems[TopLvlToIndex].FIndex:=TopLvlToIndex;
|
|
end;
|
|
end else begin
|
|
// nothing to do
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.MultiSelect(Node: TTreeNode; ClearWholeSelection: Boolean);
|
|
var
|
|
bGoNext, bOnlySiblings, bOnlyVisible: Boolean;
|
|
//
|
|
procedure _TakeNext(var N: TTreeNode);
|
|
begin
|
|
if bGoNext then
|
|
begin
|
|
if bOnlySiblings and bOnlyVisible then
|
|
N := N.GetNextVisibleSibling
|
|
else
|
|
if bOnlySiblings and not bOnlyVisible then
|
|
N := N.GetNextSibling
|
|
else
|
|
if not bOnlySiblings and bOnlyVisible then
|
|
N := N.GetNextVisible
|
|
else
|
|
N := N.GetNext;
|
|
end
|
|
else
|
|
begin
|
|
if bOnlySiblings and bOnlyVisible then
|
|
N := N.GetPrevVisibleSibling
|
|
else
|
|
if bOnlySiblings and not bOnlyVisible then
|
|
N := N.GetPrevSibling
|
|
else
|
|
if not bOnlySiblings and bOnlyVisible then
|
|
N := N.GetPrevVisible
|
|
else
|
|
N := N.GetPrev;
|
|
end;
|
|
end;
|
|
//
|
|
var
|
|
I, FirstNode, LastNode: TTreeNode;
|
|
begin
|
|
if Owner<>nil then Owner.LockSelectionChangeEvent;
|
|
bOnlySiblings := Assigned(Owner) and (msSiblingOnly in Owner.MultiSelectStyle);
|
|
bOnlyVisible := Assigned(Owner) and (msVisibleOnly in Owner.MultiSelectStyle);
|
|
|
|
try
|
|
if FStartMultiSelected=nil then
|
|
begin
|
|
FirstNode := Node;
|
|
FStartMultiSelected := Node;
|
|
end else
|
|
FirstNode := FStartMultiSelected;
|
|
|
|
if ClearWholeSelection then
|
|
begin
|
|
ClearMultiSelection(True);
|
|
end else
|
|
begin
|
|
//clear only last selection
|
|
if Assigned(FLastMultiSelected) then
|
|
begin
|
|
LastNode := FLastMultiSelected;
|
|
bGoNext := (FirstNode.Index <= LastNode.Index);
|
|
I := FirstNode;
|
|
I.MultiSelected:=False;
|
|
while (I<>LastNode) do
|
|
begin
|
|
_TakeNext(I);
|
|
if I=nil then Break;
|
|
I.MultiSelected:=False;
|
|
end;
|
|
end;
|
|
if Assigned(Owner) then
|
|
Owner.Selected := nil;
|
|
end;
|
|
|
|
//select again
|
|
bGoNext := (FirstNode.AbsoluteIndex <= Node.AbsoluteIndex);
|
|
I := FirstNode;
|
|
I.MultiSelected:=True;
|
|
while (I<>Node) do
|
|
begin
|
|
_TakeNext(I);
|
|
if I=nil then Break;
|
|
I.MultiSelected:=True;
|
|
end;
|
|
|
|
FStartMultiSelected := FirstNode;
|
|
FLastMultiSelected := Node;
|
|
finally
|
|
if Owner<>nil then Owner.UnlockSelectionChangeEvent;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.Assign(Source: TPersistent);
|
|
var
|
|
SrcNodes: TTreeNodes;
|
|
SrcStream: TMemoryStream;
|
|
begin
|
|
ClearCache;
|
|
if Source is TTreeNodes then begin
|
|
SrcNodes := TTreeNodes(Source);
|
|
Clear;
|
|
SrcStream := TMemoryStream.Create;
|
|
try
|
|
SrcNodes.WriteData(SrcStream, true);
|
|
SrcStream.Position := 0;
|
|
ReadData(SrcStream);
|
|
finally
|
|
SrcStream.Free;
|
|
end;
|
|
end
|
|
else
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TTreeNodes.DefineProperties(Filer: TFiler);
|
|
|
|
function WriteNodes: Boolean;
|
|
var
|
|
I: Integer;
|
|
Nodes: TTreeNodes;
|
|
begin
|
|
Nodes := TTreeNodes(Filer.Ancestor);
|
|
if Nodes = nil then
|
|
Result := Count > 0
|
|
else if Nodes.Count <> Count then
|
|
Result := True
|
|
else
|
|
begin
|
|
Result := False;
|
|
for I := 0 to Count - 1 do
|
|
begin
|
|
Result := not Item[I].IsEqual(Nodes[I]);
|
|
if Result then Break;
|
|
end
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
inherited DefineProperties(Filer);
|
|
Filer.DefineBinaryProperty('Data', @ReadData, @WriteData, WriteNodes);
|
|
Filer.DefineBinaryProperty('NodeData', @ReadDelphiNodeData, nil, false);
|
|
end;
|
|
|
|
procedure TTreeNodes.ReadData(Stream: TStream);
|
|
var
|
|
I, NewCount, MagicNumber: Integer;
|
|
DelphiNodeInfo: TDelphiNodeInfo;
|
|
StreamVersion: word;
|
|
begin
|
|
Clear;
|
|
// -7 for lcl stream
|
|
Stream.ReadBuffer(MagicNumber,SizeOf(Integer));
|
|
if MagicNumber=LCLStreamID then begin
|
|
// read stream version
|
|
Stream.ReadBuffer(StreamVersion,SizeOf(StreamVersion));
|
|
// read top level node count
|
|
Stream.ReadBuffer(NewCount, SizeOf(NewCount));
|
|
for I := 0 to NewCount - 1 do
|
|
Add(nil, '').ReadData(Stream, StreamVersion);
|
|
end else begin
|
|
// delphi stream
|
|
NewCount:=MagicNumber;
|
|
for I := 0 to NewCount - 1 do
|
|
Add(nil, '').ReadDelphiData(Stream, @DelphiNodeInfo);
|
|
end;
|
|
end;
|
|
|
|
{ Reads the Items.NodeData block from the dfm file written by more recent
|
|
versions of Delphi. Issue #41303 }
|
|
procedure TTreeNodes.ReadDelphiNodeData(Stream: TStream);
|
|
var
|
|
MagicNumber: Byte;
|
|
NewCount: Integer;
|
|
I: Integer;
|
|
begin
|
|
// Read signature
|
|
Stream.ReadBuffer(MagicNumber, SizeOf(Byte));
|
|
// Read top level node count
|
|
Stream.ReadBuffer(NewCount, SizeOf(NewCount));
|
|
// Read top level nodes. Each node reads its children immediately after itself.
|
|
for I := 0 to NewCount - 1 do
|
|
Add(nil, '').ReadDelphiNodeData(Stream, MagicNumber);
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteData(Stream: TStream);
|
|
begin
|
|
WriteData(Stream, false);
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteData(Stream: TStream; WriteDataPointer: boolean);
|
|
var
|
|
ANode: TTreeNode;
|
|
MagicNumber: integer;
|
|
StreamVersion: word;
|
|
begin
|
|
// -7 for lcl stream
|
|
MagicNumber:=LCLStreamID;
|
|
Stream.WriteBuffer(MagicNumber,SizeOf(MagicNumber));
|
|
// write stream version
|
|
if WriteDataPointer then
|
|
StreamVersion:=TTreeNodeWithPointerStreamVersion
|
|
else
|
|
StreamVersion:=TTreeNodeStreamVersion;
|
|
Stream.WriteBuffer(StreamVersion,SizeOf(Word));
|
|
// write top level node count
|
|
Stream.WriteBuffer(FTopLvlCount, SizeOf(Integer));
|
|
// write all nodes recursively
|
|
ANode := GetFirstNode;
|
|
while ANode <> nil do begin
|
|
ANode.WriteData(Stream, StreamVersion);
|
|
ANode := ANode.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.ReadExpandedState(Stream: TStream);
|
|
var
|
|
ItemCount,
|
|
Index: Integer;
|
|
Node: TTreeNode;
|
|
NodeExpanded: Boolean;
|
|
begin
|
|
if Stream.Position < Stream.Size then
|
|
Stream.ReadBuffer(ItemCount, SizeOf(ItemCount))
|
|
else Exit;
|
|
Index := 0;
|
|
Node := GetFirstNode;
|
|
while (Index < ItemCount) and (Node <> nil) do begin
|
|
Stream.ReadBuffer(NodeExpanded, SizeOf(NodeExpanded));
|
|
Node.Expanded := NodeExpanded;
|
|
Inc(Index);
|
|
Node := Node.GetNext;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteExpandedState(Stream: TStream);
|
|
var
|
|
Size: Integer;
|
|
ANode: TTreeNode;
|
|
NodeExpanded: Boolean;
|
|
begin
|
|
Size := SizeOf(Boolean) * Count;
|
|
Stream.WriteBuffer(Size, SizeOf(Size));
|
|
ANode := GetFirstNode;
|
|
while (ANode <> nil) do begin
|
|
NodeExpanded := ANode.Expanded;
|
|
Stream.WriteBuffer(NodeExpanded, SizeOf(Boolean));
|
|
ANode := ANode.GetNext;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.ClearCache;
|
|
begin
|
|
FNodeCache.CacheNode := nil;
|
|
end;
|
|
|
|
procedure TTreeNodes.ConsistencyCheck;
|
|
var
|
|
Node: TTreeNode;
|
|
RealCount, i: integer;
|
|
OldCache: TNodeCache;
|
|
begin
|
|
if FUpdateCount<0 then
|
|
LazTracer.RaiseGDBException('FUpdateCount<0');
|
|
RealCount:=0;
|
|
Node:=GetFirstNode;
|
|
while Node<>nil do begin
|
|
Node.ConsistencyCheck;
|
|
inc(RealCount,Node.SubTreeCount);
|
|
//DebugLn(' ConsistencyCheck: B ',RealCount,',',Node.SubTreeCount);
|
|
Node:=Node.FNextBrother;
|
|
end;
|
|
//DebugLn(' ConsistencyCheck: B ',RealCount,',',FCount);
|
|
if RealCount<>FCount then
|
|
LazTracer.RaiseGDBException('RealCount<>FCount');
|
|
if (FTopLvlCapacity<=0) and (FTopLvlItems<>nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FTopLvlCapacity>0) and (FTopLvlItems=nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if FTopLvlCapacity<FTopLvlCount then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FTopLvlCount<0) then
|
|
LazTracer.RaiseGDBException('');
|
|
for i:=0 to FTopLvlCount-1 do begin
|
|
Node:=FTopLvlItems[i];
|
|
if (i=0) and (Node.FPrevBrother<>nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i>0) and (Node.FPrevBrother<>FTopLvlItems[i-1]) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (i<FTopLvlCount-1) and (Node.FNextBrother<>FTopLvlItems[i+1])
|
|
then begin
|
|
DebugLn(' CONSISTENCY i=%d FTopLvlCount=%d FTopLvlItems[i]=%p FTopLvlItems[i].FNextBrother=%p FTopLvlItems[i+1]=%p',
|
|
[i, FTopLvlCount, Pointer(Node), Pointer(Node.FNextBrother), Pointer(FTopLvlItems[i+1])]);
|
|
LazTracer.RaiseGDBException('');
|
|
end;
|
|
if (i=FTopLvlCount-1) and (Node.FNextBrother<>nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if Node.FIndex<>i then
|
|
LazTracer.RaiseGDBException('');
|
|
end;
|
|
if FNodeCache.CacheNode<>nil then begin
|
|
OldCache:=FNodeCache;
|
|
ClearCache;
|
|
if GetNodeFromIndex(OldCache.CacheIndex)<>OldCache.CacheNode then
|
|
LazTracer.RaiseGDBException('');
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeNodes.WriteDebugReport(const Prefix: string; AllNodes: boolean);
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
DbgOut('%s%s.WriteDebugReport Self=%p', [Prefix, ClassName, Pointer(Self)]);
|
|
ConsistencyCheck;
|
|
DebugLn('');
|
|
if AllNodes then begin
|
|
Node:=GetFirstNode;
|
|
while Node<>nil do begin
|
|
Node.WriteDebugReport(Prefix+' ',true);
|
|
Node:=Node.GetNextSibling;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
type
|
|
TTreeStrings = class(TStrings)
|
|
private
|
|
FOwner: TTreeNodes;
|
|
protected
|
|
function Get(Index: Integer): string; override;
|
|
function GetBufStart(Buffer: PChar; var Level: Integer): PChar;
|
|
function GetCount: Integer; override;
|
|
function GetObject(Index: Integer): TObject; override;
|
|
procedure PutObject(Index: Integer; AObject: TObject); override;
|
|
procedure SetUpdateState(Updating: Boolean); override;
|
|
public
|
|
constructor Create(AnOwner: TTreeNodes);
|
|
function Add(const S: string): Integer; override;
|
|
procedure Clear; override;
|
|
procedure Delete(Index: Integer); override;
|
|
procedure Insert(Index: Integer; const S: string); override;
|
|
procedure LoadTreeFromStream(Stream: TStream);
|
|
procedure SaveTreeToStream(Stream: TStream);
|
|
function ConsistencyCheck: integer;
|
|
procedure WriteDebugReport(const Prefix: string);
|
|
property Owner: TTreeNodes read FOwner;
|
|
end;
|
|
|
|
constructor TTreeStrings.Create(AnOwner: TTreeNodes);
|
|
begin
|
|
inherited Create;
|
|
FOwner := AnOwner;
|
|
end;
|
|
|
|
function TTreeStrings.Get(Index: Integer): string;
|
|
const
|
|
TabChar = #9;
|
|
var
|
|
Level, I: Integer;
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := '';
|
|
Node := Owner.GetNodeFromIndex(Index);
|
|
Level := Node.Level;
|
|
for I := 0 to Level - 1 do
|
|
Result := Result + TabChar;
|
|
Result := Result + Node.Text;
|
|
end;
|
|
|
|
function TTreeStrings.GetBufStart(Buffer: PChar; var Level: Integer): PChar;
|
|
begin
|
|
Level := 0;
|
|
while Buffer^ in [' ', #9] do
|
|
begin
|
|
Inc(Buffer);
|
|
Inc(Level);
|
|
end;
|
|
Result := Buffer;
|
|
end;
|
|
|
|
function TTreeStrings.GetObject(Index: Integer): TObject;
|
|
begin
|
|
Result := TObject(Owner.GetNodeFromIndex(Index).Data);
|
|
end;
|
|
|
|
procedure TTreeStrings.PutObject(Index: Integer; AObject: TObject);
|
|
begin
|
|
Owner.GetNodeFromIndex(Index).Data := AObject;
|
|
end;
|
|
|
|
function TTreeStrings.GetCount: Integer;
|
|
begin
|
|
Result := Owner.Count;
|
|
end;
|
|
|
|
procedure TTreeStrings.Clear;
|
|
begin
|
|
Owner.Clear;
|
|
end;
|
|
|
|
procedure TTreeStrings.Delete(Index: Integer);
|
|
begin
|
|
Owner.GetNodeFromIndex(Index).Delete;
|
|
end;
|
|
|
|
procedure TTreeStrings.SetUpdateState(Updating: Boolean);
|
|
begin
|
|
//SendMessage(Owner.Handle, WM_SETREDRAW, Ord(not Updating), 0);
|
|
if not Updating then
|
|
Owner.Owner.Invalidate;
|
|
end;
|
|
|
|
function TTreeStrings.Add(const S: string): Integer;
|
|
var
|
|
Level, OldLevel, I: Integer;
|
|
NewStr: string;
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := GetCount;
|
|
if (Length(S) = 1) and (S[1] = Chr($1A)) then Exit;
|
|
Node := nil;
|
|
OldLevel := 0;
|
|
NewStr := GetBufStart(PChar(S), Level);
|
|
if Result > 0 then
|
|
begin
|
|
Node := Owner.GetNodeFromIndex(Result - 1);
|
|
OldLevel := Node.Level;
|
|
end;
|
|
if (Level > OldLevel) or (Node = nil) then
|
|
begin
|
|
if Level - OldLevel > 1 then
|
|
TreeViewError(Format('TTreeStrings.Add: Invalid level: Level=%d, OldLevel=%d',
|
|
[Level, OldLevel]));
|
|
end
|
|
else begin
|
|
for I := OldLevel downto Level do
|
|
begin
|
|
Node := Node.Parent;
|
|
if (Node = nil) and (I - Level > 0) then
|
|
TreeViewError(Format('TTreeStrings.Add: Invalid level: Node=nil, I=%d, Level=%d',
|
|
[I, Level]));
|
|
end;
|
|
end;
|
|
Owner.AddChild(Node, NewStr);
|
|
end;
|
|
|
|
procedure TTreeStrings.Insert(Index: Integer; const S: string);
|
|
begin
|
|
with Owner do
|
|
Insert(GetNodeFromIndex(Index), S);
|
|
end;
|
|
|
|
procedure TTreeStrings.LoadTreeFromStream(Stream: TStream);
|
|
var
|
|
List: TStringList;
|
|
ANode, NextNode: TTreeNode;
|
|
ALevel, i: Integer;
|
|
CurrStr: string;
|
|
begin
|
|
List := TStringList.Create;
|
|
Owner.BeginUpdate;
|
|
try
|
|
Clear;
|
|
List.LoadFromStream(Stream);
|
|
ANode := nil;
|
|
for i := 0 to List.Count - 1 do
|
|
begin
|
|
CurrStr := GetBufStart(PChar(List[i]), ALevel);
|
|
if ANode = nil then
|
|
ANode := Owner.AddChild(nil, CurrStr)
|
|
else if ANode.Level = ALevel then
|
|
ANode := Owner.AddChild(ANode.Parent, CurrStr)
|
|
else if ANode.Level = (ALevel - 1) then
|
|
ANode := Owner.AddChild(ANode, CurrStr)
|
|
else if ANode.Level > ALevel then
|
|
begin
|
|
NextNode := ANode.Parent;
|
|
while NextNode.Level > ALevel do
|
|
NextNode := NextNode.Parent;
|
|
ANode := Owner.AddChild(NextNode.Parent, CurrStr);
|
|
end
|
|
else TreeViewError(Format('TTreeStrings.LoadTreeFromStream: Level=%d, CurrStr="%s"',
|
|
[ALevel, CurrStr]));
|
|
end;
|
|
finally
|
|
Owner.EndUpdate;
|
|
List.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TTreeStrings.SaveTreeToStream(Stream: TStream);
|
|
const
|
|
TabChar = #9;
|
|
EndOfLine = #13#10;
|
|
var
|
|
i: Integer;
|
|
ANode: TTreeNode;
|
|
NodeStr: string;
|
|
begin
|
|
if Count > 0 then
|
|
begin
|
|
ANode := Owner[0];
|
|
while ANode <> nil do
|
|
begin
|
|
NodeStr := '';
|
|
for i := 0 to ANode.Level - 1 do NodeStr := NodeStr + TabChar;
|
|
NodeStr := NodeStr + ANode.Text + EndOfLine;
|
|
Stream.Write(Pointer(NodeStr)^, Length(NodeStr));
|
|
ANode := ANode.GetNext;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TTreeStrings.ConsistencyCheck: integer;
|
|
begin
|
|
Result:=0;
|
|
end;
|
|
|
|
procedure TTreeStrings.WriteDebugReport(const Prefix: string);
|
|
begin
|
|
DebugLn('%sTTreeStrings.WriteDebugReport Self=%p Consistency=%d', [Prefix, Pointer(Self), ConsistencyCheck]);
|
|
end;
|
|
|
|
|
|
{ TCustomTreeView }
|
|
|
|
constructor TCustomTreeView.Create(AnOwner: TComponent);
|
|
var
|
|
Details: TThemedElementDetails;
|
|
begin
|
|
inherited Create(AnOwner);
|
|
ControlStyle := ControlStyle - [csCaptureMouse]
|
|
+ [csDisplayDragImage, csReflector];
|
|
Width := 121;
|
|
Height := 97;
|
|
Color := clWindow;
|
|
|
|
FSBVertShowing:=-1;
|
|
FSBHorzShowing:=-1;
|
|
TabStop := True;
|
|
ParentColor := False;
|
|
// FBackgroundColor := clWindow;
|
|
FDefItemHeight := DefaultTreeNodeHeight;
|
|
FDefItemSpace := ScaleY(2, 96);
|
|
FExpandSignType := tvestTheme;
|
|
FExpandSignSize := -1;
|
|
FExpandSignWidth := 2;
|
|
Details := ThemeServices.GetElementDetails(ttGlyphOpened);
|
|
FThemeExpandSignSize := ThemeServices.GetDetailSizeForPPI(Details, Font.PixelsPerInch).cx;
|
|
FTreeNodes := CreateNodes;
|
|
BorderStyle := bsSingle;
|
|
BorderWidth := 0;
|
|
FMultiSelectStyle := DefaultMultiSelectStyle;
|
|
FOptions := DefaultTreeViewOptions;
|
|
Items.KeepCollapsedNodes:=KeepCollapsedNodes;
|
|
FScrollBars:=ssBoth;
|
|
FDragImage := TDragImageList.CreateSize(32, 32);
|
|
FIndent:=-1;
|
|
FChangeTimer := TTimer.Create(Self);
|
|
FChangeTimer.Enabled := False;
|
|
FChangeTimer.Interval := 1;
|
|
FChangeTimer.OnTimer := @ChangeTimer;
|
|
FImageChangeLink := TChangeLink.Create;
|
|
FImageChangeLink.OnChange := @ImageListChange;
|
|
FSelectedColor:=clHighlight;
|
|
FSelectedFontColor:=clWhite;
|
|
FSelectedFontColorUsed:=false;
|
|
fSeparatorColor:=clGray;
|
|
FStateChangeLink := TChangeLink.Create;
|
|
FStateChangeLink.OnChange := @ImageListChange;
|
|
FStates:=[tvsMaxLvlNeedsUpdate,tvsMaxRightNeedsUpdate,tvsScrollbarChanged];
|
|
FTreeLineColor := clWindowFrame;
|
|
FTreeLinePenStyle := psPattern;
|
|
SetLength(FTreeLinePenPattern, 2);
|
|
FTreeLinePenPattern[0] := 1;
|
|
FTreeLinePenPattern[1] := 1;
|
|
FExpandSignColor := clWindowFrame;
|
|
FHotTrackColor := clNone;
|
|
FHotTrackedPrevNodeIdx := -1;
|
|
FDisabledFontColor := clGrayText;
|
|
FPathDelimiter := '/';
|
|
// Accessibility
|
|
AccessibleDescription := rsTTreeViewAccessibilityDescription;
|
|
AccessibleRole := larTreeView;
|
|
FAccessibilityOn := WidgetSet.GetLCLCapability(lcAccessibilitySupport) = LCL_CAPABILITY_YES;
|
|
|
|
FDragScrollMargin := 20; // Height of 2 areas, on top/bottom of treeview, which auto-scroll treeview up/down
|
|
FDragScrollTimer := TTimer.Create(Self);
|
|
FDragScrollTimer.Enabled := false;
|
|
FDragScrollTimer.Interval := 150;
|
|
FDragScrollTimer.OnTimer := @DragScrollTimerTick;
|
|
end;
|
|
|
|
destructor TCustomTreeView.Destroy;
|
|
begin
|
|
LockSelectionChangeEvent; // prevent change event during destroying
|
|
|
|
Images:=nil;
|
|
FreeThenNil(FTreeNodes);
|
|
FreeThenNil(FSaveItems);
|
|
FreeThenNil(FDragImage);
|
|
FreeThenNil(FImageChangeLink);
|
|
FreeThenNil(FStateChangeLink);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TCustomTreeView.CreateWnd;
|
|
begin
|
|
Exclude(FStates,tvsStateChanging);
|
|
FSBHorzShowing:=-1;
|
|
FSBVertShowing:=-1;
|
|
inherited CreateWnd;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Click;
|
|
begin
|
|
if not FMouseDownOnFoldingSign then
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DblClick;
|
|
begin
|
|
if not FMouseDownOnFoldingSign then
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.TripleClick;
|
|
begin
|
|
if not FMouseDownOnFoldingSign then
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.QuadClick;
|
|
begin
|
|
if not FMouseDownOnFoldingSign then
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.InitializeWnd;
|
|
begin
|
|
inherited InitializeWnd;
|
|
UpdateDefaultItemHeight;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Invalidate;
|
|
begin
|
|
if tvsPainting in FStates then exit;
|
|
inherited Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EraseBackground(DC: HDC);
|
|
begin
|
|
// everything is painted, so erasing the background is not needed
|
|
end;
|
|
|
|
procedure TCustomTreeView.DestroyWnd;
|
|
begin
|
|
Include(FStates, tvsStateChanging);
|
|
inherited DestroyWnd;
|
|
if Canvas <> nil then
|
|
TControlCanvas(Canvas).FreeHandle;
|
|
FLastHorzScrollInfo.cbSize := 0;
|
|
FLastVertScrollInfo.cbSize := 0;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoAutoAdjustLayout(
|
|
const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double
|
|
);
|
|
begin
|
|
inherited DoAutoAdjustLayout(AMode, AXProportion, AYProportion);
|
|
|
|
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
|
|
begin
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomTreeView.DoAutoAdjustLayout'){$ENDIF};
|
|
try
|
|
if DefaultItemHeightIsStored then
|
|
DefaultItemHeight := Round(DefaultItemHeight*AYProportion);
|
|
if IndentIsStored then
|
|
FIndent := Round(FIndent*AXProportion);
|
|
if ExpandSignSizeIsStored then
|
|
FExpandSignSize := Round(FExpandSignSize*AXProportion);
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomTreeView.DoAutoAdjustLayout'){$ENDIF};
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoCreateNodeClass(var NewNodeClass: TTreeNodeClass);
|
|
begin
|
|
if Assigned(OnCreateNodeClass) then
|
|
OnCreateNodeClass(Self,NewNodeClass);
|
|
end;
|
|
|
|
procedure TCustomTreeView.BeginAutoDrag;
|
|
begin
|
|
BeginDrag(False);
|
|
end;
|
|
|
|
procedure TCustomTreeView.BeginEditing(ANode: TTreeNode);
|
|
var
|
|
ARect: TRect;
|
|
begin
|
|
//DebugLn(['TCustomTreeView.BeginEditing tvsIsEditing=',tvsIsEditing in FStates,' Selected=',Selected<>nil]);
|
|
if (tvsIsEditing in FStates) or (ANode=nil) then exit;
|
|
if (not CanEdit(ANode)) or (not ANode.Visible) then exit;
|
|
// if we are asked to edit another node while one is already being edited then
|
|
// stop editing that node
|
|
if FEditingItem <> nil then
|
|
EndEditing;
|
|
FEditingItem := ANode;
|
|
// make node visible (this will cancel editing, so call this first)
|
|
EnsureNodeIsVisible(ANode);
|
|
|
|
Include(FStates,tvsIsEditing);
|
|
if FEditor=nil then begin
|
|
FEditor:=TEdit.Create(Self);
|
|
FEditor.OnEditingDone:=@EditorEditingDone;
|
|
FEditor.OnKeyDown:=@EditorKeyDown;
|
|
end;
|
|
ARect:=Rect(Max(BorderWidth,ANode.DisplayTextLeft),ANode.Top-ScrolledTop,
|
|
ClientWidth-BorderWidth,ANode.Bottom-ScrolledTop);
|
|
FEditor.BoundsRect:=ARect;
|
|
FEditor.AnchorParallel(akLeft,ARect.Left,Self);
|
|
FEditor.AnchorParallel(akRight,BorderWidth,Self);
|
|
FEditor.Visible:=true;
|
|
FEditor.Parent:=Self;
|
|
FEditor.Text:=ANode.Text;
|
|
FEditor.SelectAll;
|
|
FEditor.SetFocus;
|
|
end;
|
|
|
|
procedure TCustomTreeView.BeginUpdate;
|
|
begin
|
|
Items.BeginUpdate;
|
|
LockSelectionChangeEvent;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EndUpdate;
|
|
begin
|
|
UnlockSelectionChangeEvent;
|
|
if Items.FUpdateCount<=0 then LazTracer.RaiseGDBException('TCustomTreeView.EndUpdate');
|
|
Items.EndUpdate;
|
|
if Items.FUpdateCount=0 then begin
|
|
// ToDo: only refresh if something changed
|
|
UpdateScrollBars;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.AlphaSort: Boolean;
|
|
begin
|
|
Result := CustomSort(nil);
|
|
end;
|
|
|
|
function TCustomTreeView.CustomSort(SortProc: TTreeNodeCompare): Boolean;
|
|
var Node: TTreeNode;
|
|
begin
|
|
Result := False;
|
|
if FTreeNodes.Count>0 then begin
|
|
BeginUpdate;
|
|
if not assigned(SortProc) then SortProc := @DefaultTreeViewSort;
|
|
FTreeNodes.SortTopLevelNodes(SortProc);
|
|
|
|
Node := FTreeNodes.GetFirstNode;
|
|
while Node <> nil do begin
|
|
if (Node.GetFirstChild<>nil) then Node.CustomSort(SortProc);
|
|
Node := Node.GetNext;
|
|
end;
|
|
Items.ClearCache;
|
|
FStates:= FStates+[tvsTopsNeedsUpdate, tvsTopItemNeedsUpdate,
|
|
tvsBottomItemNeedsUpdate,tvsScrollbarChanged];
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.DefaultItemHeightIsStored: Boolean;
|
|
begin
|
|
Result := not(tvoAutoItemHeight in Options);
|
|
end;
|
|
|
|
function TCustomTreeView.DefaultTreeViewSort(Node1, Node2: TTreeNode): Integer;
|
|
begin
|
|
if Assigned(OnCompare) then begin
|
|
Result:=0;
|
|
OnCompare(Node1.TreeView,Node1, Node2, Result);
|
|
end else
|
|
Result := Utf8CompareStr(Node1.Text,Node2.Text);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetAutoExpand(Value: Boolean);
|
|
begin
|
|
if AutoExpand <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoAutoExpand)
|
|
else
|
|
Exclude(FOptions,tvoAutoExpand);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetHotTrack(Value: Boolean);
|
|
begin
|
|
if HotTrack <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoHotTrack)
|
|
else
|
|
Exclude(FOptions,tvoHotTrack);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetRowSelect(Value: Boolean);
|
|
begin
|
|
if RowSelect <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoRowSelect)
|
|
else
|
|
Exclude(FOptions,tvoRowSelect);
|
|
if FSelectedNode<>nil then
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrollBars(const Value: TScrollStyle);
|
|
begin
|
|
if (FScrollBars <> Value) then begin
|
|
FScrollBars := Value;
|
|
Include(FStates,tvsScrollbarChanged);
|
|
UpdateScrollBars;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrolledLeft(AValue: integer);
|
|
var
|
|
OldScrolledLeft: Integer;
|
|
begin
|
|
OldScrolledLeft := FScrolledLeft;
|
|
if AValue<0 then AValue:=0;
|
|
if AValue=FScrolledLeft then exit;
|
|
if AValue>GetMaxScrollLeft then AValue:=GetMaxScrollLeft;
|
|
if AValue=FScrolledLeft then exit;
|
|
EndEditing(true);
|
|
FScrolledLeft:=AValue;
|
|
ScrollView(OldScrolledLeft-FScrolledLeft, 0);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetScrolledTop(AValue: integer);
|
|
var
|
|
OldScrolledTop: Integer;
|
|
begin
|
|
OldScrolledTop:=FScrolledTop;
|
|
if FScrolledTop=AValue then exit;
|
|
if AValue<0 then AValue:=0;
|
|
if AValue>GetMaxScrollTop then AValue:=GetMaxScrollTop;
|
|
if AValue=FScrolledTop then exit;
|
|
EndEditing(true);
|
|
FScrolledTop:=AValue;
|
|
FStates:=FStates+[tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
ScrollView(0, OldScrolledTop-FScrolledTop);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetToolTips(Value: Boolean);
|
|
begin
|
|
if ToolTips <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoToolTips)
|
|
else
|
|
Exclude(FOptions,tvoToolTips);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSortType(Value: TSortType);
|
|
begin
|
|
if SortType <> Value then begin
|
|
FSortType := Value;
|
|
if ((SortType in [stData, stBoth]) and Assigned(OnCompare)) or
|
|
(SortType in [stText, stBoth]) then
|
|
AlphaSort;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetBackgroundColor(Value: TColor);
|
|
begin
|
|
if Color<>Value then begin
|
|
Color:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSelectedColor(Value: TColor);
|
|
begin
|
|
if FSelectedColor<>Value then begin
|
|
FSelectedColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSelectedFontColor(Value: TColor);
|
|
begin
|
|
if FSelectedFontColor<>Value then begin
|
|
FSelectedFontColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Paint;
|
|
begin
|
|
DoPaint;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetDragMode(Value: TDragMode);
|
|
begin
|
|
inherited SetDragMode(Value);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetOptions(NewOptions: TTreeViewOptions);
|
|
var ChangedOptions: TTreeViewOptions;
|
|
begin
|
|
if FOptions=NewOptions then exit;
|
|
ChangedOptions:=(FOptions-NewOptions)+(NewOptions-FOptions);
|
|
FOptions:=NewOptions;
|
|
if tvoKeepCollapsedNodes in ChangedOptions then
|
|
Items.KeepCollapsedNodes:=(tvoKeepCollapsedNodes in FOptions);
|
|
if (tvoReadOnly in ChangedOptions) and (not (tvoReadOnly in FOptions)) then
|
|
EndEditing;
|
|
if (tvoAllowMultiselect in ChangedOptions) then begin
|
|
if (tvoAllowMultiselect in FOptions) then begin
|
|
if Selected<>nil then
|
|
Selected.MultiSelected:=true;
|
|
end else begin
|
|
Items.ClearMultiSelection;
|
|
end;
|
|
end;
|
|
if tvoAutoItemHeight in ChangedOptions then
|
|
UpdateDefaultItemHeight;
|
|
if ([tvoHideSelection,tvoReadOnly,tvoShowButtons,tvoShowRoot,tvoShowLines]
|
|
* ChangedOptions)<>[]
|
|
then
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateDefaultItemHeight;
|
|
var
|
|
NewDefItemHeight: Integer;
|
|
ImageSize, StateImageSize: TSize;
|
|
begin
|
|
if (tvoAutoItemHeight in FOptions)
|
|
and HandleAllocated and Canvas.HandleAllocated then begin
|
|
NewDefItemHeight:=Canvas.TextHeight(TVAutoHeightString)+FDefItemSpace;
|
|
ImageSize := GetImageSize;
|
|
if Assigned(FStateImages) then
|
|
StateImageSize := StateImages.SizeForPPI[StateImagesWidth, Font.PixelsPerInch];
|
|
if NewDefItemHeight<FDefItemSpace then NewDefItemHeight:=FDefItemSpace;
|
|
if (ImageSize.cy > 0) and (ImageSize.cy + FDefItemSpace > NewDefItemHeight) then
|
|
NewDefItemHeight:=ImageSize.cy+FDefItemSpace;
|
|
if (StateImages<>nil) and (StateImageSize.cy+FDefItemSpace>NewDefItemHeight) then
|
|
NewDefItemHeight:=StateImageSize.cy+FDefItemSpace;
|
|
if Odd(NewDefItemHeight) then Inc(NewDefItemHeight);
|
|
if NewDefItemHeight<>FDefItemHeight then begin
|
|
FDefItemHeight:=NewDefItemHeight;
|
|
FStates:=FStates+[tvsTopsNeedsUpdate,tvsTopItemNeedsUpdate,tvsBottomItemNeedsUpdate];
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateAllTops;
|
|
var
|
|
CurTop: integer;
|
|
|
|
procedure CalculateTops(Node: TTreeNode);
|
|
begin
|
|
while Node<>nil do begin
|
|
if Node.Visible then begin
|
|
Node.fTop:=CurTop;
|
|
inc(CurTop, Node.Height);
|
|
if Node.Expanded then
|
|
CalculateTops(Node.GetFirstChild);
|
|
end;
|
|
Node:=Node.GetNextSibling;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if not (tvsTopsNeedsUpdate in FStates) then exit;
|
|
CurTop:=0;
|
|
CalculateTops(Items.GetFirstVisibleNode);
|
|
Exclude(FStates,tvsTopsNeedsUpdate);
|
|
Include(FStates,tvsScrollbarChanged);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateMaxLvl;
|
|
|
|
procedure LookInChildsAndBrothers(Node: TTreeNode; CurLvl: integer);
|
|
begin
|
|
if Node=nil then exit;
|
|
if CurLvl>FMaxLvl then FMaxLvl:=CurLvl;
|
|
LookInChildsAndBrothers(Node.GetFirstChild,CurLvl+1);
|
|
LookInChildsAndBrothers(Node.GetNextSibling,CurLvl);
|
|
end;
|
|
|
|
begin
|
|
if not (tvsMaxLvlNeedsUpdate in FStates) then exit;
|
|
FMaxLvl:=0;
|
|
LookInChildsAndBrothers(Items.GetFirstNode,1);
|
|
Exclude(FStates,tvsMaxRightNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateMaxRight;
|
|
const
|
|
LargeItemCount = 100;
|
|
ReservedWidth = 100;
|
|
var
|
|
Node: TTreeNode;
|
|
i: integer;
|
|
FMaxTextLen, AIndent: Integer;
|
|
Cnt: Integer;
|
|
begin
|
|
if not (tvsMaxRightNeedsUpdate in FStates) then exit;
|
|
FMaxRight := 0;
|
|
FMaxTextLen := 0;
|
|
Node := Items.GetFirstNode;
|
|
Cnt := 0;
|
|
AIndent := Indent;
|
|
while Node <> nil do
|
|
begin
|
|
if not Node.ParentsExpandedVisible then
|
|
begin
|
|
Node := Node.GetNext;
|
|
Continue;
|
|
end;
|
|
inc(Cnt);
|
|
if (Cnt < LargeItemCount) then
|
|
begin
|
|
i := Node.DisplayTextRight + ScrolledLeft + AIndent div 2;
|
|
end else
|
|
begin
|
|
// computing DisplayTextRight is too expensive when the tree
|
|
// has hundreds of nodes
|
|
// => use a heuristic
|
|
if length(Node.Text) > FMaxTextLen then
|
|
i := Node.DisplayTextRight + ScrolledLeft + ReservedWidth
|
|
else
|
|
i := FMaxRight;
|
|
end;
|
|
if FMaxRight < i then
|
|
begin
|
|
FMaxRight := i;
|
|
FMaxTextLen := length(Node.Text);
|
|
end;
|
|
Node := Node.GetNext;
|
|
end;
|
|
Exclude(FStates, tvsMaxRightNeedsUpdate);
|
|
Include(FStates, tvsScrollbarChanged);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateTopItem;
|
|
begin
|
|
//DebugLn('TCustomTreeView.UpdateTopItem tvsTopItemNeedsUpdate in FStates=',tvsTopItemNeedsUpdate in FStates);
|
|
if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate]=[]) then exit;
|
|
FTopItem:=GetNodeAtY(BorderWidth);
|
|
Exclude(FStates,tvsTopItemNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateBottomItem;
|
|
begin
|
|
if (FStates*[tvsTopItemNeedsUpdate,tvsTopsNeedsUpdate,tvsBottomItemNeedsUpdate]=[])
|
|
then exit;
|
|
// if not (tvsBottomItemNeedsUpdate in FStates) then exit; already above
|
|
FBottomItem:=TopItem;
|
|
while (FBottomItem<>nil) and (FBottomItem.GetNextVisible<>nil) do
|
|
FBottomItem:=FBottomItem.GetNextVisible;
|
|
Exclude(FStates,tvsBottomItemNeedsUpdate);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetBottomItem(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated and (Value <> nil) then begin
|
|
Value.MakeVisible;
|
|
ScrolledTop:=Value.Top+Value.Height-ClientHeight;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSeparatorColor(const AValue: TColor);
|
|
begin
|
|
if fSeparatorColor=AValue then exit;
|
|
fSeparatorColor:=AValue;
|
|
if tvoShowSeparators in Options then
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowButton(Value: Boolean);
|
|
begin
|
|
if ShowButtons <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowButtons)
|
|
else
|
|
Exclude(FOptions,tvoShowButtons);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowLines(Value: Boolean);
|
|
begin
|
|
if ShowLines <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowLines)
|
|
else
|
|
Exclude(FOptions,tvoShowLines);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowRoot(Value: Boolean);
|
|
begin
|
|
if ShowRoot <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowRoot)
|
|
else
|
|
Exclude(FOptions,tvoShowRoot);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowScrollBar(Which: Integer; AShow: Boolean);
|
|
begin
|
|
if ((Which in [SB_Horz, SB_BOTH]) and (FSBHorzShowing<>Ord(AShow)))
|
|
or ((Which in [SB_Vert, SB_BOTH]) and (FSBVertShowing<>Ord(AShow)))
|
|
then
|
|
ShowScrollBar(Handle, Which, AShow);
|
|
|
|
if Which in [SB_Horz, SB_BOTH] then
|
|
FSBHorzShowing:=Ord(AShow);
|
|
if Which in [SB_Vert, SB_BOTH] then
|
|
FSBVertShowing:=Ord(AShow);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetShowSeparators(Value: Boolean);
|
|
begin
|
|
if ShowSeparators <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoShowSeparators)
|
|
else
|
|
Exclude(FOptions,tvoShowSeparators);
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetKeepCollapsedNodes(Value: Boolean);
|
|
begin
|
|
if KeepCollapsedNodes=Value then exit;
|
|
if Value then
|
|
Include(FOptions,tvoKeepCollapsedNodes)
|
|
else
|
|
Exclude(FOptions,tvoKeepCollapsedNodes);
|
|
Items.KeepCollapsedNodes:=Value;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetMultiSelect(const AValue: Boolean);
|
|
begin
|
|
if MultiSelect <> AValue then
|
|
begin
|
|
ClearSelection;
|
|
if AValue then
|
|
Include(FOptions,tvoAllowMultiselect)
|
|
else
|
|
Exclude(FOptions,tvoAllowMultiselect);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetMultiSelectStyle(const AValue: TMultiSelectStyle);
|
|
begin
|
|
if FMultiSelectStyle=AValue then exit;
|
|
FMultiSelectStyle:=AValue;
|
|
// there must be at least one multiselectstyle according to docs
|
|
// http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/ComCtrls_TCustomTreeView_MultiSelectStyle.html
|
|
if FMultiSelectStyle = [] then
|
|
FMultiSelectStyle := DefaultMultiSelectStyle;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetReadOnly(Value: Boolean);
|
|
begin
|
|
if ReadOnly = Value then exit;
|
|
if Value then
|
|
Include(FOptions,tvoReadOnly)
|
|
else
|
|
Exclude(FOptions,tvoReadOnly);
|
|
if not Value then EndEditing;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetRightClickSelect(Value: Boolean);
|
|
begin
|
|
if Value then
|
|
Include(FOptions,tvoRightClickSelect)
|
|
else
|
|
Exclude(FOptions,tvoRightClickSelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetHideSelection(Value: Boolean);
|
|
begin
|
|
if HideSelection <> Value then begin
|
|
if Value then
|
|
Include(FOptions,tvoHideSelection)
|
|
else
|
|
Exclude(FOptions,tvoHideSelection);
|
|
if FSelectedNode<>nil then Invalidate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxLvl: integer;
|
|
begin
|
|
UpdateMaxRight;
|
|
Result:=FMaxRight;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxScrollLeft: integer;
|
|
begin
|
|
UpdateMaxRight;
|
|
Result:=FMaxRight-(ClientWidth-2*BorderWidth);
|
|
if Result<0 then Result:=0;
|
|
end;
|
|
|
|
function TCustomTreeView.GetMaxScrollTop: integer;
|
|
var
|
|
LastVisibleNode: TTreeNode;
|
|
begin
|
|
LastVisibleNode:=Items.GetLastExpandedSubNode;
|
|
if LastVisibleNode=nil then
|
|
Result:=0
|
|
else begin
|
|
Result:=LastVisibleNode.Top+LastVisibleNode.Height
|
|
-ClientHeight+2*integer(BorderWidth);
|
|
//DebugLn('>>> ',LastVisibleNode.Text,' ',Result);
|
|
if Result<0 then Result:=0;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeAtY(Y: Integer): TTreeNode;
|
|
// search in all expanded nodes for the node at the screen coordinate Y
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := nil;
|
|
if not Assigned(Items) then
|
|
Exit;
|
|
if (Y >= BorderWidth) and (Y < ClientHeight - BorderWidth) then
|
|
begin
|
|
inc(Y, FScrolledTop - BorderWidth);
|
|
i := IndexOfNodeAtTop(Items.FTopLvlItems, Items.FTopLvlCount, Y);
|
|
if i >= 0 then
|
|
begin
|
|
Result := Items.FTopLvlItems[i];
|
|
while Result.HasStates([nsExpanded,nsVisible]) do
|
|
begin
|
|
i := IndexOfNodeAtTop(Result.FItems, Result.FCount, Y);
|
|
if i >= 0 then
|
|
Result := Result.Items[i]
|
|
else
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeDrawAreaWidth: integer;
|
|
begin
|
|
Result:=ClientWidth-BorderWidth*2;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeDrawAreaHeight: integer;
|
|
begin
|
|
Result:=ClientHeight-BorderWidth*2;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeAt(X, Y: Integer): TTreeNode;
|
|
var
|
|
b: Boolean;
|
|
begin
|
|
Result := GetNodeAtY(Y);
|
|
if Result = nil then Exit;
|
|
if tvoRowSelect in Options then // row select
|
|
b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
|
|
else
|
|
b := (X < Result.DisplayStateIconLeft) or (X >= Result.DisplayTextRight);
|
|
if b then
|
|
Result := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetNodeWithExpandSignAt(X, Y: Integer): TTreeNode;
|
|
var
|
|
b: Boolean;
|
|
begin
|
|
Result := GetNodeAtY(Y);
|
|
if Result = nil then Exit;
|
|
if tvoRowSelect in Options then // row select
|
|
b := (X < BorderWidth) or (X >= ClientWidth - BorderWidth)
|
|
else // need to include DisplayExpandSignLeft
|
|
b := (X < Result.DisplayExpandSignLeft) or (X >= Result.DisplayTextRight);
|
|
if b then
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TCustomTreeView.GetInsertMarkAt(X, Y: Integer;
|
|
out AnInsertMarkNode: TTreeNode; out AnInsertMarkType: TTreeViewInsertMarkType);
|
|
var
|
|
ANode: TTreeNode;
|
|
NodeRect: TRect;
|
|
NodeMidY: integer;
|
|
begin
|
|
AnInsertMarkNode:=nil;
|
|
AnInsertMarkType:=tvimNone;
|
|
if Y<0 then Y:=0;
|
|
if Y>=ClientHeight then Y:=ClientHeight-1;
|
|
ANode:=GetNodeAtY(Y);
|
|
if ANode<>nil then
|
|
begin
|
|
NodeRect:=ANode.DisplayRect(false);
|
|
NodeMidY:=NodeRect.Top + (NodeRect.Bottom-NodeRect.Top) div 2;
|
|
AnInsertMarkNode:=ANode;
|
|
if (X>AnInsertMarkNode.DisplayExpandSignRight) then
|
|
if Y>=NodeMidY then begin
|
|
// insert as first child of pointed node
|
|
AnInsertMarkType:=tvimAsFirstChild;
|
|
end else begin
|
|
// insert as previous sibling of pointed node
|
|
AnInsertMarkType:=tvimAsPrevSibling;
|
|
end else begin
|
|
if Y>=NodeMidY then begin
|
|
if ANode.Expanded and ANode.HasChildren then begin
|
|
// insert as first child of pointed node
|
|
AnInsertMarkType:=tvimAsFirstChild;
|
|
end else begin
|
|
// insert as next sibling of pointed node
|
|
AnInsertMarkType:=tvimAsNextSibling;
|
|
end;
|
|
end else begin
|
|
// insert as previous sibling of pointed node
|
|
AnInsertMarkType:=tvimAsPrevSibling;
|
|
end;
|
|
end;
|
|
end else begin
|
|
// insert behind all nodes
|
|
ANode:=Items.GetLastExpandedSubNode;
|
|
if ANode<>nil then begin
|
|
AnInsertMarkNode:=ANode;
|
|
if X>AnInsertMarkNode.DisplayExpandSignRight then
|
|
// insert as first child of last visible node
|
|
AnInsertMarkType:=tvimAsFirstChild
|
|
else
|
|
// insert as next sibling of last visible node
|
|
AnInsertMarkType:=tvimAsNextSibling;
|
|
end else begin
|
|
// insert as new root
|
|
AnInsertMarkNode:=nil;
|
|
AnInsertMarkType:=tvimAsFirstChild;
|
|
end;
|
|
end;
|
|
|
|
// normalize (try to replace tvimAsPrevSibling)
|
|
if (AnInsertMarkType=tvimAsPrevSibling) and (AnInsertMarkNode<>nil) then begin
|
|
if (AnInsertMarkNode.GetPrevSibling<>nil) then begin
|
|
if (AnInsertMarkNode.GetPrevSibling.Expanded=false)
|
|
and (AnInsertMarkNode.GetPrevSibling.IsVisible) then begin
|
|
AnInsertMarkNode:=AnInsertMarkNode.GetPrevSibling;
|
|
AnInsertMarkType:=tvimAsNextSibling;
|
|
end;
|
|
end else if (AnInsertMarkNode.Parent<>nil)
|
|
and (AnInsertMarkNode.Parent.IsVisible) then begin
|
|
AnInsertMarkNode:=AnInsertMarkNode.Parent;
|
|
AnInsertMarkType:=tvimAsFirstChild;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetInsertMark(AnInsertMarkNode: TTreeNode;
|
|
AnInsertMarkType: TTreeViewInsertMarkType);
|
|
begin
|
|
InsertMarkNode:=AnInsertMarkNode;
|
|
InsertMarkType:=AnInsertMarkType;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetInsertMarkAt(X, Y: integer);
|
|
var
|
|
AnInsertMarkNode: TTreeNode;
|
|
AnInsertMarkType: TTreeViewInsertMarkType;
|
|
begin
|
|
GetInsertMarkAt(X,Y,AnInsertMarkNode,AnInsertMarkType);
|
|
SetInsertMark(AnInsertMarkNode,AnInsertMarkType);
|
|
end;
|
|
|
|
function TCustomTreeView.GetHitTestInfoAt(X, Y: Integer): THitTests;
|
|
// ToDo: Set also flags [htAbove, htBelow, htOnRight, htToLeft, htToRight];
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
Result := [];
|
|
if (X>=0) and (X<ClientWidth) and (Y>=0) and (Y<ClientHeight)
|
|
then begin
|
|
Node:=GetNodeAtY(Y);
|
|
if Node<>nil then begin
|
|
Include(Result,htOnItem);
|
|
if X<Node.DisplayExpandSignLeft then
|
|
Include(Result,htOnIndent)
|
|
else if X<Node.DisplayStateIconLeft then
|
|
Include(Result,htOnButton)
|
|
else if X<Node.DisplayIconLeft then
|
|
Include(Result,htOnStateIcon)
|
|
else if X<Node.DisplayTextLeft then
|
|
Include(Result,htOnIcon)
|
|
else if X<Node.DisplayTextRight then
|
|
Include(Result,htOnLabel);
|
|
end else
|
|
Include(Result,htNowhere);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTreeLineColor(Value: TColor);
|
|
begin
|
|
if FTreeLineColor<>Value then begin
|
|
FTreeLineColor:=Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTreeNodes(Value: TTreeNodes);
|
|
begin
|
|
Items.Assign(Value);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetIndent(Value: Integer);
|
|
begin
|
|
if Value <> Indent then begin
|
|
FIndent := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.FullExpand;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
BeginUpdate;
|
|
try
|
|
Node := Items.GetFirstNode;
|
|
while Node <> nil do begin
|
|
Node.Expand(True);
|
|
Node := Node.GetNextSibling;
|
|
end;
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.FullCollapse;
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
BeginUpdate;
|
|
try
|
|
Node := Items.GetFirstNode;
|
|
while Node <> nil do begin
|
|
Node.Collapse(True);
|
|
Node := Node.GetNextSibling;
|
|
end;
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.IsNodeVisible(ANode: TTreeNode): Boolean;
|
|
begin
|
|
Result:=(ANode<>nil) and (ANode.Visible) and (ANode.ParentsExpandedVisible);
|
|
if Result then begin
|
|
//DebugLn('[TCustomTreeView.IsNodeVisible] B Node=',DbgS(ANode),
|
|
// ' ',dbgs(FScrolledTop)+'>=',dbgs(ANode.Top+ANode.Height)+' or =',dbgs(FScrolledTop),'+'+dbgs(ClientHeight)+'<',dbgs(ANode.Top));
|
|
if (FScrolledTop>=ANode.Top+ANode.Height)
|
|
or (FScrolledTop+ClientHeight-2*BorderWidth<ANode.Top)
|
|
then
|
|
Result:=false;
|
|
end;
|
|
//DebugLn('[TCustomTreeView.IsNodeVisible] END Node=',DbgS(ANode),
|
|
//' Node.Text=',ANode.Text,' Visible=',Result);
|
|
end;
|
|
|
|
function TCustomTreeView.IsNodeHeightFullVisible(ANode: TTreeNode): Boolean;
|
|
begin
|
|
Result:=(ANode<>nil) and (ANode.ParentsExpandedVisible);
|
|
if Result then begin
|
|
//DebugLn('[TCustomTreeView.IsNodeVisible] B Node=',DbgS(ANode),
|
|
//' ',FScrolledTop,'>=',ANode.Top,'+',ANode.Height,' or ',FScrolledTop,'+',ClientHeight,'<',ANode.Top);
|
|
if (FScrolledTop>ANode.Top)
|
|
or (FScrolledTop+ClientHeight-2*BorderWidth
|
|
<ANode.Top+ANode.Height)
|
|
then
|
|
Result:=false;
|
|
end;
|
|
//DebugLn('[TCustomTreeView.IsNodeVisible] END Node=',DbgS(ANode),
|
|
//' Node.Text=',ANode.Text,' Visible=',Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.KeyDown(var Key: Word; Shift: TShiftState);
|
|
const
|
|
EditKey = VK_F2;
|
|
EditKeyShift = [];
|
|
begin
|
|
inherited KeyDown(Key, Shift);
|
|
|
|
if (Key = EditKey) and (Shift = EditKeyShift) and (not ReadOnly) then
|
|
begin
|
|
BeginEditing(Selected);
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
|
|
if (Shift <> []) and (Shift <> [ssShift]) then exit;
|
|
|
|
case Key of
|
|
VK_DOWN:
|
|
begin
|
|
MoveToNextNode(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
VK_UP:
|
|
begin
|
|
MoveToPrevNode(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
|
|
VK_HOME:
|
|
begin
|
|
MoveHome(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
VK_END:
|
|
begin
|
|
MoveEnd(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
|
|
VK_PRIOR: // Page Up
|
|
begin
|
|
MovePageUp(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
VK_NEXT: // Page Down
|
|
begin
|
|
MovePageDown(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
|
|
VK_LEFT:
|
|
begin
|
|
MoveLeft(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
VK_RIGHT:
|
|
begin
|
|
MoveRight(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
|
|
VK_ADD: // NumPlus
|
|
begin
|
|
MoveExpand(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
VK_SUBTRACT: // NumMinus
|
|
begin
|
|
MoveCollapse(AllowMultiSelectWithShift(Shift));
|
|
Key:=VK_UNKNOWN;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Loaded;
|
|
begin
|
|
inherited Loaded;
|
|
if csDesigning in ComponentState then FullExpand;
|
|
UpdateDefaultItemHeight;
|
|
end;
|
|
|
|
function TCustomTreeView.GetTopItem: TTreeNode;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
UpdateTopItem;
|
|
Result := FTopItem;
|
|
end
|
|
else
|
|
Result := FTopItem;
|
|
end;
|
|
|
|
procedure TCustomTreeView.HintMouseLeave(Sender: TObject);
|
|
begin
|
|
if FindLCLControl(Mouse.CursorPos)<>Self then
|
|
FHintWnd.Hide;
|
|
end;
|
|
|
|
function TCustomTreeView.IsStoredBackgroundColor: Boolean;
|
|
begin
|
|
result := Color <> clWindow;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetTopItem(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated and (Value <> nil) then
|
|
begin
|
|
Value.MakeVisible;
|
|
ScrolledTop:=Value.Top;
|
|
end
|
|
else
|
|
FTopItem := Value;
|
|
end;
|
|
|
|
procedure TCustomTreeView.ChangeTimer(Sender: TObject);
|
|
begin
|
|
FChangeTimer.Enabled := False;
|
|
//debugln('TCustomTreeView.OnChangeTimer');
|
|
FCallingChange := True;
|
|
try
|
|
Change(FSelectedNode);
|
|
finally
|
|
FCallingChange := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateScrollbars;
|
|
var
|
|
ScrollInfo: TScrollInfo;
|
|
MaxScrollLeft, MaxScrollTop: Integer;
|
|
begin
|
|
if not (tvsScrollbarChanged in FStates) then exit;
|
|
|
|
if not HandleAllocated or (Items.FUpdateCount>0) then
|
|
exit;
|
|
|
|
MaxScrollLeft := GetMaxScrollLeft;
|
|
MaxScrollTop := GetMaxScrollTop;
|
|
|
|
//DebugLn('* TCustomTreeView.UpdateScrollbars Enter *');
|
|
if ScrolledLeft>MaxScrollLeft then ScrolledLeft:=MaxScrollLeft;
|
|
if ScrolledTop>MaxScrollTop then ScrolledTop:=MaxScrollTop;
|
|
Exclude(FStates,tvsScrollbarChanged);
|
|
|
|
if fScrollBars in [ssBoth, ssHorizontal, ssAutoBoth, ssAutoHorizontal] then
|
|
begin
|
|
// horizontal scrollbar
|
|
FillChar(ScrollInfo,SizeOf(ScrollInfo),0);
|
|
ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
|
ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL;
|
|
ScrollInfo.nTrackPos := 0;
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nPage := Max(1,ClientWidth-2*BorderWidth);
|
|
ScrollInfo.nMax := Max(1,MaxScrollLeft+integer(ScrollInfo.nPage));
|
|
ScrollInfo.nPos := Max(FScrolledLeft,0);
|
|
if not CompareMem(@ScrollInfo,@FLastHorzScrollInfo,SizeOf(TScrollInfo))
|
|
then begin
|
|
if (fScrollBars in [ssAutoBoth, ssAutoHorizontal])
|
|
and (ScrollInfo.nPage>=cardinal(ScrollInfo.nMax)) then begin
|
|
//DebugLn(['TCustomTreeView.UpdateScrollbars Hide Horizontal.']);
|
|
FLastHorzScrollInfo.cbSize:=0;
|
|
SetShowScrollBar(SB_HORZ, false);
|
|
end else begin
|
|
//DebugLn(['TCustomTreeView.UpdateScrollbars Show Horizontal: nMin=',ScrollInfo.nMin,
|
|
//' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
|
|
//' nPos=',ScrollInfo.nPos,' GetMaxScrollLeft=',MaxScrollLeft,
|
|
//' ClientW=',ClientWidth, ' MaxRight=',FMaxRight]);
|
|
FLastHorzScrollInfo:=ScrollInfo;
|
|
SetShowScrollBar(SB_HORZ, true);
|
|
SetScrollInfo(Handle, SB_HORZ, ScrollInfo, true);
|
|
end;
|
|
end;
|
|
end else begin
|
|
FLastHorzScrollInfo.cbSize:=0;
|
|
SetShowScrollBar(SB_HORZ,false);
|
|
end;
|
|
|
|
if fScrollBars in [ssBoth, ssVertical, ssAutoBoth, ssAutoVertical] then begin
|
|
// vertical scrollbar
|
|
ScrollInfo.cbSize := SizeOf(ScrollInfo);
|
|
ScrollInfo.fMask := SIF_ALL or SIF_DISABLENOSCROLL;
|
|
ScrollInfo.nTrackPos := 0;
|
|
ScrollInfo.nMin := 0;
|
|
ScrollInfo.nPage := Max(1,ClientHeight-FDefItemHeight);
|
|
ScrollInfo.nMax := Max(1,MaxScrollTop+integer(ScrollInfo.nPage));
|
|
ScrollInfo.nTrackPos := 0;
|
|
ScrollInfo.nPos := Max(0,FScrolledTop);
|
|
if not CompareMem(@ScrollInfo,@FLastVertScrollInfo,SizeOf(TScrollInfo))
|
|
then begin
|
|
if (fScrollBars in [ssAutoBoth, ssAutoVertical])
|
|
and (ScrollInfo.nPage>=cardinal(ScrollInfo.nMax)) then begin
|
|
//DebugLn(['TCustomTreeView.UpdateScrollbars Hide Vertical.']);
|
|
ScrollInfo.nPos:=0;
|
|
SetScrollInfo(Handle, SB_VERT, ScrollInfo, false);
|
|
FLastVertScrollInfo.cbSize:=0;
|
|
SetShowScrollBar(SB_VERT, false);
|
|
end else begin
|
|
//DebugLn(['TCustomTreeView.UpdateScrollbars Show Vertical: nMin=',ScrollInfo.nMin,
|
|
//' nMax=',ScrollInfo.nMax,' nPage=',ScrollInfo.nPage,
|
|
//' nPos=',ScrollInfo.nPos,' GetMaxScrollTop=',MaxScrollTop,
|
|
//' ClientH=',ClientHeight]);
|
|
FLastVertScrollInfo:=ScrollInfo;
|
|
SetShowScrollBar(SB_VERT, true);
|
|
SetScrollInfo(Handle, SB_VERT, ScrollInfo, true);
|
|
end;
|
|
end;
|
|
end else begin
|
|
FLastVertScrollInfo.cbSize:=0;
|
|
SetShowScrollBar(SB_VERT,false);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateTooltip(X, Y: integer);
|
|
var
|
|
Node: TTreeNode;
|
|
PHint, PLeft: TPoint;
|
|
R, TextRect, IntRect: TRect;
|
|
CurMonitor: TMonitor;
|
|
begin
|
|
if not (tvoToolTips in FOptions) then exit;
|
|
|
|
if FHintWnd=nil then
|
|
begin
|
|
FHintWnd:=THintWindow.Create(Self);
|
|
FHintWnd.OnMouseLeave:=@HintMouseLeave;
|
|
end;
|
|
|
|
Node := GetNodeAt(X, Y);
|
|
if Node=nil then
|
|
begin
|
|
FHintWnd.Hide;
|
|
exit;
|
|
end;
|
|
|
|
TextRect := Rect(Node.DisplayTextLeft, Node.Top, Node.DisplayTextRight, Node.Top + Node.Height);
|
|
Types.OffsetRect(TextRect, 0, -ScrolledTop);
|
|
if not PtInRect(TextRect, Point(X, Y))
|
|
or (IntersectRect(IntRect, TextRect, ClientRect) and EqualRect(IntRect, TextRect)) then
|
|
begin
|
|
FHintWnd.Hide;
|
|
exit;
|
|
end;
|
|
|
|
// Get max width for hint from monitor's work area.
|
|
CurMonitor := GetParentForm(Self).Monitor;
|
|
R := CurMonitor.WorkareaRect;
|
|
R := FHintWnd.CalcHintRect(R.Right-R.Left, Node.Text, nil);
|
|
|
|
if WidgetSet.GetLCLCapability(lcTransparentWindow) = LCL_CAPABILITY_YES then
|
|
begin
|
|
// Font is explicitly set for transparent hints, otherwise default font is used.
|
|
if not FHintWnd.Visible then
|
|
begin
|
|
FHintWnd.Font.Assign(Self.Font);
|
|
FHintWnd.Font.Color := Screen.HintFont.Color;
|
|
end;
|
|
// Put transparent hint exactly on the node.
|
|
PHint := ClientToScreen(Point(TextRect.Left-1, TextRect.Top-3+BorderWidth));
|
|
end
|
|
else begin
|
|
// By default put hint to the right side of node.
|
|
PHint := ClientToScreen(Point(ClientWidth, TextRect.Top-3+BorderWidth));
|
|
if PHint.X + R.Right > CurMonitor.BoundsRect.Right then
|
|
begin // No space on the right? Put it to the left side.
|
|
PLeft := ClientToScreen(Point(ClientRect.Left, ClientRect.Top));
|
|
if PLeft.X >= R.Right then // enough space on left?
|
|
PHint.X := PLeft.X - R.Right;
|
|
end;
|
|
end;
|
|
Types.OffsetRect(R, PHint.X, PHint.Y);
|
|
FHintWnd.ActivateHint(R, Node.Text)
|
|
end;
|
|
|
|
function TCustomTreeView.GetSelection: TTreeNode;
|
|
begin
|
|
if RightClickSelect and Assigned(FRClickNode) then
|
|
Result := FRClickNode
|
|
else
|
|
Result := FSelectedNode;
|
|
end;
|
|
|
|
function TCustomTreeView.GetSelectionCount: Cardinal;
|
|
begin
|
|
Result := Items.SelectionCount;
|
|
end;
|
|
|
|
function TCustomTreeView.GetSelections(AIndex: Integer): TTreeNode;
|
|
begin
|
|
if (AIndex >= 0) and (AIndex < Items.SelectionCount) then
|
|
Result := Items.GetSelections(AIndex)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetSelection(Value: TTreeNode);
|
|
var
|
|
OldNode: TTreeNode;
|
|
begin
|
|
if FSelectedNode = Value then Exit;
|
|
if not CanChange(Value) then
|
|
exit;
|
|
{$IFDEF TREEVIEW_DEBUG}
|
|
DebugLn('TCustomTreeView.SetSelection: Changing selection for Node: ', Text);
|
|
{$ENDIF}
|
|
EndEditing(true); // end editing before FSelectedNode change
|
|
OldNode := FSelectedNode;
|
|
FSelectedNode := Value;
|
|
if Assigned(OldNode) then
|
|
OldNode.Selected := False;
|
|
if Assigned(Value) then
|
|
begin
|
|
Value.Selected := True;
|
|
Value.MakeVisible;
|
|
end;
|
|
InternalSelectionChanged;
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowButtons: boolean;
|
|
begin
|
|
Result:=(tvoShowButtons in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowLines: boolean;
|
|
begin
|
|
Result:=(tvoShowLines in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowRoot: boolean;
|
|
begin
|
|
Result:=(tvoShowRoot in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetShowSeparators: boolean;
|
|
begin
|
|
Result:=(tvoShowSeparators in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetToolTips: boolean;
|
|
begin
|
|
Result:=(tvoToolTips in FOptions);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetExpandSignType(Value: TTreeViewExpandSignType);
|
|
begin
|
|
if Value <> FExpandSignType then
|
|
begin
|
|
FExpandSignType := Value;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetDefaultItemHeight(Value: integer);
|
|
begin
|
|
if (tvoAutoItemHeight in FOptions) and (not (csLoading in ComponentState))
|
|
then exit;
|
|
if Value<=0 then Value:=DefaultTreeNodeHeight;
|
|
if Value=FDefItemHeight then exit;
|
|
FDefItemHeight:=Value;
|
|
Include(FStates,tvsTopsNeedsUpdate);
|
|
Invalidate;
|
|
end;
|
|
|
|
function TCustomTreeView.GetAutoExpand: boolean;
|
|
begin
|
|
Result:=(tvoAutoExpand in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetBackgroundColor: TColor;
|
|
begin
|
|
Result := Color;
|
|
end;
|
|
|
|
function TCustomTreeView.GetBottomItem: TTreeNode;
|
|
begin
|
|
if HandleAllocated then begin
|
|
UpdateBottomItem;
|
|
Result := FBottomItem;
|
|
end else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetDropTarget: TTreeNode;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
Result := FLastDropTarget;
|
|
end
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetHideSelection: boolean;
|
|
begin
|
|
Result:=(tvoHideSelection in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetHotTrack: boolean;
|
|
begin
|
|
Result:=(tvoHotTrack in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetKeepCollapsedNodes: boolean;
|
|
begin
|
|
Result:=(tvoKeepCollapsedNodes in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetMultiSelect: Boolean;
|
|
begin
|
|
Result := (tvoAllowMultiSelect in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetReadOnly: boolean;
|
|
begin
|
|
Result:=(tvoReadOnly in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetExpandSignSize: integer;
|
|
begin
|
|
if FExpandSignSize>=0 then
|
|
Result := FExpandSignSize
|
|
else
|
|
if ExpandSignType = tvestTheme then
|
|
Result := ScaleScreenToFont(FThemeExpandSignSize)
|
|
else
|
|
Result := Scale96ToFont(DefaultTreeNodeExpandSignSize);
|
|
end;
|
|
|
|
function TCustomTreeView.GetIndent: Integer;
|
|
begin
|
|
if FIndent<0 then
|
|
Result := Scale96ToFont(15)
|
|
else
|
|
Result := FIndent;
|
|
end;
|
|
|
|
function TCustomTreeView.GetRightClickSelect: boolean;
|
|
begin
|
|
Result:=(tvoRightClickSelect in FOptions);
|
|
end;
|
|
|
|
function TCustomTreeView.GetRowSelect: boolean;
|
|
begin
|
|
Result:=(tvoRowSelect in FOptions);
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetDropTarget(Value: TTreeNode);
|
|
begin
|
|
if HandleAllocated then
|
|
if Value <> nil then
|
|
Value.DropTarget := True;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetExpandSignSize(const AExpandSignSize: integer);
|
|
begin
|
|
if FExpandSignSize = AExpandSignSize then Exit;
|
|
FExpandSignSize := AExpandSignSize;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetExpandSignWidth(const AValue: integer);
|
|
begin
|
|
if FExpandSignWidth = AValue then Exit;
|
|
FExpandSignWidth := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
function TCustomTreeView.IsEditing: Boolean;
|
|
begin
|
|
Result:=tvsIsEditing in FStates;
|
|
end;
|
|
|
|
function TCustomTreeView.GetDragImages: TDragImageList;
|
|
begin
|
|
if Assigned(FDragImage) and (FDragImage.Count > 0) then
|
|
Result := FDragImage
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.GetBuiltinIconSize: TSize;
|
|
begin
|
|
Result := Types.Size(0, 0);
|
|
end;
|
|
|
|
function TCustomTreeView.GetImageSize: TSize;
|
|
begin
|
|
if FImages <> nil then
|
|
begin
|
|
Result := Images.SizeForPPI[ImagesWidth, Font.PixelsPerInch]
|
|
end else
|
|
Result := GetBuiltinIconSize;
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateInsertMark(X,Y: integer);
|
|
begin
|
|
if (tvoAutoInsertMark in Options) and (not (csDesigning in ComponentState))
|
|
then
|
|
SetInsertMarkAt(X,Y)
|
|
else
|
|
SetInsertMark(nil,tvimNone);
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoSelectionChanged;
|
|
var
|
|
lAccessibleObject: TLazAccessibleObject;
|
|
lSelection: TTreeNode;
|
|
lSelectedText: string;
|
|
begin
|
|
// Update the accessibility information
|
|
lAccessibleObject := GetAccessibleObject();
|
|
lSelection := Self.Selected;
|
|
if lSelection = nil then lSelectedText := ''
|
|
else lSelectedText := lSelection.Text;
|
|
lAccessibleObject.AccessibleValue := lSelectedText;
|
|
|
|
if Assigned(OnSelectionChanged) then OnSelectionChanged(Self);
|
|
end;
|
|
|
|
function TCustomTreeView.IsInsertMarkVisible: boolean;
|
|
begin
|
|
Result:=(FInsertMarkType<>tvimNone) and (FInsertMarkNode<>nil)
|
|
and (FInsertMarkNode.IsVisible);
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoStartDrag(var DragObject: TDragObject);
|
|
var
|
|
P: TPoint;
|
|
begin
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.DoStartDrag A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
inherited DoStartDrag(DragObject);
|
|
FLastDropTarget := nil;
|
|
if FDragNode = nil then begin
|
|
GetCursorPos(P);
|
|
with ScreenToClient(P) do FDragNode := GetNodeAt(X, Y);
|
|
{$IFDEF VerboseDrag}
|
|
if FDragNode<>nil then
|
|
DebugLn('TCustomTreeView.DoStartDrag DragNode=',FDragNode.Text)
|
|
else
|
|
DebugLn('TCustomTreeView.DoStartDrag DragNode=nil');
|
|
{$ENDIF}
|
|
end;
|
|
FPrevToolTips := ToolTips;
|
|
ToolTips := False;
|
|
FDragScrollTimer.Enabled := true;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoEndDrag(Target: TObject; X, Y: Integer);
|
|
begin
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.DoEndDrag A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
inherited DoEndDrag(Target, X, Y);
|
|
FLastDropTarget := nil;
|
|
FDragScrollTimer.Enabled := false;
|
|
ToolTips := FPrevToolTips;
|
|
end;
|
|
|
|
function TCustomTreeView.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
|
|
MousePos: TPoint): Boolean;
|
|
var
|
|
NDelta: integer;
|
|
begin
|
|
Result:=inherited DoMouseWheel(Shift, WheelDelta, MousePos);
|
|
if not Result then
|
|
begin
|
|
NDelta := (WheelDelta * Mouse.WheelScrollLines * DefaultItemHeight) div 120;
|
|
ScrolledTop := ScrolledTop - NDelta;
|
|
Result := true;
|
|
UpdateScrollbars;
|
|
end;
|
|
UpdateTooltip(MousePos.X, MousePos.Y);
|
|
end;
|
|
|
|
function TCustomTreeView.DoMouseWheelHorz(Shift: TShiftState;
|
|
WheelDelta: Integer; MousePos: TPoint): Boolean;
|
|
var
|
|
NDelta: integer;
|
|
const
|
|
cScrollStep = 50;
|
|
begin
|
|
Result:=inherited DoMouseWheelHorz(Shift, WheelDelta, MousePos);
|
|
if not Result then
|
|
begin
|
|
NDelta := (WheelDelta * cScrollStep) div 120;
|
|
ScrolledLeft := ScrolledLeft + NDelta;
|
|
Result := true;
|
|
end;
|
|
UpdateTooltip(MousePos.X, MousePos.Y);
|
|
end;
|
|
|
|
function TCustomTreeView.DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint; ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean):LRESULT;
|
|
begin
|
|
Result:=inherited;
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.DoDragMsg ',Name,':',ClassName,' ',IntToStr(ord(ADragMessage)));
|
|
{$ENDIF}
|
|
case ADragMessage of
|
|
{dmDragMove:
|
|
begin
|
|
P:=ScreenToClient(Pos);
|
|
DoDragOver(Source, P.X, P.Y, AMessage.Result <> 0);
|
|
end;}
|
|
dmDragLeave:
|
|
begin
|
|
ADragObject.HideDragImage;
|
|
FLastDropTarget := DropTarget;
|
|
DropTarget := nil;
|
|
ADragObject.ShowDragImage;
|
|
end;
|
|
dmDragDrop: FLastDropTarget := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DragOver(Source: TObject; X,Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
var
|
|
Node: TTreeNode;
|
|
begin
|
|
inherited DragOver(Source,X,Y,State,Accept);
|
|
Node := GetNodeAt(X, Y);
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn(['TCustomTreeView.DragOver ',Name,':',ClassName,' ',Node<>nil,' ',Node <> DropTarget,' ',Node = FLastDropTarget]);
|
|
DebugLn(['TCustomTreeView.DragOver Source ',Source,':',Source.ClassName]);
|
|
{$ENDIF}
|
|
if (Node <> nil)
|
|
and ((Node <> DropTarget) or (Node = FLastDropTarget)) then
|
|
begin
|
|
FLastDropTarget := nil;
|
|
Node.DropTarget := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoPaint;
|
|
var
|
|
a,HalfBorderWidth:integer;
|
|
SpaceRect, DrawRect: TRect;
|
|
Node: TTreeNode;
|
|
InsertMarkRect: TRect;
|
|
bkColor: TColor;
|
|
begin
|
|
if [tvsPainting] * FStates <> [] then Exit;
|
|
Include(FStates, tvsPainting);
|
|
try
|
|
if Focused then
|
|
Include(FStates,tvoFocusedPainting)
|
|
else
|
|
Exclude(FStates,tvoFocusedPainting);
|
|
if (tvoAutoItemHeight in fOptions) then
|
|
UpdateDefaultItemHeight;
|
|
//UpdateScrollbars;
|
|
with Canvas do
|
|
begin
|
|
bkColor := Self.Color;
|
|
Canvas.Brush.Color := bkColor;
|
|
if IsCustomDrawn(dtControl, cdPrePaint) then
|
|
begin
|
|
DrawRect := ClientRect;
|
|
if not CustomDraw(DrawRect, cdPrePaint) then exit;
|
|
bkColor := Canvas.Brush.Color;
|
|
end;
|
|
// draw nodes
|
|
Node := TopItem;
|
|
//write('[TCustomTreeView.DoPaint] A Node=',DbgS(Node));
|
|
//if Node<>nil then DebugLn(' Node.Text=',Node.Text) else DebugLn('');
|
|
while Node <> nil do
|
|
begin
|
|
if Node.Visible then
|
|
DoPaintNode(Node);
|
|
Node := Node.GetNextVisible;
|
|
//write('[TCustomTreeView.DoPaint] B Node=',DbgS(Node));
|
|
//if Node<>nil then DebugLn(' Node.Text=',Node.Text) else DebugLn('');
|
|
end;
|
|
SpaceRect := Rect(BorderWidth, BorderWidth,
|
|
ClientWidth - BorderWidth,
|
|
ClientHeight - BorderWidth);
|
|
// draw insert mark for new root node
|
|
if (InsertMarkType = tvimAsFirstChild) and (Items.Count = 0) then
|
|
begin
|
|
Pen.Color := FTreeLineColor;
|
|
Brush.Color := FSelectedColor;
|
|
InsertMarkRect := SpaceRect;
|
|
InsertMarkRect.Bottom := InsertMarkRect.Top + 2;
|
|
Rectangle(InsertMarkRect);
|
|
SpaceRect.Top := InsertMarkRect.Bottom;
|
|
end;
|
|
// draw unused space below nodes
|
|
Node := BottomItem;
|
|
if Node <> nil then
|
|
SpaceRect.Top := Node.Top + Node.Height - FScrolledTop + BorderWidth;
|
|
//if Node<>nil then DebugLn('BottomItem=',BottomItem.text) else DebugLn('NO BOTTOMITEM!!!!!!!!!');
|
|
// TWinControl(Parent).InvalidateRect(Self,SpaceRect,true);
|
|
Brush.Color := bkColor;
|
|
if (Brush.Color <> clNone) and (SpaceRect.Top < SpaceRect.Bottom) then
|
|
// if (Color <> clNone) and (SpaceRect.Top < SpaceRect.Bottom) then
|
|
begin
|
|
//DebugLn(' SpaceRect=',SpaceRect.Left,',',SpaceRect.Top,',',SpaceRect.Right,',',SpaceRect.Bottom);
|
|
//Brush.Color := Color;
|
|
FillRect(SpaceRect);
|
|
end;
|
|
// draw border
|
|
HalfBorderWidth := BorderWidth shr 1;
|
|
Pen.Color := clGray;
|
|
for a := 0 to BorderWidth - 1 do
|
|
begin
|
|
if a = HalfBorderWidth then
|
|
Pen.Color := clBlack;
|
|
MoveTo(a, ClientHeight - 1 - a);
|
|
LineTo(a, a);
|
|
LineTo(ClientWidth - 1 - a, a);
|
|
end;
|
|
Pen.Color := clWhite;
|
|
for a := 0 to BorderWidth - 1 do
|
|
begin
|
|
if a = HalfBorderWidth then
|
|
Pen.Color := clLtGray;
|
|
MoveTo(ClientWidth - 1 - a, a);
|
|
LineTo(ClientWidth - 1 - a, ClientHeight - 1 - a);
|
|
LineTo(a, ClientHeight - 1 - a);
|
|
end;
|
|
if IsCustomDrawn(dtControl, cdPostPaint) then
|
|
begin
|
|
DrawRect := ClientRect;
|
|
if not CustomDraw(DrawRect, cdPostPaint) then exit;
|
|
end;
|
|
end;
|
|
finally
|
|
Exclude(FStates, tvsPainting);
|
|
end;
|
|
end;
|
|
|
|
function InvertNdColor(AColor: TColor): TColor;
|
|
var
|
|
Red, Green, Blue: integer;
|
|
begin
|
|
if AColor<>clHighlight then begin
|
|
Result:=clWhite;
|
|
Red:=(AColor shr 16) and $ff;
|
|
Green:=(AColor shr 8) and $ff;
|
|
Blue:=AColor and $ff;
|
|
if Red+Green+Blue>$180 then
|
|
Result:=clBlack;
|
|
//DebugLn(['[TCustomTreeView.DoPaintNode.InvertColor] Result=',Result,' ',Red,',',Green,',',Blue]);
|
|
end
|
|
else
|
|
Result := clHighlightText;
|
|
end;
|
|
|
|
procedure TCustomTreeView.DoPaintNode(Node: TTreeNode);
|
|
var
|
|
NodeRect: TRect;
|
|
VertMid, VertDelta, RealExpandSignSize, RealIndent: integer;
|
|
NodeSelected, NodeHot, NodeDisabled, HasExpandSign, CustomDrawn: Boolean;
|
|
|
|
procedure DrawVertLine(X, Y1, Y2: Integer);
|
|
begin
|
|
if Y1 > Y2 then
|
|
Exit;
|
|
if TreeLinePenStyle = psPattern then
|
|
begin
|
|
// TODO: implement psPattern support in the LCL
|
|
Y1 := Y1 + VertDelta;
|
|
while Y1 < Y2 do
|
|
begin
|
|
Canvas.Pixels[X, Y1] := TreeLineColor;
|
|
inc(Y1, 2);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
Canvas.MoveTo(X, Y1);
|
|
Canvas.LineTo(X, Y2);
|
|
end;
|
|
end;
|
|
|
|
procedure DrawHorzLine(Y, X1, X2: Integer);
|
|
begin
|
|
if X1 > X2 then
|
|
Exit;
|
|
if TreeLinePenStyle = psPattern then
|
|
begin
|
|
// TODO: implement psPattern support in the LCL
|
|
while X1 < X2 do
|
|
begin
|
|
Canvas.Pixels[X1, Y] := TreeLineColor;
|
|
inc(X1, 2);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
Canvas.MoveTo(X1, Y);
|
|
Canvas.LineTo(X2, Y);
|
|
end;
|
|
end;
|
|
|
|
function DrawTreeLines(CurNode: TTreeNode): integer;
|
|
// paints tree lines, returns indent
|
|
var
|
|
CurMid: integer;
|
|
begin
|
|
if (CurNode <> nil) and ((tvoShowRoot in Options) or (CurNode.Parent<>nil)) then
|
|
begin
|
|
Result := DrawTreeLines(CurNode.Parent);
|
|
if ShowLines then
|
|
begin
|
|
CurMid := Result + (RealIndent shr 1);
|
|
if CurNode = Node then
|
|
begin
|
|
// draw horizontal line
|
|
if HasExpandSign then
|
|
DrawHorzLine(VertMid, CurMid + RealExpandSignSize div 2, Result + RealIndent)
|
|
else
|
|
DrawHorzLine(VertMid, CurMid, Result + RealIndent);
|
|
end;
|
|
|
|
if (CurNode.GetNextVisibleSibling <> nil) then
|
|
begin
|
|
// draw vertical line to next brother
|
|
if (CurNode = Node) and HasExpandSign then
|
|
begin
|
|
if (Node.Parent = nil) and (Node.GetPrevSibling = nil) then
|
|
DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2, NodeRect.Bottom)
|
|
else
|
|
begin
|
|
DrawVertLine(CurMid, NodeRect.Top, VertMid);
|
|
DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2 + VertDelta, NodeRect.Bottom);
|
|
end;
|
|
end
|
|
else
|
|
if (Node.Parent = nil) and (Node.GetPrevSibling = nil) then
|
|
DrawVertLine(CurMid, VertMid + VertDelta, NodeRect.Bottom)
|
|
else
|
|
DrawVertLine(CurMid, NodeRect.Top, NodeRect.Bottom);
|
|
end else
|
|
if (CurNode = Node) then
|
|
begin
|
|
// draw vertical line from top to horizontal line
|
|
if HasExpandSign then
|
|
begin
|
|
if ((InsertMarkNode = Node) and (InsertMarkType = tvimAsNextSibling)) then
|
|
begin
|
|
DrawVertLine(CurMid, NodeRect.Top, VertMid);
|
|
DrawVertLine(CurMid, VertMid + RealExpandSignSize div 2, NodeRect.Bottom - 1);
|
|
end
|
|
else
|
|
DrawVertLine(CurMid, NodeRect.Top, VertMid);
|
|
end
|
|
else
|
|
if ((InsertMarkNode = Node) and (InsertMarkType = tvimAsNextSibling)) then
|
|
DrawVertLine(CurMid, NodeRect.Top, NodeRect.Bottom - 1)
|
|
else
|
|
DrawVertLine(CurMid, NodeRect.Top, VertMid);
|
|
end;
|
|
end;
|
|
inc(Result, RealIndent);
|
|
end else
|
|
begin
|
|
Result := BorderWidth - FScrolledLeft;
|
|
if CurNode <> nil then // indent first level of tree with ShowRoot = false a bit
|
|
inc(Result, RealIndent shr 2);
|
|
end;
|
|
end;
|
|
|
|
procedure DrawExpandSign(MidX, MidY: integer; CollapseSign: boolean);
|
|
const
|
|
PlusMinusDetail: array[Boolean {Hot}, Boolean {Expanded}] of TThemedTreeview =
|
|
(
|
|
(ttGlyphClosed, ttGlyphOpened),
|
|
(ttHotGlyphClosed, ttHotGlyphOpened)
|
|
);
|
|
var
|
|
HalfSize, ALeft, ATop, ARight, ABottom, SmallIndent: integer;
|
|
Points: array[0..2] of TPoint; // for triangle
|
|
Details: TThemedElementDetails;
|
|
R: TRect;
|
|
PrevColor: TColor;
|
|
PrevStyle: TBrushStyle;
|
|
const
|
|
cShiftHorzArrow = 2; //paint horz arrow N pixels upper than MidY
|
|
begin
|
|
HalfSize := RealExpandSignSize div 2;
|
|
//if not Odd(RealExpandSignSize) then
|
|
// Dec(HalfSize);
|
|
ALeft := MidX - HalfSize;
|
|
ARight := MidX + HalfSize;
|
|
ATop := MidY - HalfSize;
|
|
ABottom := MidY + HalfSize;
|
|
|
|
if Assigned(FOnCustomDrawArrow) then
|
|
begin
|
|
FOnCustomDrawArrow(Self, Rect(ALeft, ATop, ARight, ABottom), not CollapseSign);
|
|
Exit
|
|
end;
|
|
|
|
with Canvas do
|
|
begin
|
|
PrevStyle := Brush.Style;
|
|
PrevColor := Brush.Color;
|
|
|
|
Pen.Color := FExpandSignColor;
|
|
Pen.Style := psSolid;
|
|
case ExpandSignType of
|
|
tvestTheme:
|
|
begin
|
|
// draw a themed expand sign. Todo: track hot
|
|
R := Rect(ALeft, ATop, ARight, ABottom);
|
|
Canvas.Brush.Color := Self.Color;
|
|
Details := ThemeServices.GetElementDetails(PlusMinusDetail[False, CollapseSign]);
|
|
ThemeServices.DrawElement(Canvas.Handle, Details, R, nil);
|
|
end;
|
|
tvestPlusMinus:
|
|
begin
|
|
// draw a plus or a minus sign
|
|
R := Rect(ALeft, ATop, ARight+1, ABottom+1); //+1 for centering of line in square
|
|
Canvas.Brush.Color := Self.Color;
|
|
Rectangle(R);
|
|
SmallIndent := Scale96ToFont(2);
|
|
MoveTo(R.Left + SmallIndent, MidY);
|
|
LineTo(R.Right - SmallIndent, MidY);
|
|
if not CollapseSign then
|
|
begin
|
|
MoveTo(MidX, R.Top + SmallIndent);
|
|
LineTo(MidX, R.Bottom - SmallIndent);
|
|
end;
|
|
end;
|
|
tvestArrow,
|
|
tvestArrowFill:
|
|
begin
|
|
// draw an arrow. down for collapse and right for expand
|
|
R := Rect(ALeft, ATop, ARight+1, ABottom+1); //+1 for simmetry of arrow
|
|
if CollapseSign then
|
|
begin
|
|
// draw an arrow down
|
|
Points[0] := Point(R.Left, MidY - cShiftHorzArrow);
|
|
Points[1] := Point(R.Right - 1, MidY - cShiftHorzArrow);
|
|
Points[2] := Point(MidX, R.Bottom - 1 - cShiftHorzArrow);
|
|
end else
|
|
begin
|
|
// draw an arrow right
|
|
Points[0] := Point(MidX - 1, ATop);
|
|
Points[1] := Point(R.Right - 2, MidY);
|
|
Points[2] := Point(MidX - 1, R.Bottom - 1);
|
|
end;
|
|
|
|
if ExpandSignType = tvestArrowFill then
|
|
Brush.Color := ExpandSignColor;
|
|
Polygon(Points, 3, False);
|
|
end;
|
|
tvestAngleBracket:
|
|
begin
|
|
// draw an arrow. down for collapse and right for expand
|
|
R := Rect(ALeft, ATop, ARight+1, ABottom+1); //+1 for simmetry of arrow
|
|
if CollapseSign then
|
|
begin
|
|
// draw an arrow down
|
|
Points[0] := Point(R.Left, MidY - cShiftHorzArrow);
|
|
Points[1] := Point(R.Right - 1, MidY - cShiftHorzArrow);
|
|
Points[2] := Point(MidX, R.Bottom - 1 - cShiftHorzArrow);
|
|
end else
|
|
begin
|
|
// draw an arrow right
|
|
Points[0] := Point(MidX - 2, ATop);
|
|
Points[1] := Point(MidX - 2, R.Bottom - 1);
|
|
Points[2] := Point(R.Right - 3, MidY);
|
|
end;
|
|
|
|
for SmallIndent := 1 to FExpandSignWidth do
|
|
begin
|
|
Line(Points[2], Points[0]);
|
|
Line(Points[2], Points[1]);
|
|
|
|
if CollapseSign then
|
|
begin
|
|
Dec(Points[0].Y);
|
|
Dec(Points[1].Y);
|
|
Dec(Points[2].Y);
|
|
end
|
|
else
|
|
begin
|
|
Dec(Points[0].X);
|
|
Dec(Points[1].X);
|
|
Dec(Points[2].X);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
Brush.Color := PrevColor;
|
|
Brush.Style := PrevStyle;
|
|
end;
|
|
end;
|
|
|
|
procedure DrawInsertMark;
|
|
var
|
|
InsertMarkRect: TRect;
|
|
x: Integer;
|
|
begin
|
|
case InsertMarkType of
|
|
|
|
tvimAsFirstChild:
|
|
if InsertMarkNode=Node then begin
|
|
// draw insert mark for new first child
|
|
with Canvas do begin
|
|
// draw virtual tree line
|
|
Pen.Color:=TreeLineColor;
|
|
// Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
|
|
x:=Node.DisplayExpandSignRight+RealIndent div 2;
|
|
MoveTo(x,NodeRect.Bottom-3);
|
|
LineTo(x,NodeRect.Bottom-2);
|
|
x:=Node.DisplayExpandSignRight+RealIndent;
|
|
LineTo(x,NodeRect.Bottom-2);
|
|
Pen.Style:=psSolid;
|
|
|
|
// draw virtual rectangle
|
|
Pen.Color:=TreeLineColor;
|
|
Brush.Color:=FSelectedColor;
|
|
InsertMarkRect:=Rect(x,NodeRect.Bottom-3,
|
|
NodeRect.Right,NodeRect.Bottom-1);
|
|
Rectangle(InsertMarkRect);
|
|
end;
|
|
end;
|
|
|
|
tvimAsPrevSibling:
|
|
if InsertMarkNode=Node then begin
|
|
// draw insert mark for new previous sibling
|
|
with Canvas do begin
|
|
// draw virtual tree line
|
|
Pen.Color:=TreeLineColor;
|
|
//Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
|
|
x:=Node.DisplayExpandSignLeft+RealIndent div 2;
|
|
MoveTo(x,NodeRect.Top+1);
|
|
x:=Node.DisplayExpandSignRight;
|
|
LineTo(x,NodeRect.Top+1);
|
|
Pen.Style:=psSolid;
|
|
|
|
// draw virtual rectangle
|
|
Pen.Color:=TreeLineColor;
|
|
Brush.Color:=FSelectedColor;
|
|
InsertMarkRect:=Rect(x,NodeRect.Top,
|
|
NodeRect.Right,NodeRect.Top+2);
|
|
Rectangle(InsertMarkRect);
|
|
end;
|
|
end;
|
|
|
|
tvimAsNextSibling:
|
|
if InsertMarkNode=Node then begin
|
|
// draw insert mark for new next sibling
|
|
with Canvas do begin
|
|
// draw virtual tree line
|
|
Pen.Color:=TreeLineColor;
|
|
//Pen.Style:=TreeLinePenStyle; ToDo: not yet implemented in all widgetsets
|
|
x:=Node.DisplayExpandSignLeft+RealIndent div 2;
|
|
MoveTo(x,NodeRect.Bottom-3);
|
|
LineTo(x,NodeRect.Bottom-2);
|
|
x:=Node.DisplayExpandSignRight;
|
|
LineTo(x,NodeRect.Bottom-2);
|
|
Pen.Style:=psSolid;
|
|
|
|
// draw virtual rectangle
|
|
Pen.Color:=TreeLineColor;
|
|
Brush.Color:=FSelectedColor;
|
|
InsertMarkRect:=Rect(x,NodeRect.Bottom-3,
|
|
NodeRect.Right,NodeRect.Bottom-1);
|
|
Rectangle(InsertMarkRect);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
function GetThemedDetail(IsFocused, IsSelected, IsHot: Boolean): TThemedTreeView;
|
|
const
|
|
DETAIL: array[boolean, boolean, boolean] of TThemedTreeView = (
|
|
// IsFocused = false
|
|
( { IsHot = false IsHot = true }
|
|
{ IsSelected = false } ( ttItemNormal, ttItemHot ),
|
|
{ IsSelected = true } ( ttItemSelectedNotFocus, ttItemSelectedNotFocus )
|
|
),
|
|
// IsFocused = true
|
|
( { IsHot = false IsHot = true }
|
|
{ IsSelected = false } ( ttItemNormal, ttItemHot ),
|
|
{ IsSelected = true } ( ttItemSelected, ttItemSelected )
|
|
)
|
|
);
|
|
begin
|
|
Result := DETAIL[IsFocused, IsSelected and (not SelectionIsHidden), IsHot];
|
|
end;
|
|
|
|
{ Draws the default normal node background }
|
|
procedure DrawNormalBackground(ARect: TRect);
|
|
begin
|
|
if Canvas.Brush.Color <> clNone then
|
|
Canvas.FillRect(ARect);
|
|
end;
|
|
|
|
{ Default-draws the background of selected and hot-tracked nodes over the full
|
|
client width. This does not occur when the tree is not in RowSelect mode,
|
|
or when the preceding OnAdvancedCustomDrawItem event handler has been
|
|
exited with DefaultDraw = false. }
|
|
procedure DrawSpecialBackground(IsSelected, IsHot: Boolean; ARect: TRect);
|
|
var
|
|
Details: TThemedElementDetails;
|
|
tt: TThemedTreeView;
|
|
begin
|
|
if not RowSelect then
|
|
exit;
|
|
if not (IsSelected or IsHot) then
|
|
exit;
|
|
|
|
if (tvoThemedDraw in Options) then
|
|
begin
|
|
tt := GetThemedDetail(Focused, IsSelected, IsHot);
|
|
Details := ThemeServices.GetElementDetails(tt);
|
|
if tt <> ttItemNormal then
|
|
ThemeServices.DrawElement(Canvas.Handle, Details, ARect, nil);
|
|
end else
|
|
if (IsSelected or IsHot) and (Canvas.Brush.Color <> clNone) then
|
|
Canvas.FillRect(ARect);
|
|
end;
|
|
|
|
{ Draws first the node text background unless deactivated by user selection
|
|
in the custom-draw events. Then the node text is drawn. }
|
|
procedure DrawNodeText(IsSelected, IsHot: Boolean; ANodeRect: TRect; AText: String);
|
|
var
|
|
Details: TThemedElementDetails;
|
|
tt: TThemedTreeView;
|
|
begin
|
|
// Themed drawing here
|
|
if tvoThemedDraw in Options then
|
|
begin
|
|
tt := GetThemedDetail(Focused, IsSelected, IsHot);
|
|
Details := ThemeServices.GetElementDetails(tt);
|
|
if (tt <> ttItemNormal) and (not RowSelect) then
|
|
ThemeServices.DrawElement(Canvas.Handle, Details, ANodeRect, nil);
|
|
end else
|
|
// Non-themed drawing of text background here
|
|
if not RowSelect and (Canvas.Brush.Color <> clNone) then
|
|
Canvas.FillRect(ANodeRect);
|
|
|
|
// Draw the node text
|
|
ANodeRect.Offset(ScaleX(2, 96), 0);
|
|
if (tvoThemedDraw in Options) then
|
|
begin
|
|
if not (Enabled and Node.Enabled) then
|
|
Details.State := 4; // TmSchema.TREIS_DISABLED = 4
|
|
ThemeServices.DrawText(Canvas, Details, AText, ANodeRect, DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX, 0);
|
|
end
|
|
else
|
|
begin
|
|
SetBkMode(Canvas.Handle, TRANSPARENT);
|
|
DrawText(Canvas.Handle, PChar(AText), -1, ANodeRect, DT_LEFT or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
x, ImgIndex: integer;
|
|
CurTextRect, ImgRect: TRect;
|
|
DrawState: TCustomDrawState;
|
|
PaintImages: boolean;
|
|
OverlayIndex: Integer;
|
|
ImageRes, StateImageRes: TScaledImageListResolution;
|
|
savedBrushColor: TColor;
|
|
begin
|
|
if Assigned(FImages) then
|
|
ImageRes := Images.ResolutionForPPI[ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
|
|
if Assigned(FStateImages) then
|
|
StateImageRes := StateImages.ResolutionForPPI[StateImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
|
|
RealExpandSignSize := ExpandSignSize;
|
|
RealIndent := Indent;
|
|
NodeRect := Node.DisplayRect(False);
|
|
if (NodeRect.Bottom < 0) or (NodeRect.Top >= ClientHeight) then
|
|
Exit;
|
|
NodeSelected := (Node.Selected) or (Node.MultiSelected);
|
|
NodeHot := (tvoHotTrack in FOptions) and (Node = FNodeUnderCursor) and Assigned(FNodeUnderCursor);
|
|
NodeDisabled := not (Enabled and Node.Enabled);
|
|
Canvas.Font.Assign(Self.Font);
|
|
if NodeSelected and not (tvoThemedDraw in Options) and not SelectionIsHidden then
|
|
begin
|
|
Canvas.Brush.Color := FSelectedColor;
|
|
Canvas.Font.Color := FSelectedFontColor;
|
|
end else
|
|
begin
|
|
Canvas.Font.Color := Font.Color;
|
|
Canvas.Brush.Color := Color;
|
|
if NodeHot and not (tvoThemedDraw in FOptions) then
|
|
begin
|
|
Canvas.Font.Style := [fsUnderline];
|
|
if FHotTrackColor <> clNone then
|
|
Canvas.Font.Color := FHotTrackColor;
|
|
end;
|
|
end;
|
|
if NodeDisabled and (FDisabledFontColor <> clNone) then
|
|
Canvas.Font.Color := FDisabledFontColor;
|
|
PaintImages := True;
|
|
customdrawn := IsCustomDrawn(dtItem, cdPrePaint);
|
|
if customDrawn then
|
|
begin
|
|
DrawState := [];
|
|
if NodeSelected then
|
|
Include(DrawState, cdsSelected);
|
|
if Node.Focused then
|
|
Include(DrawState, cdsFocused);
|
|
if Node.MultiSelected then
|
|
Include(DrawState, cdsMarked);
|
|
if NodeHot then
|
|
Include(DrawState, cdsHot);
|
|
if NodeDisabled then
|
|
Include(DrawState, cdsDisabled);
|
|
if not CustomDrawItem(Node, DrawState, cdPrePaint, PaintImages) then Exit;
|
|
end;
|
|
|
|
VertMid := NodeRect.Top + (NodeRect.Bottom - NodeRect.Top) div 2;
|
|
HasExpandSign := ShowButtons and Node.HasChildren and ((tvoShowRoot in Options) or (Node.Parent <> nil));
|
|
VertDelta := Ord(FDefItemHeight and 3 = 2);
|
|
//DebugLn(['[TCustomTreeView.DoPaintNode] Node=',DbgS(Node),' Node.Text=',Node.Text,' NodeRect=',NodeRect.Left,',',NodeRect.Top,',',NodeRect.Right,',',NodeRect.Bottom,' VertMid=',VertMid]);
|
|
with Canvas do
|
|
begin
|
|
// Draw the normal node background, no selected color, no hot-track color
|
|
savedBrushColor := Canvas.Brush.Color;
|
|
Canvas.Brush.Color := Color;
|
|
customDrawn := IsCustomDrawn(dtItem, cdPreErase);
|
|
if not IsCustomDrawn(dtItem, cdPreErase) or CustomDrawItem(Node, [], cdPreErase, PaintImages) then
|
|
DrawNormalBackground(NodeRect);
|
|
Canvas.Brush.Color := savedBrushColor;
|
|
|
|
// Background of selected or hot-tracked nodes
|
|
customDrawn := IsCustomDrawn(dtItem, cdPostErase);
|
|
if not IsCustomDrawn(dtItem, cdPostErase) or CustomDrawItem(Node, DrawState, cdPostErase, PaintImages) then
|
|
// If not custom-painted draw it over the full tree client width
|
|
DrawSpecialBackground(NodeSelected, NodeHot, NodeRect);
|
|
|
|
// Draw tree lines
|
|
Pen.Color := TreeLineColor;
|
|
Pen.Style := TreeLinePenStyle;
|
|
if Pen.Style = psPattern then
|
|
Pen.SetPattern(FTreeLinePenPattern);
|
|
x := DrawTreeLines(Node);
|
|
Pen.Style := psSolid;
|
|
|
|
// draw expand sign
|
|
if HasExpandSign then
|
|
DrawExpandSign(x - RealIndent + (RealIndent shr 1), VertMid, Node.Expanded);
|
|
|
|
// draw state icon
|
|
if (StateImages <> nil) then
|
|
begin
|
|
if (Node.StateIndex >= 0) and (Node.StateIndex < StateImages.Count) then
|
|
begin
|
|
if PaintImages then
|
|
StateImageRes.Draw(Canvas, x + 1, NodeRect.Top +(NodeRect.Bottom - NodeRect.Top - StateImageRes.Height) div 2,
|
|
Node.StateIndex, True);
|
|
Inc(x, StateImageRes.Width + FDefItemSpace);
|
|
end;
|
|
end;
|
|
|
|
// draw icon
|
|
if (Images = nil) then
|
|
begin
|
|
imgRect := NodeRect;
|
|
imgRect.Left := x+1;
|
|
inc(x, DrawBuiltinIcon(Node, imgRect).CX + FDefItemSpace);
|
|
end else
|
|
begin
|
|
if FSelectedNode <> Node then
|
|
begin
|
|
GetImageIndex(Node);
|
|
ImgIndex := Node.ImageIndex
|
|
end
|
|
else
|
|
begin
|
|
GetSelectedIndex(Node);
|
|
ImgIndex := Node.SelectedIndex;
|
|
end;
|
|
if (ImgIndex >= 0) and (ImgIndex < Images.Count) then
|
|
begin
|
|
if PaintImages then
|
|
begin
|
|
if (Node.OverlayIndex >= 0) then begin
|
|
OverlayIndex:=Node.OverlayIndex;
|
|
if Images.HasOverlays then begin
|
|
ImageRes.DrawOverlay(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
|
|
ImgIndex, OverlayIndex, Node.FNodeEffect);
|
|
end else begin
|
|
// draw the Overlay using the image from the list
|
|
// set an Overlay
|
|
Images.OverLay(OverlayIndex,0);
|
|
// draw overlay
|
|
ImageRes.DrawOverlay(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
|
|
ImgIndex, 0, Node.FNodeEffect);
|
|
// reset the Overlay
|
|
Images.OverLay(-1,0);
|
|
end;
|
|
end
|
|
else begin
|
|
ImageRes.Draw(Canvas, x + 1, NodeRect.Top + (NodeRect.Bottom - NodeRect.Top - ImageRes.Height) div 2,
|
|
ImgIndex, Node.FNodeEffect);
|
|
end;
|
|
end;
|
|
Inc(x, ImageRes.Width + FDefItemSpace);
|
|
end;
|
|
end;
|
|
|
|
// draw text
|
|
if (Node.Text <> '') and (Node <> FEditingItem) then
|
|
begin
|
|
CurTextRect := NodeRect;
|
|
CurTextRect.Left := x;
|
|
CurTextRect.Right := x + TextWidth(Node.Text) + (FDefItemSpace * 2);
|
|
DrawNodeText(NodeSelected, NodeHot, CurTextRect, Node.Text);
|
|
end;
|
|
|
|
// draw separator
|
|
if (tvoShowSeparators in FOptions) then
|
|
begin
|
|
Pen.Color:=SeparatorColor;
|
|
MoveTo(NodeRect.Left,NodeRect.Bottom-1);
|
|
LineTo(NodeRect.Right,NodeRect.Bottom-1);
|
|
end;
|
|
|
|
// draw insert mark
|
|
DrawInsertMark;
|
|
end;
|
|
PaintImages := true;
|
|
if IsCustomDrawn(dtItem, cdPostPaint) then
|
|
begin
|
|
DrawState:=[];
|
|
if Node.Selected then
|
|
Include(DrawState,cdsSelected);
|
|
if Node.Focused then
|
|
Include(DrawState,cdsFocused);
|
|
if Node.MultiSelected then
|
|
Include(DrawState,cdsMarked);
|
|
if NodeHot then
|
|
Include(DrawState, cdsHot);
|
|
if not CustomDrawItem(Node,DrawState,cdPostPaint,PaintImages) then exit;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.DrawBuiltinIcon(ANode: TTreeNode; ARect: TRect): TSize;
|
|
begin
|
|
Result := Size(0, 0);
|
|
end;
|
|
|
|
procedure TCustomTreeView.GetImageIndex(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnGetImageIndex) then
|
|
FOnGetImageIndex(Self, Node);
|
|
end;
|
|
|
|
procedure TCustomTreeView.GetSelectedIndex(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnGetSelectedIndex) then FOnGetSelectedIndex(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanChange(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(Node) and Assigned(FOnChanging) then
|
|
FOnChanging(Self, Node, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Change(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnChange) then
|
|
FOnChange(Self, Node);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Delete(Node: TTreeNode);
|
|
begin
|
|
if Assigned(FOnDeletion) then FOnDeletion(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.ExpandSignSizeIsStored: Boolean;
|
|
begin
|
|
Result := FExpandSignSize >= 0;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Expand(Node: TTreeNode);
|
|
begin
|
|
UpdateScrollbars;
|
|
if Assigned(FOnExpanded) then
|
|
FOnExpanded(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanExpand(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnExpanding)then
|
|
FOnExpanding(Self, Node, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.Collapse(Node: TTreeNode);
|
|
begin
|
|
if csDestroying in ComponentState then
|
|
exit;
|
|
UpdateScrollbars;
|
|
if Assigned(FOnCollapsed) then
|
|
FOnCollapsed(Self, Node);
|
|
end;
|
|
|
|
function TCustomTreeView.CanCollapse(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if csDestroying in ComponentState then
|
|
exit;
|
|
if Assigned(FOnCollapsing) then
|
|
FOnCollapsing(Self, Node, Result);
|
|
end;
|
|
|
|
function TCustomTreeView.CanEdit(Node: TTreeNode): Boolean;
|
|
begin
|
|
Result := True;
|
|
if Assigned(FOnEditing) then
|
|
FOnEditing(Self, Node, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.EndEditing(Cancel: boolean);
|
|
var
|
|
NewText: String;
|
|
Node: TTreeNode;
|
|
begin
|
|
//DebugLn(['TCustomTreeView.EndEditing ',DbgSName(Self),' ',tvsIsEditing in FStates,' ',DbgSName(FEditor)]);
|
|
if not (tvsIsEditing in FStates) then exit;
|
|
Exclude(FStates,tvsIsEditing);
|
|
if FEditor<>nil then begin
|
|
// get new value fom edit control and hide it
|
|
NewText:='';
|
|
if not Cancel then
|
|
NewText:=FEditor.Text;
|
|
FEditor.Parent:=nil;
|
|
|
|
// commit new value
|
|
if not Cancel then begin
|
|
Node:=FEditingItem;
|
|
if (Node<>nil) then begin
|
|
if Assigned(OnEdited) then
|
|
OnEdited(Self,Node,NewText);
|
|
Node.Text:=NewText;
|
|
end;
|
|
end;
|
|
if Assigned(FOnEditingEnd) then FOnEditingEnd(Self, FEditingItem, Cancel);
|
|
end;
|
|
FEditingItem := nil;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EnsureNodeIsVisible(ANode: TTreeNode);
|
|
var b: integer;
|
|
begin
|
|
if ANode=nil then exit;
|
|
ANode.ExpandParents;
|
|
if ANode.Top<ScrolledTop then
|
|
ScrolledTop:=ANode.Top
|
|
else begin
|
|
b:=ANode.Top+ANode.Height-GetNodeDrawAreaHeight;
|
|
if ScrolledTop<b then ScrolledTop:=b;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.CreateNode: TTreeNode;
|
|
var
|
|
NewNodeClass: TTreeNodeClass;
|
|
begin
|
|
Result := nil;
|
|
if Assigned(FOnCustomCreateItem) then
|
|
FOnCustomCreateItem(Self, Result);
|
|
if Result = nil then
|
|
begin
|
|
NewNodeClass:=TTreeNode;
|
|
DoCreateNodeClass(NewNodeClass);
|
|
Result := NewNodeClass.Create(Items);
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.CreateNodes: TTreeNodes;
|
|
begin
|
|
Result := TTreeNodes.Create(Self);
|
|
end;
|
|
|
|
procedure TCustomTreeView.ImageListChange(Sender: TObject);
|
|
begin
|
|
Invalidate;
|
|
end;
|
|
|
|
function TCustomTreeView.IndentIsStored: Boolean;
|
|
begin
|
|
Result := FIndent >= 0;
|
|
end;
|
|
|
|
function TCustomTreeView.NodeIsSelected(aNode: TTreeNode): Boolean;
|
|
begin
|
|
Result := Assigned(aNode) and
|
|
(aNode.Selected or ((tvoAllowMultiselect in Options) and aNode.MultiSelected));
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseDown(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
var
|
|
CursorNode: TTreeNode;
|
|
CursorNdSelected: Boolean;
|
|
LogicalX: Integer;
|
|
begin
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.MouseDown A ',DbgSName(Self),' ');
|
|
{$ENDIF}
|
|
FMouseDownPos := Point(X,Y);
|
|
FStates:=FStates-[tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
|
|
|
|
CursorNode := GetNodeAt(X, Y);
|
|
if (CursorNode <> nil) and not CursorNode.Enabled then Exit;
|
|
CursorNdSelected := NodeIsSelected(CursorNode);
|
|
LogicalX:=X;
|
|
|
|
//change selection on right click
|
|
if (Button = mbRight) and RightClickSelect and //right click
|
|
(([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
|
|
not AllowMultiSelectWithCtrl(Shift) and //only when CTRL is not pressed
|
|
(CursorNode <> nil)
|
|
then
|
|
begin
|
|
if not (tvoRowSelect in Options) and
|
|
(tvoEmptySpaceUnselect in Options) and
|
|
(LogicalX >= CursorNode.DisplayStateIconLeft) and
|
|
(LogicalX > CursorNode.DisplayTextRight) then
|
|
ClearSelection
|
|
else
|
|
if not (tvoAllowMultiselect in Options) then
|
|
Selected := CursorNode
|
|
else
|
|
if not CursorNdSelected then
|
|
Items.SelectOnlyThis(CursorNode);
|
|
end
|
|
else // empty space below last node
|
|
if (Button = mbRight) and RightClickSelect and (CursorNode = nil) and
|
|
(tvoEmptySpaceUnselect in Options) then
|
|
ClearSelection;
|
|
|
|
if not Focused and CanFocus then
|
|
SetFocus;
|
|
|
|
inherited MouseDown(Button, Shift, X, Y);
|
|
|
|
//CursorNode must be reassigned again - e.g. in OnMouseDown the node can be deleted or moved.
|
|
CursorNode := GetNodeWithExpandSignAt(LogicalX, Y);
|
|
CursorNdSelected := NodeIsSelected(CursorNode);
|
|
|
|
//Flag is used for DblClick/TripleClick/QuadClick, so set it before testing ShiftState
|
|
FMouseDownOnFoldingSign :=
|
|
Assigned(CursorNode) and CursorNode.HasChildren and ShowButtons and
|
|
(LogicalX >= CursorNode.DisplayExpandSignLeft) and
|
|
(LogicalX < CursorNode.DisplayExpandSignRight);
|
|
|
|
//change selection on left click
|
|
if (Button = mbLeft) and //left click
|
|
(([ssDouble, ssTriple, ssQuad] * Shift) = []) and //single or first of a multi click
|
|
(CursorNode <> nil) then
|
|
begin
|
|
if FMouseDownOnFoldingSign then
|
|
// mousedown occurred on expand sign -> expand/collapse
|
|
CursorNode.Expanded := not CursorNode.Expanded
|
|
else if (LogicalX >= CursorNode.DisplayStateIconLeft) or (tvoRowSelect in Options) then
|
|
begin
|
|
// mousedown occurred in text or icon -> select node and begin drag operation
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn(['TCustomTreeView.MouseDown In Text ',DbgSName(Self),' MouseCapture=',MouseCapture]);
|
|
{$ENDIF}
|
|
if (Selected = CursorNode) and (LogicalX >= CursorNode.DisplayTextLeft) then
|
|
Include(FStates, tvsEditOnMouseUp);
|
|
if not (tvoAllowMultiselect in Options) then
|
|
Selected := CursorNode
|
|
else
|
|
begin
|
|
if AllowMultiSelectWithShift(Shift) then
|
|
begin
|
|
Exclude(FStates,tvsEditOnMouseUp);
|
|
LockSelectionChangeEvent;
|
|
try
|
|
Items.MultiSelect(CursorNode, not AllowMultiSelectWithCtrl(Shift));
|
|
finally
|
|
UnlockSelectionChangeEvent;
|
|
end;
|
|
end
|
|
else if AllowMultiSelectWithCtrl(Shift) then
|
|
begin
|
|
Exclude(FStates,tvsEditOnMouseUp);
|
|
CursorNode.MultiSelected:=not CursorNode.MultiSelected;
|
|
if CursorNode.MultiSelected then
|
|
FTreeNodes.FStartMultiSelected := CursorNode;
|
|
end
|
|
else
|
|
begin
|
|
if not CursorNdSelected then
|
|
Items.SelectOnlyThis(CursorNode)
|
|
else
|
|
Include(FStates, tvsSingleSelectOnMouseUp);
|
|
end;
|
|
end;
|
|
end
|
|
else if tvoEmptySpaceUnselect in Options then
|
|
ClearSelection;
|
|
end
|
|
else// multi click
|
|
if not (tvoNoDoubleClickExpand in Options) and (ssDouble in Shift)
|
|
and (Button = mbLeft) and (CursorNode<>nil) then
|
|
CursorNode.Expanded := not CursorNode.Expanded
|
|
else // empty space below last node
|
|
if (Button = mbLeft) and (CursorNode = nil) and (tvoEmptySpaceUnselect in Options) and
|
|
not AllowMultiSelectWithShift(Shift) and not AllowMultiSelectWithCtrl(Shift) then
|
|
ClearSelection;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseMove(Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
inherited MouseMove(Shift, x, y);
|
|
if (tvoAutoInsertMark in FOptions) then
|
|
UpdateInsertMark(X,Y);
|
|
UpdateTooltip(X, Y);
|
|
UpdateHotTrack(X, Y);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UpdateHotTrack(X, Y: Integer);
|
|
var
|
|
lNode, nodeUnderCursorY: TTreeNode;
|
|
R: TRect;
|
|
begin
|
|
FNodeUnderCursor := nil;
|
|
if Cursor = crHandPoint then
|
|
Cursor := crDefault;
|
|
if not (tvoHotTrack in FOptions) then Exit;
|
|
|
|
FNodeUnderCursor := GetNodeAt(X, Y);
|
|
if Assigned(FNodeUnderCursor) then
|
|
Cursor := crHandPoint;
|
|
|
|
// Invalidate;
|
|
// Too global, can lead to massive flicker (issue #41290). Replaced by code below.
|
|
// Invalidate only the affected lines (at least if not MultiSelect)
|
|
// Affected lines are: where the cursor is now, and where it was before.
|
|
|
|
if Self.MultiSelect then
|
|
Invalidate
|
|
else
|
|
begin
|
|
// Invalidate the previous hot node
|
|
nodeUnderCursorY := GetNodeAtY(Y); // Instead of GetNodeAt(X, Y). Need to trigger redraw across full row for OwnerDraw
|
|
if FHotTrackedPrevNodeIdx >= Items.Count then
|
|
FHotTrackedPrevNodeIdx := -1;
|
|
if FHotTrackedPrevNodeIdx > -1 then
|
|
begin
|
|
lNode := Items[FHotTrackedPrevNodeIdx];
|
|
if Assigned(lNode) and lNode.Visible then
|
|
begin
|
|
R := lNode.DisplayRect(False);
|
|
InvalidateRect(Handle, @R, True);
|
|
end;
|
|
FHotTrackedPrevNodeIdx := -1;
|
|
end;
|
|
// Invalidate the current hot node
|
|
if Assigned(nodeUnderCursorY) then
|
|
begin
|
|
R := nodeUnderCursorY.DisplayRect(False);
|
|
InvalidateRect(Handle, @R, True);
|
|
FHotTrackedPrevNodeIdx := nodeUnderCursorY.AbsoluteIndex;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseUp(Button: TMouseButton; Shift: TShiftState;
|
|
X, Y: Integer);
|
|
var
|
|
aMouseDownNode, aMouseUpNode: TTreeNode;
|
|
begin
|
|
// must hide hint window in mouse up to receive redirected mouse up messages
|
|
if (FHintWnd<>nil) and FHintWnd.Visible then
|
|
FHintWnd.Hide;
|
|
inherited MouseUp(Button, Shift, X, Y);
|
|
if (Button = mbRight) and (Shift = [ssRight]) and Assigned(PopupMenu) then
|
|
exit;
|
|
if Button=mbLeft then
|
|
begin
|
|
MouseCapture := False;
|
|
if FStates * [tvsDblClicked, tvsTripleClicked, tvsQuadClicked] = [] then
|
|
begin
|
|
//AquirePrimarySelection;
|
|
aMouseDownNode:=GetNodeAt(FMouseDownPos.X,FMouseDownPos.Y);
|
|
aMouseUpNode:=GetNodeAt(X,Y);
|
|
if (abs(FMouseDownPos.X-X)+abs(FMouseDownPos.Y-Y)<10)
|
|
and (aMouseDownNode=aMouseUpNode) then
|
|
begin
|
|
// mouse up on mouse-down node
|
|
if (tvsEditOnMouseUp in FStates) and (not ReadOnly) then
|
|
BeginEditing(Selected)
|
|
else if (tvsSingleSelectOnMouseUp in FStates) then
|
|
Items.SelectOnlyThis(aMouseUpNode);
|
|
end;
|
|
end;
|
|
end;
|
|
FStates:=FStates-[tvsDblClicked,tvsTripleClicked,tvsQuadClicked,
|
|
tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveEnd(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
lNode := Items.GetLastExpandedSubNode;
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveHome(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
lNode := Items.GetFirstVisibleEnabledNode;
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MovePageDown(ASelect: Boolean);
|
|
var
|
|
I: Integer;
|
|
lNode, NextNode: TTreeNode;
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
if lNode = nil then
|
|
lNode := Items.GetFirstVisibleEnabledNode;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
I := Pred(ClientHeight div DefaultItemHeight);
|
|
while (I > 0) do
|
|
begin
|
|
NextNode := lNode.GetNextExpanded(true);
|
|
if NextNode <> nil then
|
|
begin
|
|
lNode := NextNode;
|
|
Dec(I);
|
|
end
|
|
else Break;
|
|
end;
|
|
end;
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MovePageUp(ASelect: Boolean);
|
|
var
|
|
I: Integer;
|
|
lNode, PrevNode: TTreeNode;
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
if lNode = nil then
|
|
lNode := Items.GetFirstVisibleEnabledNode;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
I := Pred(ClientHeight div DefaultItemHeight);
|
|
while (I > 0) do
|
|
begin
|
|
PrevNode := lNode.GetPrevExpanded(true);
|
|
if PrevNode <> nil then
|
|
begin
|
|
lNode := PrevNode;
|
|
Dec(I);
|
|
end
|
|
else Break;
|
|
end;
|
|
end;
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveLeft(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
if (tvoAllowMultiSelect in FOptions) and ASelect then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
if lNode.Expanded then
|
|
lNode.Expanded := False
|
|
else
|
|
if lNode.Parent <> nil then
|
|
lNode := lNode.Parent;
|
|
end;
|
|
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveRight(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
if (tvoAllowMultiSelect in FOptions) and ASelect then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
if lNode.Expanded then
|
|
lNode := lNode.GetNextExpanded
|
|
else
|
|
lNode.Expanded := True;
|
|
end;
|
|
|
|
if lNode <> nil then
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveExpand(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
if (tvoAllowMultiSelect in FOptions) and ASelect then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
lNode.Expanded := True;
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveCollapse(ASelect: Boolean);
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
if (tvoAllowMultiSelect in FOptions) and ASelect then
|
|
lNode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
lNode := Selected;
|
|
|
|
if lNode <> nil then
|
|
begin
|
|
lNode.Expanded := False;
|
|
MoveSelection(lNode, ASelect);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveSelection(ANewNode: TTreeNode; ASelect: Boolean);
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
begin
|
|
if ASelect then
|
|
FTreeNodes.MultiSelect(ANewNode, False)
|
|
else begin
|
|
FTreeNodes.SelectOnlyThis(ANewNode);
|
|
end;
|
|
end else
|
|
Selected := ANewNode;
|
|
ANewNode.MakeVisible;
|
|
|
|
UpdateScrollbars;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MouseLeave;
|
|
begin
|
|
FStates:=FStates-[tvsDblClicked,tvsTripleClicked,tvsQuadClicked,
|
|
tvsEditOnMouseUp,tvsSingleSelectOnMouseUp];
|
|
if Assigned(FHintWnd) and FHintWnd.Visible
|
|
and ((WidgetSet.GetLCLCapability(lcTransparentWindow) = LCL_CAPABILITY_YES)
|
|
or not PtInRect(ClientRect, ScreenToClient(Mouse.CursorPos))) then
|
|
FHintWnd.Hide;
|
|
|
|
if tvoHotTrack in FOptions then
|
|
begin
|
|
FNodeUnderCursor:=nil;
|
|
FHotTrackedPrevNodeIdx:=-1;
|
|
Cursor:=crDefault;
|
|
Invalidate;
|
|
end;
|
|
|
|
inherited MouseLeave;
|
|
end;
|
|
|
|
procedure TCustomTreeView.NodeChanged(Node: TTreeNode; ChangeReason: TTreeNodeChangeReason);
|
|
begin
|
|
if assigned(FOnNodeChanged) then
|
|
OnNodeChanged(self,Node,ChangeReason);
|
|
end;
|
|
|
|
function TCustomTreeView.NodeHasChildren(Node: TTreeNode): Boolean;
|
|
begin
|
|
if Assigned(FOnHasChildren) then
|
|
Result := FOnHasChildren(Self, Node)
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Notification(AComponent: TComponent; Operation: TOperation);
|
|
begin
|
|
inherited Notification(AComponent, Operation);
|
|
if Operation = opRemove then begin
|
|
if AComponent = Images then Images := nil;
|
|
if AComponent = StateImages then StateImages := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetImages(Value: TCustomImageList);
|
|
var
|
|
AImageHeight: Integer;
|
|
begin
|
|
if Images = Value then exit;
|
|
if Images <> nil then
|
|
Images.UnRegisterChanges(FImageChangeLink);
|
|
FImages := Value;
|
|
if Images <> nil then begin
|
|
Images.RegisterChanges(FImageChangeLink);
|
|
Images.FreeNotification(Self);
|
|
AImageHeight := Images.HeightForPPI[ImagesWidth, Font.PixelsPerInch];
|
|
if DefaultItemHeight<AImageHeight+FDefItemSpace then
|
|
DefaultItemHeight:=AImageHeight+FDefItemSpace;
|
|
end;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetImagesWidth(const aImagesWidth: Integer);
|
|
begin
|
|
if FImagesWidth = aImagesWidth then Exit;
|
|
FImagesWidth := aImagesWidth;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetInsertMarkNode(const AValue: TTreeNode);
|
|
var
|
|
InvalidateNeeded: Boolean;
|
|
begin
|
|
if FInsertMarkNode=AValue then exit;
|
|
InvalidateNeeded:=IsInsertMarkVisible;
|
|
FInsertMarkNode:=AValue;
|
|
InvalidateNeeded:=InvalidateNeeded or IsInsertMarkVisible;
|
|
if InvalidateNeeded then Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetInsertMarkType(
|
|
const AValue: TTreeViewInsertMarkType);
|
|
var
|
|
InvalidateNeeded: Boolean;
|
|
begin
|
|
if FInsertMarkType=AValue then exit;
|
|
InvalidateNeeded:=IsInsertMarkVisible;
|
|
FInsertMarkType:=AValue;
|
|
InvalidateNeeded:=InvalidateNeeded or IsInsertMarkVisible;
|
|
if InvalidateNeeded then Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetStateImages(Value: TCustomImageList);
|
|
var
|
|
AStateImageHeight: Integer;
|
|
begin
|
|
if FStateImages=Value then exit;
|
|
if StateImages <> nil then
|
|
StateImages.UnRegisterChanges(FStateChangeLink);
|
|
FStateImages := Value;
|
|
if StateImages <> nil then begin
|
|
StateImages.RegisterChanges(FStateChangeLink);
|
|
StateImages.FreeNotification(Self);
|
|
AStateImageHeight := StateImages.HeightForPPI[StateImagesWidth, Font.PixelsPerInch];
|
|
if DefaultItemHeight<AStateImageHeight+FDefItemSpace then
|
|
DefaultItemHeight:=AStateImageHeight+FDefItemSpace;
|
|
end;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SetStateImagesWidth(const aStateImagesWidth: Integer);
|
|
begin
|
|
if FStateImagesWidth = aStateImagesWidth then Exit;
|
|
FStateImagesWidth := aStateImagesWidth;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TCustomTreeView.LoadFromFile(const FileName: string);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.LoadFromStream(Stream: TStream);
|
|
begin
|
|
with TTreeStrings.Create(Items) do
|
|
try
|
|
LoadTreeFromStream(Stream);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SaveToFile(const FileName: string);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TFileStream.Create(FileName, fmCreate);
|
|
try
|
|
SaveToStream(Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.SaveToStream(Stream: TStream);
|
|
begin
|
|
with TTreeStrings.Create(Items) do
|
|
try
|
|
SaveTreeToStream(Stream);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.ScrollView(DeltaX, DeltaY: Integer);
|
|
var
|
|
ScrollArea: TRect;
|
|
ScrollFlags: Integer;
|
|
begin
|
|
if (DeltaX=0) and (DeltaY=0) then
|
|
Exit;
|
|
|
|
Include(FStates,tvsScrollbarChanged);
|
|
ScrollFlags := SW_INVALIDATE or SW_ERASE;
|
|
ScrollArea := ClientRect;
|
|
InflateRect(ScrollArea, -BorderWidth, -BorderWidth);
|
|
ScrollWindowEx(Handle, DeltaX, DeltaY, @ScrollArea, @ScrollArea, 0, nil, ScrollFlags);
|
|
UpdateScrollbars;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMVScroll(var Msg: TLMScroll);
|
|
begin
|
|
case Msg.ScrollCode of
|
|
// Scrolls to start / end of the text
|
|
SB_TOP: ScrolledTop := 0;
|
|
SB_BOTTOM: ScrolledTop := GetMaxScrollTop;
|
|
// Scrolls one line up / down
|
|
SB_LINEDOWN: ScrolledTop := ScrolledTop + FDefItemHeight;
|
|
SB_LINEUP: ScrolledTop := ScrolledTop - FDefItemHeight;
|
|
// Scrolls one page of lines up / down
|
|
SB_PAGEDOWN: ScrolledTop := ScrolledTop + ClientHeight
|
|
- FDefItemHeight;
|
|
SB_PAGEUP: ScrolledTop := ScrolledTop - ClientHeight
|
|
+ FDefItemHeight;
|
|
// Scrolls to the current scroll bar position
|
|
SB_THUMBPOSITION,
|
|
SB_THUMBTRACK: ScrolledTop := Msg.Pos;
|
|
|
|
SB_ENDSCROLL: ; // Ends scrolling
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMHScroll(var Msg: TLMScroll);
|
|
begin
|
|
case Msg.ScrollCode of
|
|
// Scrolls to start / end of the text
|
|
SB_LEFT: ScrolledLeft := 0;
|
|
SB_RIGHT: ScrolledLeft := GetMaxScrollLeft;
|
|
// Scrolls one line left / right
|
|
SB_LINERIGHT: ScrolledLeft := ScrolledLeft + FDefItemHeight div 2;
|
|
SB_LINELEFT: ScrolledLeft := ScrolledLeft - FDefItemHeight div 2;
|
|
// Scrolls one page of lines left / right
|
|
SB_PAGERIGHT: ScrolledLeft := ScrolledLeft + ClientHeight
|
|
- FDefItemHeight;
|
|
SB_PAGELEFT: ScrolledLeft := ScrolledLeft - ClientHeight
|
|
+ FDefItemHeight;
|
|
// Scrolls to the current scroll bar position
|
|
SB_THUMBPOSITION,
|
|
SB_THUMBTRACK: ScrolledLeft := Msg.Pos;
|
|
|
|
SB_ENDSCROLL: ;// Ends scrolling
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMLButtonDown(var AMessage: TLMLButtonDown);
|
|
begin
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.WMLButtonDown A ',Name,':',ClassName,' ');
|
|
{$ENDIF}
|
|
Exclude(FStates,tvsDragged);
|
|
inherited WMLButtonDown(AMessage);
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TCustomTreeView.WMLButtonDown END ',Name,':',ClassName,' ');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMSetFocus(var Message: TLMSetFocus);
|
|
begin
|
|
Invalidate;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WMKillFocus(var Message: TLMKillFocus);
|
|
begin
|
|
Invalidate;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Resize;
|
|
begin
|
|
FStates:=FStates+[tvsScrollbarChanged,tvsBottomItemNeedsUpdate];
|
|
inherited Resize;
|
|
UpdateScrollbars;
|
|
end;
|
|
|
|
function TCustomTreeView.GetSelectedChildAccessibleObject: TLazAccessibleObject;
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
Result := nil;
|
|
lNode := GetSelection();
|
|
if lNode = nil then Exit;
|
|
Result := FAccessibleObject.GetChildAccessibleObjectWithDataObject(lNode);
|
|
end;
|
|
|
|
function TCustomTreeView.GetChildAccessibleObjectAtPos(APos: TPoint): TLazAccessibleObject;
|
|
var
|
|
lNode: TTreeNode;
|
|
begin
|
|
Result := nil;
|
|
lNode := GetNodeAt(APos.X, APos.Y);
|
|
//if lNode = nil then DebugLn('[TCustomTreeView.GetChildAccessibleObjectAtPos] lNode=nil')
|
|
//else DebugLn('[TCustomTreeView.GetChildAccessibleObjectAtPos] lNode=' + lNode.Text);
|
|
if lNode = nil then Exit;
|
|
Result := FAccessibleObject.GetChildAccessibleObjectWithDataObject(lNode);
|
|
end;
|
|
|
|
procedure TCustomTreeView.InternalSelectionChanged;
|
|
begin
|
|
if FSelectionChangeEventLock > 0 then
|
|
Include(FStates, tvsSelectionChanged)
|
|
else
|
|
begin
|
|
Exclude(FStates, tvsSelectionChanged);
|
|
DoSelectionChanged;
|
|
FChangeTimer.Enabled := False;
|
|
if not FCallingChange then // Prevent recursive loop from OnChange handler.
|
|
FChangeTimer.Enabled := True;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.AllowMultiSelectWithCtrl(AState: TShiftState): Boolean;
|
|
begin
|
|
Result := (ssCtrl in AState) and (msControlSelect in FMultiSelectStyle);
|
|
end;
|
|
|
|
function TCustomTreeView.AllowMultiSelectWithShift(AState: TShiftState): Boolean;
|
|
begin
|
|
Result := (ssShift in AState) and (msShiftSelect in FMultiSelectStyle);
|
|
end;
|
|
|
|
class procedure TCustomTreeView.WSRegisterClass;
|
|
begin
|
|
inherited WSRegisterClass;
|
|
RegisterCustomTreeView;
|
|
end;
|
|
|
|
class function TCustomTreeView.GetControlClassDefaultSize: TSize;
|
|
begin
|
|
Result.CX := 121;
|
|
Result.CY := 97;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Added(Node: TTreeNode);
|
|
begin
|
|
if Assigned(OnAddition) then OnAddition(Self,Node);
|
|
end;
|
|
|
|
{ CustomDraw support }
|
|
|
|
procedure TCustomTreeView.EditorEditingDone(Sender: TObject);
|
|
var
|
|
WasFocused: Boolean;
|
|
begin
|
|
WasFocused := Assigned(FEditor) and FEditor.Focused;
|
|
EndEditing;
|
|
if WasFocused then
|
|
SetFocus;
|
|
end;
|
|
|
|
procedure TCustomTreeView.EditorKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
var
|
|
WasFocused: Boolean;
|
|
begin
|
|
if (Key in [VK_RETURN, VK_ESCAPE]) and (Shift = []) then
|
|
begin
|
|
WasFocused := Assigned(FEditor) and FEditor.Focused;
|
|
EndEditing(Key = VK_ESCAPE);
|
|
if WasFocused then
|
|
SetFocus;
|
|
Key := 0; // key was handled
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.CanvasChanged(Sender: TObject);
|
|
begin
|
|
Include(FStates,tvsCanvasChanged);
|
|
end;
|
|
|
|
procedure TCustomTreeView.DragScrollTimerTick(Sender: TObject);
|
|
const
|
|
cScrollDelta = 10;
|
|
var
|
|
Pnt: TPoint;
|
|
begin
|
|
Pnt := ScreenToClient(Mouse.CursorPos);
|
|
if (Pnt.X >= 0) and (Pnt.X < ClientWidth) then
|
|
begin
|
|
if (Pnt.Y >= 0) and (Pnt.Y < FDragScrollMargin) then
|
|
ScrolledTop := ScrolledTop - cScrollDelta
|
|
else
|
|
if (Pnt.Y >= ClientHeight-FDragScrollMargin) and (Pnt.Y < ClientHeight) then
|
|
ScrolledTop := ScrolledTop + cScrollDelta;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.ClearSelection(KeepPrimary: Boolean);
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
Items.ClearMultiSelection(not KeepPrimary)
|
|
else
|
|
if not KeepPrimary then Selected := nil;
|
|
end;
|
|
|
|
function TCustomTreeView.IsCustomDrawn(Target: TCustomDrawTarget;
|
|
Stage: TCustomDrawStage): Boolean;
|
|
begin
|
|
{ Tree view doesn't support erase notifications }
|
|
if Stage = cdPrePaint then begin
|
|
if Target = dtItem then
|
|
Result := Assigned(FOnCustomDrawItem)
|
|
or Assigned(FOnAdvancedCustomDrawItem)
|
|
else if Target = dtControl then
|
|
Result := Assigned(FOnCustomDraw) or Assigned(FOnAdvancedCustomDraw) or
|
|
Assigned(FOnCustomDrawItem) or Assigned(FOnAdvancedCustomDrawItem)
|
|
else
|
|
Result := False;
|
|
end else begin
|
|
if Target = dtItem then
|
|
Result := Assigned(FOnAdvancedCustomDrawItem)
|
|
else if Target = dtControl then
|
|
Result := Assigned(FOnAdvancedCustomDraw)
|
|
or Assigned(FOnAdvancedCustomDrawItem)
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.CustomDraw(const ARect: TRect;
|
|
Stage: TCustomDrawStage): Boolean;
|
|
begin
|
|
Result := True;
|
|
if (Stage = cdPrePaint) and Assigned(FOnCustomDraw) then
|
|
FOnCustomDraw(Self, ARect, Result);
|
|
if Assigned(FOnAdvancedCustomDraw) then
|
|
FOnAdvancedCustomDraw(Self, ARect, Stage, Result);
|
|
end;
|
|
|
|
function TCustomTreeView.CustomDrawItem(Node: TTreeNode;
|
|
State: TCustomDrawState;
|
|
Stage: TCustomDrawStage; var PaintImages: Boolean): Boolean;
|
|
begin
|
|
Result := True;
|
|
PaintImages := True;
|
|
if (Stage = cdPrePaint) and Assigned(FOnCustomDrawItem) then
|
|
FOnCustomDrawItem(Self, Node, State, Result);
|
|
if Assigned(FOnAdvancedCustomDrawItem) then
|
|
FOnAdvancedCustomDrawItem(Self, Node, State, Stage, PaintImages, Result);
|
|
end;
|
|
|
|
procedure TCustomTreeView.ConsistencyCheck;
|
|
var OldMaxRight, OldLastTop, OldMaxLvl: integer;
|
|
OldTopItem, OldBottomItem: TTreeNode;
|
|
begin
|
|
if Canvas=nil then
|
|
LazTracer.RaiseGDBException('Canvas=nil');
|
|
if FDefItemHeight<0 then
|
|
LazTracer.RaiseGDBException('FDefItemHeight='+IntToStr(FDefItemHeight));
|
|
if FIndent<0 then
|
|
LazTracer.RaiseGDBException('FIndent='+IntToStr(FIndent));
|
|
if FMaxRight<0 then
|
|
LazTracer.RaiseGDBException('FMaxRight='+IntToStr(FMaxRight));
|
|
if FTreeNodes=nil then
|
|
LazTracer.RaiseGDBException('FTreeNodes=nil');
|
|
FTreeNodes.ConsistencyCheck;
|
|
if Items.FUpdateCount<0 then
|
|
LazTracer.RaiseGDBException('FUpdateCount='+IntToStr(Items.FUpdateCount));
|
|
if (not (tvsTopsNeedsUpdate in FStates)) then begin
|
|
if Items.GetLastSubNode<>nil then begin
|
|
OldLastTop:=Items.GetLastSubNode.Top;
|
|
Include(FStates,tvsTopsNeedsUpdate);
|
|
UpdateAllTops;
|
|
if OldLastTop<>Items.GetLastSubNode.Top then
|
|
LazTracer.RaiseGDBException('OldLastTop='+DbgS(OldLastTop)
|
|
+'<>Items.GetLastSubNode.Top='+DbgS(Items.GetLastSubNode.Top));
|
|
end;
|
|
end;
|
|
if not (tvsMaxRightNeedsUpdate in FStates) then begin
|
|
OldMaxRight:=FMaxRight;
|
|
Include(FStates,tvsMaxRightNeedsUpdate);
|
|
UpdateMaxRight;
|
|
if OldMaxRight<>FMaxRight then
|
|
LazTracer.RaiseGDBException('OldMaxRight<>FMaxRight');
|
|
end;
|
|
if not (tvsMaxLvlNeedsUpdate in FStates) then begin
|
|
OldMaxLvl:=FMaxLvl;
|
|
Include(FStates,tvsMaxLvlNeedsUpdate);
|
|
UpdateMaxLvl;
|
|
if OldMaxLvl<>FMaxLvl then
|
|
LazTracer.RaiseGDBException('OldMaxLvl<>FMaxLvl');
|
|
end;
|
|
if (tvsIsEditing in FStates) and (FSelectedNode=nil) then
|
|
LazTracer.RaiseGDBException('');
|
|
if (FSelectedNode<>nil) then begin
|
|
if not FSelectedNode.IsVisible then
|
|
LazTracer.RaiseGDBException('not FSelectedNode.IsVisible');
|
|
end;
|
|
if not (tvsTopItemNeedsUpdate in FStates) then begin
|
|
OldTopItem:=FTopItem;
|
|
UpdateTopItem;
|
|
if FTopItem<>OldTopItem then
|
|
LazTracer.RaiseGDBException('FTopItem<>OldTopItem');
|
|
end;
|
|
if not (tvsBottomItemNeedsUpdate in FStates) then begin
|
|
OldBottomItem:=FBottomItem;
|
|
UpdateBottomItem;
|
|
if FBottomItem<>OldBottomItem then
|
|
LazTracer.RaiseGDBException('FBottomItem<>OldBottomItem');
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.WriteDebugReport(const Prefix: string; AllNodes: boolean);
|
|
begin
|
|
DbgOut('%s%s.WriteDebugReport Self=%p', [Prefix, ClassName, Pointer(Self)]);
|
|
ConsistencyCheck;
|
|
DebugLn('');
|
|
if AllNodes then begin
|
|
Items.WriteDebugReport(Prefix+' ',true);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.LockSelectionChangeEvent;
|
|
begin
|
|
inc(FSelectionChangeEventLock);
|
|
end;
|
|
|
|
procedure TCustomTreeView.UnlockSelectionChangeEvent;
|
|
begin
|
|
dec(FSelectionChangeEventLock);
|
|
if FSelectionChangeEventLock<0 then
|
|
LazTracer.RaiseGDBException('TCustomTreeView.UnlockSelectionChangeEvent');
|
|
if (FSelectionChangeEventLock=0) and (tvsSelectionChanged in FStates) then
|
|
InternalSelectionChanged;
|
|
end;
|
|
|
|
function TCustomTreeView.GetFirstMultiSelected: TTreeNode;
|
|
begin
|
|
Result := Items.FFirstMultiSelected;
|
|
end;
|
|
|
|
function TCustomTreeView.GetLastMultiSelected: TTreeNode;
|
|
begin
|
|
Result := Items.FLastMultiSelected;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Select(Node: TTreeNode; ShiftState: TShiftState = []);
|
|
begin
|
|
if (tvoAllowMultiSelect in FOptions) and AllowMultiSelectWithCtrl(ShiftState) then
|
|
Node.Selected := True
|
|
else begin
|
|
ClearSelection;
|
|
Selected := Node;
|
|
if (tvoAllowMultiSelect in FOptions) then
|
|
Node.Selected := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Select(const Nodes: array of TTreeNode);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
ClearSelection;
|
|
if Length(Nodes)>0 then begin
|
|
Selected := Nodes[0];
|
|
if tvoAllowMultiSelect in FOptions then
|
|
for I := Low(Nodes) to High(Nodes) do
|
|
Nodes[I].Selected := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.Select(Nodes: TList);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
ClearSelection;
|
|
if Nodes.Count>0 then begin
|
|
Selected := TTreeNode(Nodes[0]);
|
|
if tvoAllowMultiSelect in FOptions then
|
|
for I := 0 to Nodes.Count - 1 do
|
|
TTreeNode(Nodes[I]).Selected := True;
|
|
end;
|
|
end;
|
|
|
|
function TCustomTreeView.SelectionVisible: boolean;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
ANode:=GetFirstMultiSelected;
|
|
if (ANode<>nil) and (ANode.GetNextMultiSelected<>nil) then begin
|
|
// 2 or more elements => a real multi selection =>
|
|
// is visible if even one of its nodes is partly visible
|
|
while (ANode<>nil) do begin
|
|
if ANode.IsVisible then begin
|
|
Result:=true;
|
|
exit;
|
|
end;
|
|
ANode:=ANode.GetNextMultiSelected;
|
|
end;
|
|
Result:=false;
|
|
end else begin
|
|
if ANode=nil then
|
|
ANode:=Selected;
|
|
Result:=(ANode<>nil) and (ANode.IsFullHeightVisible);
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.MakeSelectionVisible;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if SelectionVisible then exit;
|
|
ANode:=GetFirstMultiSelected;
|
|
if (ANode=nil) then
|
|
ANode:=Selected;
|
|
if ANode=nil then exit;
|
|
ANode.MakeVisible;
|
|
end;
|
|
|
|
procedure TCustomTreeView.ClearInvisibleSelection;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then begin
|
|
Items.ClearMultiSelection(True); // Now clears all multi-selected
|
|
end
|
|
else begin
|
|
ANode := Selected; // Clear a single selection only if not visible
|
|
if Assigned(ANode) and not ANode.Visible then
|
|
ANode.Selected:=False; // Selected := nil;
|
|
end;
|
|
end;
|
|
|
|
{ When HideSelection is true (and the tree is not focused), the selection is
|
|
really hidden only in Delphi mode. Lazarus mode ignores the HideSelection
|
|
setting. }
|
|
function TCustomTreeView.SelectionIsHidden: Boolean;
|
|
begin
|
|
Result := (not Focused) and HideSelection and (FHideSelectionMode = hsmHide);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveToNextNode(ASelect: Boolean);
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
ANode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
ANode := Selected;
|
|
if ANode <> nil then
|
|
ANode := ANode.GetNextVisible(true)
|
|
else
|
|
ANode := FTreeNodes.GetFirstVisibleEnabledNode;
|
|
if ANode <> nil then
|
|
MoveSelection(ANode, ASelect);
|
|
end;
|
|
|
|
procedure TCustomTreeView.MoveToPrevNode(ASelect: Boolean);
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
if tvoAllowMultiSelect in FOptions then
|
|
ANode := FTreeNodes.FLastMultiSelected
|
|
else
|
|
ANode := Selected;
|
|
if ANode <> nil then
|
|
ANode := ANode.GetPrevVisible(true)
|
|
else
|
|
ANode := Items.GetFirstVisibleEnabledNode;
|
|
if ANode <> nil then
|
|
MoveSelection(ANode, ASelect);
|
|
end;
|
|
|
|
function TCustomTreeView.StoreCurrentSelection: TStringList;
|
|
var
|
|
ANode: TTreeNode;
|
|
begin
|
|
Result:=TStringList.Create;
|
|
ANode:=Selected;
|
|
while ANode<>nil do begin
|
|
Result.Insert(0,ANode.Text);
|
|
ANode:=ANode.Parent;
|
|
end;
|
|
end;
|
|
|
|
procedure TCustomTreeView.ApplyStoredSelection(ASelection: TStringList; FreeList: boolean);
|
|
var
|
|
ANode: TTreeNode;
|
|
CurText: string;
|
|
begin
|
|
ANode:=nil;
|
|
while ASelection.Count>0 do begin
|
|
CurText:=ASelection[0];
|
|
if ANode=nil then
|
|
ANode:=Items.GetFirstNode
|
|
else
|
|
ANode:=ANode.GetFirstChild;
|
|
while (ANode<>nil) and (ANode.Text<>CurText) do
|
|
ANode:=ANode.GetNextSibling;
|
|
if ANode=nil then break;
|
|
ASelection.Delete(0);
|
|
end;
|
|
if ANode<>nil then
|
|
Selected:=ANode;
|
|
if FreeList then
|
|
ASelection.Free;
|
|
end;
|
|
|
|
// back to comctrls.pp
|