lazarus/components/ideintf/componenttreeview.pas

927 lines
31 KiB
ObjectPascal

{
*****************************************************************************
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Author: Mattias Gaertner
Abstract:
TComponentTreeView is a component to show the child components of a
TComponent. TControls are shown in a hierachic view.
It supports
- multi selecting components
- editing the creation order
- editing the TControl.Parent hierachy
For an usage example, see the object inspector.
}
unit ComponentTreeView;
{$mode objfpc}{$H+}
{off $DEFINE VerboseComponentTVWalker}
interface
uses
Classes, SysUtils, TypInfo, AVL_Tree,
// LazUtils
LazUtilities, LazLoggerBase, LazTracer, AvgLvlTree,
// LCL
Dialogs, Forms, Controls, ComCtrls,
// IdeIntf
ObjInspStrConsts, PropEdits, PropEditUtils, ComponentEditors, IDEImagesIntf;
type
TCTVGetImageIndexEvent = procedure(APersistent: TPersistent;
var AIndex: integer) of object;
TCTVParentAcceptsChildEvent = function(aParent, aChild, aLookupRoot: TPersistent): boolean of object;
TCTVSetParentEvent = procedure(aChild, aParent, aLookupRoot: TPersistent) of object;
// First 4 are ways to change ZOrder, zoDelete deletes a component.
TZOrderDelete = (zoToFront, zoToBack, zoForward, zoBackward, zoDelete);
{ TComponentTreeView }
TComponentTreeView = class(TCustomTreeView)
private
FComponentList: TBackupComponentList;
FOnParentAcceptsChild: TCTVParentAcceptsChildEvent;
FOnSetParent: TCTVSetParentEvent;
FPropertyEditorHook: TPropertyEditorHook;
// Map of Root component -> TAVLTree of collapsed components.
FRoot2CollapasedMap: TPointerToPointerTree;
FCollapsedComps: TAVLTree; // The current list of collapsed components.
FDrawWholeTree: Boolean;
FZOrderDelCommand: TZOrderDelete;
FPreviousDeleted: TPersistent; // Delete command can be called twice. Keep track.
// Events
FOnComponentGetImageIndex: TCTVGetImageIndexEvent;
FOnModified: TNotifyEvent;
function AddOrGetPersNode(AParentNode: TTreeNode; APers: TPersistent;
ACapt: String): TTreeNode;
procedure AddChildren(AComponent: TComponent; ARootNode: TTreeNode);
procedure ChangeNode(ANode: TTreeNode);
function FindAndChange(APers: TPersistent; AZOrderDel: TZOrderDelete): Boolean;
function GetRootObject: TPersistent;
function GetSelection: TPersistentSelectionList;
function IterateTree(ANode: TTreeNode; APers: TPersistent): TTreeNode;
procedure NodeCollapsed(Sender: TObject; Node: TTreeNode);
procedure NodeExpanded(Sender: TObject; Node: TTreeNode);
procedure RestoreExpand(ANode: TTreeNode);
procedure SetPropertyEditorHook(AValue: TPropertyEditorHook);
procedure SetSelection(NewSelection: TPersistentSelectionList);
procedure UpdateCompNode(ANode: TTreeNode);
procedure UpdateSelNode(ANode: TTreeNode);
procedure UpdateSelected;
protected
procedure DoSelectionChanged; override;
function GetImageFor(APersistent: TPersistent):integer;
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;
var Accept: Boolean); override;
procedure DragCanceled; override;
procedure MouseLeave; override;
procedure GetComponentInsertMarkAt(X, Y: Integer;
out AnInsertMarkNode: TTreeNode;
out AnInsertMarkType: TTreeViewInsertMarkType);
procedure DoModified;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
procedure DragDrop(Source: TObject; X, Y: Integer); override;
procedure BuildComponentNodes(AWholeTree: Boolean);
procedure ChangeCompZOrder(APersistent: TPersistent; AZOrder: TZOrderDelete);
procedure DeleteComponentNode(APersistent: TPersistent);
procedure UpdateComponentNodesValues;
public
ImgIndexForm: Integer;
ImgIndexComponent: Integer;
ImgIndexControl: Integer;
ImgIndexBox: Integer;
ImgIndexCollection: Integer;
ImgIndexItem: Integer;
property Selection: TPersistentSelectionList read GetSelection
write SetSelection;
property HideSelection default false;
property PropertyEditorHook: TPropertyEditorHook
read FPropertyEditorHook write SetPropertyEditorHook;
property OnSelectionChanged;
property OnModified: TNotifyEvent read FOnModified write FOnModified;
property OnComponentGetImageIndex : TCTVGetImageIndexEvent
read FOnComponentGetImageIndex write FOnComponentGetImageIndex;
property OnParentAcceptsChild: TCTVParentAcceptsChildEvent read FOnParentAcceptsChild write FOnParentAcceptsChild;
property OnSetParent: TCTVSetParentEvent read FOnSetParent write FOnSetParent;
end;
implementation
{$R ../../images/componenttreeview.res}
type
TCollectionAccess = class(TCollection);
TComponentAccessor = class(TComponent);
{ TComponentWalker }
TComponentWalker = class
private
FCompTV: TComponentTreeView;
FLookupRoot: TComponent;
FNode: TTreeNode;
procedure AddCollection(AColl: TCollection; AParentNode: TTreeNode);
procedure AddOwnedPersistent(APers: TPersistent; const APropName: String;
AParentNode: TTreeNode);
procedure GetOwnedPersistents(APers: TPersistent; AParentNode: TTreeNode);
procedure Walk(AComponent: TComponent);
public
constructor Create(ACompTV: TComponentTreeView; ALookupRoot: TComponent);
end;
function CollectionCaption(ACollection: TCollection; DefaultName: string): string;
var
PropList: PPropList;
i, PropCount: Integer;
begin
Result := '';
if Result <> '' then
Result := TCollectionAccess(ACollection).PropName
else if DefaultName<>'' then
Result := DefaultName // DefaultName is the property name.
else if ACollection.Owner <> nil then
begin
PropCount := GetPropList(ACollection.Owner, PropList);
try // Find the property name where ACollection can be found.
for i := 0 to PropCount - 1 do
if (PropList^[i]^.PropType^.Kind = tkClass) then
if GetObjectProp(ACollection.Owner, PropList^[i], ACollection.ClassType) = ACollection then
begin
Result := PropList^[i]^.Name;
Break;
end;
finally
FreeMem(PropList);
end;
end;
if Result = '' then
Result := '<unknown collection>';
Result := Result + ': ' + ACollection.ClassName;
end;
function CollectionItemCaption(ACollItem: TCollectionItem): string;
begin
Result := IntToStr(ACollItem.Index)+' - '+ACollItem.DisplayName+': '+ACollItem.ClassName;
end;
function ComponentCaption(AComponent: TComponent): String;
begin
Result := AComponent.Name + ': ' + AComponent.ClassName;
end;
function CreateNodeCaption(APersistent: TPersistent; DefaultName: string): string;
begin
Result := APersistent.ClassName;
if APersistent is TComponent then
Result := ComponentCaption(TComponent(APersistent))
else if APersistent is TCollection then
Result := CollectionCaption(TCollection(APersistent), DefaultName)
else if APersistent is TCollectionItem then
Result := CollectionItemCaption(TCollectionItem(APersistent))
else if DefaultName<>'' then
Result := DefaultName + ':' + Result;
end;
{ TComponentWalker }
constructor TComponentWalker.Create(ACompTV: TComponentTreeView; ALookupRoot: TComponent);
begin
{$IFDEF VerboseComponentTVWalker}
DebugLn(['TComponentWalker.Create ALookupRoot=',DbgSName(ALookupRoot)]);
{$ENDIF}
FCompTV := ACompTV;
FLookupRoot := ALookupRoot;
end;
procedure TComponentWalker.AddCollection(AColl: TCollection; AParentNode: TTreeNode);
var
ItemNode: TTreeNode;
Item: TCollectionItem;
i: integer;
begin
for i := 0 to AColl.Count - 1 do
begin
Item := AColl.Items[i];
{$IFDEF VerboseComponentTVWalker}
DebugLn(['TComponentWalker.AddCollection, Adding CollectionItem ',
Item.DisplayName, ':', Item.ClassName]);
{$ENDIF}
ItemNode := FCompTV.AddOrGetPersNode(AParentNode, Item, CollectionItemCaption(Item));
// Collections can be nested. Add possible Collections under a CollectionItem.
GetOwnedPersistents(Item, ItemNode);
end;
end;
procedure TComponentWalker.AddOwnedPersistent(APers: TPersistent;
const APropName: String; AParentNode: TTreeNode);
var
TVNode: TTreeNode;
TheRoot: TPersistent;
begin
if (APers is TComponent) then
Assert(not (csDestroying in TComponent(APers).ComponentState), 'TComponentWalker: Comp is Destroying.');
TheRoot := GetLookupRootForComponent(APers);
{$IFDEF VerboseComponentTVWalker}
DebugLn(['TComponentWalker.AddOwnedPersistent'+
' PropName=',APropName,' Persistent=',DbgSName(APers),
' its root=',DbgSName(TheRoot),' FLookupRoot=',DbgSName(FLookupRoot)]);
{$ENDIF}
if TheRoot <> FLookupRoot then begin
DebugLn(['TComponentWalker.AddOwnedPersistent: TheRoot "', TheRoot, '" <> FLookupRoot "', FLookupRoot, '"']);
Exit;
end;
TVNode := FCompTV.AddOrGetPersNode(AParentNode, APers, CreateNodeCaption(APers, APropName));
if APers is TCollection then
AddCollection(TCollection(APers), TVNode);
end;
procedure TComponentWalker.GetOwnedPersistents(APers: TPersistent; AParentNode: TTreeNode);
var
PropList: PPropList;
PropCount, i: Integer;
PropInfo: PPropInfo;
PropPers: TPersistent;
begin
PropCount := GetPropList(APers, PropList);
try
for i := 0 to PropCount - 1 do begin
PropInfo:=PropList^[i];
if (PropInfo^.PropType^.Kind <> tkClass) then Continue;
{$IFDEF ShowOwnedObjectsOI}
PropPers := TPersistent(GetObjectProp(APers, PropInfo, TPersistent));
{$ELSE}
PropPers := TPersistent(GetObjectProp(APers, PropInfo, TCollection));
{$ENDIF}
if PropPers=nil then Continue;
if GetEditorClass(PropInfo, APers)=nil then Continue;
{$IFDEF VerboseComponentTVWalker}
DebugLn(['TComponentWalker.GetOwnedPersistents Persistent=',DbgSName(APers),
' PropName=',PropInfo^.Name,' FLookupRoot=',DbgSName(FLookupRoot)]);
{$ENDIF}
AddOwnedPersistent(PropPers, PropInfo^.Name, AParentNode);
end;
finally
FreeMem(PropList);
end;
end;
procedure TComponentWalker.Walk(AComponent: TComponent);
var
OldNode: TTreeNode;
Root: TComponent;
begin
if csDestroying in AComponent.ComponentState then begin
DebugLn(['TComponentWalker.Walk: ', AComponent, ' is Destroying.']);
Exit;
end;
if GetLookupRootForComponent(AComponent) <> FLookupRoot then begin
DebugLn(['TComponentWalker.Walk: "', AComponent, '" LookupRoot <> FLookupRoot "', FLookupRoot, '"']);
Exit;
end;
OldNode := FNode;
FNode := FCompTV.AddOrGetPersNode(FNode, AComponent, ComponentCaption(AComponent));
GetOwnedPersistents(AComponent, FNode);
if (csInline in AComponent.ComponentState) or (AComponent.Owner = nil) then
Root := AComponent
else
Root := AComponent.Owner;
if not ( (Root is TControl)
and (csOwnedChildrenNotSelectable in TControl(Root).ControlStyle) )
then
TComponentAccessor(AComponent).GetChildren(@Walk, Root);
FNode := OldNode;
end;
{ TComponentTreeView }
procedure TComponentTreeView.SetSelection(NewSelection: TPersistentSelectionList);
begin
if (PropertyEditorHook = nil) then
begin
if (FComponentList.LookupRoot = nil) then Exit;
FComponentList.Clear;
end
else if not NewSelection.ForceUpdate
and FComponentList.IsEqual(PropertyEditorHook.LookupRoot, NewSelection) then
begin
// nodes ok, but maybe node values need update
UpdateComponentNodesValues;
Exit;
end;
FComponentList.LookupRoot := PropertyEditorHook.LookupRoot;
FComponentList.Selection.Assign(NewSelection);
NewSelection.ForceUpdate:=false;
UpdateSelected;
end;
procedure TComponentTreeView.DoSelectionChanged;
var
ANode: TTreeNode;
APersistent: TPersistent;
NewSelection: TPersistentSelectionList;
begin
NewSelection := TPersistentSelectionList.Create;
try
if (PropertyEditorHook<>nil) and
(PropertyEditorHook.LookupRoot<>nil) and
(not (csDestroying in ComponentState)) then
begin
ANode := GetFirstMultiSelected;
while ANode <> nil do
begin
APersistent := TPersistent(ANode.Data);
if APersistent = nil then
RaiseGDBException('TComponentTreeView.DoSelectionChanged ANode.Data=nil');
if GetLookupRootForComponent(APersistent) = PropertyEditorHook.LookupRoot then
NewSelection.Add(APersistent);
ANode := ANode.GetNextMultiSelected;
end;
NewSelection.SortLike(FComponentList.Selection);
end;
if NewSelection.IsEqual(FComponentList.Selection) then
Exit;
FComponentList.Selection.Assign(NewSelection);
inherited DoSelectionChanged;
finally
NewSelection.Free;
end;
end;
procedure TComponentTreeView.DragDrop(Source: TObject; X, Y: Integer);
var
Node, ParentNode, SelNode: TTreeNode;
ACollection: TCollection;
AContainer, OldContainer: TWinControl;
AControl: TControl;
InsertType: TTreeViewInsertMarkType;
RootDesigner: TIDesigner;
CompEditDsg: TComponentEditorDesigner;
NewIndex, AIndex: Integer;
ok: Boolean;
ParentObj: TObject;
aLookupRoot, aParent, aChild: TPersistent;
begin
if PropertyEditorHook<>nil then
aLookupRoot := PropertyEditorHook.LookupRoot
else
aLookupRoot := nil;
GetComponentInsertMarkAt(X, Y, Node, InsertType);
SetInsertMark(nil, tvimNone);
if InsertType in [tvimAsNextSibling, tvimAsPrevSibling] then
ParentNode := Node.Parent
else
ParentNode := Node;
if Assigned(ParentNode) then
begin
// Find designer for Undo actions.
Assert(Assigned(FPropertyEditorHook), 'TComponentTreeView.DragDrop: PropertyEditorHook=Nil.');
RootDesigner := FindRootDesigner(FPropertyEditorHook.LookupRoot);
if (RootDesigner is TComponentEditorDesigner) then
CompEditDsg := TComponentEditorDesigner(RootDesigner) //if CompEditDsg.IsUndoLocked then Exit;
else
CompEditDsg := nil;
ParentObj:=TObject(ParentNode.Data);
if ParentObj is TWinControl then
begin
// reparent lcl TControl(s)
AContainer := TWinControl(ParentObj);
SelNode := GetFirstMultiSelected;
while Assigned(SelNode) do
begin
if TObject(SelNode.Data) is TControl then
begin
AControl := TControl(SelNode.Data);
ok:=false;
try
OldContainer := AControl.Parent;
AControl.Parent := AContainer;
if Assigned(CompEditDsg) then
CompEditDsg.AddUndoAction(AControl, uopChange, True, 'Parent',
OldContainer.Name, AContainer.Name);
ok:=true;
DoModified;
except
on E: Exception do
MessageDlg(oisError,
Format(oisUnableToChangeParentOfControlToNewParent,
[DbgSName(AControl), DbgSName(AContainer), LineEnding, E.Message]),
mtError, [mbOk], 0);
end;
if not ok then break;
end;
SelNode := SelNode.GetNextMultiSelected;
end;
end
else
if TObject(Node.Data) is TCollectionItem then
begin
// reorder collection item
ACollection := TCollectionItem(Node.Data).Collection;
ACollection.BeginUpdate;
case InsertType of
tvimAsNextSibling:
NewIndex := TCollectionItem(Node.Data).Index + 1;
tvimAsPrevSibling:
NewIndex := TCollectionItem(Node.Data).Index;
end;
SelNode := GetLastMultiSelected;
while Assigned(SelNode) do
begin
if (TObject(SelNode.Data) is TCollectionItem) and
(TCollectionItem(SelNode.Data).Collection = ACollection) then
begin
ok := False;
try
AIndex := TCollectionItem(SelNode.Data).Index;
if AIndex < NewIndex then
TCollectionItem(SelNode.Data).Index := NewIndex - 1
else
TCollectionItem(SelNode.Data).Index := NewIndex;
ok := True;
DoModified;
except
on E: Exception do
MessageDlg(E.Message, mtError, [mbOk], 0);
end;
if not ok then break;
end;
SelNode := SelNode.GetPrevMultiSelected;
end;
ACollection.EndUpdate;
end else if Assigned(OnSetParent) and (ParentObj is TPersistent) then begin
// default: reparent
aParent:=TPersistent(ParentObj);
SelNode := GetLastMultiSelected;
while Assigned(SelNode) do
begin
if (TObject(SelNode.Data) is TPersistent) then
begin
aChild:=TPersistent(TObject(SelNode.Data));
OnSetParent(aChild,aParent,aLookupRoot);
end;
SelNode := SelNode.GetPrevMultiSelected;
end;
end;
BuildComponentNodes(True);
end;
inherited DragDrop(Source, X, Y);
end;
procedure TComponentTreeView.DragOver(Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
var
Node: TTreeNode;
AnObject: TObject;
AControl: TControl absolute AnObject;
aLookupRoot, AContainer: TPersistent;
InsertType: TTreeViewInsertMarkType;
ParentNode: TTreeNode;
UserAccept: Boolean;
begin
//debugln('TComponentTreeView.DragOver START ',dbgs(Accept));
Accept:=false;
AContainer := nil;
GetComponentInsertMarkAt(X, Y, Node, InsertType);
SetInsertMark(Node, InsertType);
if PropertyEditorHook<>nil then
aLookupRoot := PropertyEditorHook.LookupRoot
else
aLookupRoot := nil;
// check new parent
ParentNode := Node;
if InsertType in [tvimAsNextSibling, tvimAsPrevSibling] then
ParentNode := ParentNode.Parent;
if Assigned(ParentNode) and Assigned(ParentNode.Data) then
begin
AnObject := TObject(ParentNode.Data);
if AnObject is TPersistent then
begin
AContainer:=TPersistent(AnObject);
end;
end;
//debugln(['TComponentTreeView.DragOver AContainer=',DbgSName(AContainer)]);
if AContainer<>nil then
begin
Node := GetFirstMultiSelected;
while Assigned(Node) do
begin
AnObject := TObject(Node.Data);
if Assigned(OnParentAcceptsChild) and (AnObject is TPersistent) then
begin
//debugln(['TComponentTreeView.DragOver Child=',DbgSName(AnObject),' AContainer=',DbgSName(AContainer)]);
if not OnParentAcceptsChild(AContainer,TPersistent(AnObject),aLookupRoot) then
break;
end else begin
// default rules for components:
// don't allow to move ancestor components
if (AnObject is TComponent) and
(csAncestor in TComponent(AnObject).ComponentState) then break;
if (AnObject is TControl) then
begin
if AnObject = AContainer then break;
if not (AContainer is TWinControl) then break;
//DebugLn(['TComponentTreeView.DragOver AControl=',DbgSName(AControl),' Parent=',DbgSName(AControl.Parent),' OldAccepts=',csAcceptsControls in AControl.Parent.ControlStyle]);
// check if new parent allows this control class
if not TWinControl(AContainer).CheckChildClassAllowed(AnObject.ClassType, False) then
break;
// check if one of the parents of the container is the control itself
if AControl.IsParentOf(TWinControl(AContainer)) then break;
// do not move children of a restricted parent to another parent
// e.g. TPage of TPageControl
if (AControl.Parent <> nil) and (AControl.Parent <> AContainer) and
(not (csAcceptsControls in AControl.Parent.ControlStyle)) then
break;
end;
end;
if (AnObject is TCollectionItem) then
begin
// allow to reorder collection items
if AnObject = AContainer then break;
if not (AContainer is TCollection) then
break;
if TCollectionItem(AnObject).Collection <> TCollection(AContainer) then
break;
end;
Node := Node.GetNextMultiSelected;
end;
Accept := (Node = nil);
end;
//debugln('TComponentTreeView.DragOver A ',dbgs(Accept));
UserAccept:=Accept;
inherited DragOver(Source, X, Y, State, UserAccept);
if Assigned(OnDragOver) then
Accept:=UserAccept;
//debugln('TComponentTreeView.DragOver B ',dbgs(Accept));
end;
procedure TComponentTreeView.DragCanceled;
begin
SetInsertMark(nil, tvimNone);
inherited DragCanceled;
end;
procedure TComponentTreeView.MouseLeave;
begin
SetInsertMark(nil,tvimNone);
inherited MouseLeave;
end;
procedure TComponentTreeView.GetComponentInsertMarkAt(X, Y: Integer; out
AnInsertMarkNode: TTreeNode; out AnInsertMarkType: TTreeViewInsertMarkType);
var
Node: TTreeNode;
begin
Node := GetFirstMultiSelected;
if (Node <> nil) and (TObject(Node.Data) is TControl) then
begin
// TWinControl allows only to add/remove children, but not at a specific position
AnInsertMarkNode := GetNodeAt(X,Y);
AnInsertMarkType := tvimAsFirstChild;
end
else
begin
GetInsertMarkAt(X, Y, AnInsertMarkNode, AnInsertMarkType);
if (Node <> nil) and (TObject(Node.Data) is TCollectionItem) then
if AnInsertMarkType = tvimAsFirstChild then
AnInsertMarkType := tvimAsPrevSibling;
end;
end;
procedure TComponentTreeView.DoModified;
begin
if Assigned(PropertyEditorHook) then
PropertyEditorHook.RefreshPropertyValues;
if Assigned(FOnModified) then
OnModified(Self);
end;
function TComponentTreeView.GetImageFor(APersistent: TPersistent): integer;
begin
Result := -1;
if Assigned(APersistent) then
begin
if (APersistent is TControl) then begin
if (csAcceptsControls in TControl(APersistent).ControlStyle) then
Result := ImgIndexBox
else
Result := ImgIndexControl;
end
else
if (APersistent is TComponent) then
Result := ImgIndexComponent
else
if (APersistent is TCollection) then
Result := ImgIndexCollection
else
if (APersistent is TCollectionItem) then
Result := ImgIndexItem;
end;
// finally, ask the designer such as TDesignerMediator to override it, if any
if Assigned(OnComponentGetImageIndex) then
OnComponentGetImageIndex(APersistent, Result);
end;
procedure TComponentTreeView.SetPropertyEditorHook(AValue: TPropertyEditorHook);
begin
if FPropertyEditorHook=AValue then exit;
FPropertyEditorHook:=AValue;
end;
function TComponentTreeView.GetSelection: TPersistentSelectionList;
begin
Result:=FComponentList.Selection;
end;
constructor TComponentTreeView.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
DragMode := dmAutomatic;
FComponentList:=TBackupComponentList.Create;
FRoot2CollapasedMap:=TPointerToPointerTree.Create;
Options := Options + [tvoAllowMultiselect, tvoAutoItemHeight, tvoKeepCollapsedNodes, tvoReadOnly];
MultiSelectStyle := MultiSelectStyle + [msShiftSelect];
ImgIndexForm := IDEImages.GetImageIndex('oi_form');
ImgIndexComponent := IDEImages.GetImageIndex('oi_comp');
ImgIndexControl := IDEImages.GetImageIndex('oi_control');
ImgIndexBox := IDEImages.GetImageIndex('oi_box');
ImgIndexCollection := IDEImages.GetImageIndex('oi_collection');
ImgIndexItem := IDEImages.GetImageIndex('oi_item');
Images := IDEImages.Images_16;
HideSelection := false;
end;
destructor TComponentTreeView.Destroy;
var
Enumer: TPointerToPointerEnumerator;
begin
Enumer := FRoot2CollapasedMap.GetEnumerator;
while Enumer.MoveNext do
FreeAndNil(TObject(Enumer.Current^.Value)); // Free the CollapsedComp TAVLTrees.
Enumer.Free;
FreeThenNil(FRoot2CollapasedMap);
FreeThenNil(FComponentList);
inherited Destroy;
end;
procedure TComponentTreeView.NodeCollapsed(Sender: TObject; Node: TTreeNode);
begin
Assert(Assigned(FCollapsedComps), 'TComponentTreeView.NodeCollapsed: FCollapsedComps=Nil.');
FCollapsedComps.Add(Node.Data);
end;
procedure TComponentTreeView.NodeExpanded(Sender: TObject; Node: TTreeNode);
begin
Assert(Assigned(FCollapsedComps), 'TComponentTreeView.NodeExpanded: FCollapsedComps=Nil.');
if not FCollapsedComps.Remove(Node.Data) then
DebugLn(['TComponentTreeView.NodeExpanded: Removing node ', TPersistent(Node.Data), ' failed.']);
end;
function TComponentTreeView.AddOrGetPersNode(AParentNode: TTreeNode;
APers: TPersistent; ACapt: String): TTreeNode;
var
xNode: TTreeNode;
begin
if not FDrawWholeTree then
begin
if AParentNode = nil then
Exit(Items.GetFirstNode); // Return existing root node.
// Search for an existing valid node.
xNode := AParentNode.GetFirstChild;
while (xNode<>nil) and (TObject(xNode.Data)<>APers) do
xNode := xNode.GetNextSibling;
if Assigned(xNode) then
Exit(xNode); // Return existing node if there is one.
end;
// Add a new node and set its properties.
Result := Items.AddChildObject(AParentNode, ACapt, APers);
if AParentNode = nil then
Result.ImageIndex := ImgIndexForm
else
Result.ImageIndex := GetImageFor(APers);
Result.SelectedIndex := Result.ImageIndex;
Result.MultiSelected := Selection.IndexOf(APers) >= 0;
end;
procedure TComponentTreeView.AddChildren(AComponent: TComponent; ARootNode: TTreeNode);
var
Walker: TComponentWalker;
begin
if csDestroying in AComponent.ComponentState then exit;
Walker := TComponentWalker.Create(Self, AComponent);
Walker.FNode := ARootNode;
try // add inline components children
TComponentAccessor(AComponent).GetChildren(@Walker.Walk, AComponent);
finally
Walker.Free;
end;
end;
function TComponentTreeView.GetRootObject: TPersistent;
// Get root object / component
begin
if PropertyEditorHook = nil then Exit(nil);
Result := PropertyEditorHook.LookupRoot;
if (Result is TComponent) and (csDestroying in TComponent(Result).ComponentState) then
Result := nil;
end;
procedure TComponentTreeView.BuildComponentNodes(AWholeTree: Boolean);
// Add all components to the tree.
// AWholeTree=True means clearing and refilling all,
// False means existing tree is used and only missing components are added.
var
RootObject: TPersistent;
RootNode: TTreeNode;
begin
OnCollapsed:=nil; // Don't handle these events while the tree builds.
OnExpanded:=nil;
BeginUpdate;
RootObject := GetRootObject;
if AWholeTree then
Items.Clear;
if RootObject <> nil then
begin
//DebugLn(['TComponentTreeView.BuildComponentNodes: RootObj=', RootObject, ', AWholeTree=', AWholeTree]);
FDrawWholeTree := AWholeTree;
// first add the lookup root
RootNode := AddOrGetPersNode(nil, RootObject, CreateNodeCaption(RootObject,''));
// add components in creation order and TControl.Parent relationship
if RootObject is TComponent then
AddChildren(TComponent(RootObject), RootNode);
if AWholeTree then
begin
// Get the right list of collapsed nodes based on LookupRoot
FCollapsedComps := TAVLTree(FRoot2CollapasedMap[RootObject]);
if FCollapsedComps = nil then
begin
FCollapsedComps := TAVLTree.Create;
FRoot2CollapasedMap[RootObject] := FCollapsedComps;
end;
end;
RestoreExpand(RootNode); // then restore the Expanded/Collapsed state.
MakeSelectionVisible;
end;
EndUpdate;
OnCollapsed:=@NodeCollapsed;
OnExpanded:=@NodeExpanded;
end;
procedure TComponentTreeView.RestoreExpand(ANode: TTreeNode);
// Restore Expanded/Collapsed state based on user's choice from last time.
begin
ANode.Expanded := FCollapsedComps.Find(ANode.Data) = Nil; // Nil means a user
ANode := ANode.GetFirstChild; // did not collapse the node last time.
while ANode<>nil do begin
RestoreExpand(ANode); // Recursive call.
ANode := ANode.GetNextSibling;
end;
end;
procedure TComponentTreeView.ChangeNode(ANode: TTreeNode);
// Change ZOrder of the given node or delete it.
var
Neighbor: TTreeNode;
begin
case FZOrderDelCommand of
zoToFront: begin // Front means the last sibling.
Neighbor := ANode.GetLastSibling;
if Assigned(Neighbor) then
ANode.MoveTo(Neighbor, naInsertBehind);
end;
zoToBack: begin // Back means the first sibling.
Neighbor := ANode.GetFirstSibling;
if Assigned(Neighbor) then
ANode.MoveTo(Neighbor, naInsert);
end;
zoForward: begin // Towards the end.
Neighbor := ANode.GetNextSibling;
if Assigned(Neighbor) then
ANode.MoveTo(Neighbor, naInsertBehind);
end;
zoBackward: begin // Towards the beginning.
Neighbor := ANode.GetPrevSibling;
if Assigned(Neighbor) then
ANode.MoveTo(Neighbor, naInsert);
end;
zoDelete: ANode.Delete; // Delete the node
end;
end;
function TComponentTreeView.IterateTree(ANode: TTreeNode; APers: TPersistent): TTreeNode;
// Returns the node that was changed.
begin
Result := Nil;
if TObject(ANode.Data)=APers then
begin
ChangeNode(ANode);
Exit(ANode); // Found and changed.
end;
// Iterate subnodes.
ANode := ANode.GetFirstChild;
while Assigned(ANode) and (Result=Nil) do
begin
Result := IterateTree(ANode, APers); // Recursive call.
ANode := ANode.GetNextSibling;
end;
end;
function TComponentTreeView.FindAndChange(APers: TPersistent;
AZOrderDel: TZOrderDelete): Boolean;
// APers is Component to be moved or deleted based on AZOrderDel value.
var
ChangedNode: TTreeNode;
begin
// Search for a node to change.
Assert(Assigned(APers), 'TComponentTreeView.FindAndChangeItem: APers=Nil.');
Assert(Items.GetFirstNode.GetNextSibling=Nil,
'TComponentTreeView.FindAndChange: Top node has siblings.');
FZOrderDelCommand := AZOrderDel;
ChangedNode := IterateTree(Items.GetFirstNode, APers);
Result := Assigned(ChangedNode);
end;
procedure TComponentTreeView.ChangeCompZOrder(APersistent: TPersistent;
AZOrder: TZOrderDelete);
begin
if not FindAndChange(APersistent, AZOrder) then
DebugLn(['TComponentTreeView.ChangeCompZOrder failed.']);
end;
procedure TComponentTreeView.DeleteComponentNode(APersistent: TPersistent);
begin
if FPreviousDeleted = APersistent then
FPreviousDeleted := Nil // Don't try to delete the same component twice.
else begin
FPreviousDeleted := APersistent;
//FindAndChange(APersistent, zoDelete); // Does not work with CollectionItems.
// Now rebuild the tree. ToDo: use TCollectionObserver, IFPObserver interface.
BuildComponentNodes(True);
end;
end;
procedure TComponentTreeView.UpdateCompNode(ANode: TTreeNode);
var
APersistent: TPersistent;
begin
if ANode = nil then Exit;
APersistent := TPersistent(ANode.Data);
if APersistent is TComponent then
ANode.Text := ComponentCaption(TComponent(APersistent))
else if APersistent is TCollectionItem then
ANode.Text := CollectionItemCaption(TCollectionItem(APersistent));
// Note: Collection name does not change, don't update.
UpdateCompNode(ANode.GetFirstChild); // Recursive call.
UpdateCompNode(ANode.GetNextSibling);
end;
procedure TComponentTreeView.UpdateComponentNodesValues;
// Could be optimised by adding a PropName parameter and searching a node by name.
begin
BeginUpdate;
UpdateCompNode(Items.GetFirstNode);
EndUpdate;
end;
procedure TComponentTreeView.UpdateSelNode(ANode: TTreeNode);
var
APersistent: TPersistent;
begin
if ANode = nil then Exit;
APersistent := TPersistent(ANode.Data);
ANode.MultiSelected := Selection.IndexOf(APersistent) >= 0;
UpdateSelNode(ANode.GetFirstChild); // Recursive call.
UpdateSelNode(ANode.GetNextSibling);
end;
procedure TComponentTreeView.UpdateSelected;
begin
BeginUpdate;
Selected := Nil;
UpdateSelNode(Items.GetFirstNode);
EndUpdate;
end;
end.