mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-03 01:04:38 +02:00
345 lines
11 KiB
ObjectPascal
345 lines
11 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
* *
|
|
* See the file COPYING.modifiedLGPL, included in this distribution, *
|
|
* for details about the copyright. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* *
|
|
*****************************************************************************
|
|
|
|
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
|
|
- ToDo: editing the creation order
|
|
- ToDo: editing the TControl.Parent hierachy
|
|
For an usage example, see the object inspector.
|
|
|
|
ToDo:
|
|
- icons
|
|
- drag&drop: change parent and position
|
|
- TComponent childs (e.g. TMenu and TMenuItems)
|
|
}
|
|
unit ComponentTreeView;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc, AvgLvlTree, Controls, ComCtrls, PropEdits, Menus;
|
|
|
|
type
|
|
{ TComponentTreeView }
|
|
|
|
TComponentTreeView = class(TCustomTreeView)
|
|
private
|
|
FComponentList: TBackupComponentList;
|
|
FPropertyEditorHook: TPropertyEditorHook;
|
|
function GetSelection: TPersistentSelectionList;
|
|
procedure SetPropertyEditorHook(const AValue: TPropertyEditorHook);
|
|
procedure SetSelection(const NewSelection: TPersistentSelectionList);
|
|
protected
|
|
procedure DoSelectionChanged; override;
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure RebuildComponentNodes; virtual;
|
|
procedure UpdateComponentNodesValues; virtual;
|
|
function CreateNodeCaption(APersistent: TPersistent): string; virtual;
|
|
public
|
|
property Selection: TPersistentSelectionList read GetSelection
|
|
write SetSelection;
|
|
property PropertyEditorHook: TPropertyEditorHook
|
|
read FPropertyEditorHook write SetPropertyEditorHook;
|
|
property OnSelectionChanged;
|
|
end;
|
|
|
|
implementation
|
|
|
|
type
|
|
TComponentCandidate = class
|
|
public
|
|
APersistent: TPersistent;
|
|
Parent: TComponent;
|
|
Added: boolean;
|
|
end;
|
|
|
|
function CompareComponentCandidates(
|
|
Candidate1, Candidate2: TComponentCandidate): integer;
|
|
begin
|
|
Result:=ComparePointers(Candidate1.APersistent,Candidate2.APersistent);
|
|
end;
|
|
|
|
function ComparePersistentWithComponentCandidate(
|
|
APersistent: TPersistent; Candidate: TComponentCandidate): integer;
|
|
begin
|
|
Result:=ComparePointers(APersistent,Candidate.APersistent);
|
|
end;
|
|
|
|
{ TComponentTreeView }
|
|
|
|
procedure TComponentTreeView.SetSelection(
|
|
const NewSelection: TPersistentSelectionList);
|
|
begin
|
|
if (PropertyEditorHook=nil) then begin
|
|
if (FComponentList.LookupRoot=nil) then
|
|
exit;
|
|
FComponentList.Clear;
|
|
end else begin
|
|
if FComponentList.IsEqual(PropertyEditorHook.LookupRoot,NewSelection) then
|
|
begin
|
|
// nodes ok, but maybe node values need update
|
|
UpdateComponentNodesValues;
|
|
exit;
|
|
end;
|
|
end;
|
|
FComponentList.LookupRoot:=PropertyEditorHook.LookupRoot;
|
|
FComponentList.Selection.Assign(NewSelection);
|
|
RebuildComponentNodes;
|
|
end;
|
|
|
|
procedure TComponentTreeView.DoSelectionChanged;
|
|
var
|
|
ANode: TTreeNode;
|
|
AComponent: TComponent;
|
|
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
|
|
AComponent:=TComponent(ANode.Data);
|
|
if AComponent=nil then
|
|
RaiseGDBException('TComponentTreeView.DoSelectionChanged ANode.Data=nil');
|
|
if (AComponent=PropertyEditorHook.LookupRoot)
|
|
or (AComponent.Owner=PropertyEditorHook.LookupRoot)
|
|
then
|
|
NewSelection.Add(AComponent);
|
|
ANode:=ANode.GetNextMultiSelected;
|
|
end;
|
|
end;
|
|
if NewSelection.IsEqual(FComponentList.Selection) then exit;
|
|
FComponentList.Selection.Assign(NewSelection);
|
|
inherited DoSelectionChanged;
|
|
finally
|
|
NewSelection.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TComponentTreeView.SetPropertyEditorHook(
|
|
const AValue: TPropertyEditorHook);
|
|
begin
|
|
if FPropertyEditorHook=AValue then exit;
|
|
FPropertyEditorHook:=AValue;
|
|
RebuildComponentNodes;
|
|
end;
|
|
|
|
function TComponentTreeView.GetSelection: TPersistentSelectionList;
|
|
begin
|
|
Result:=FComponentList.Selection;
|
|
end;
|
|
|
|
constructor TComponentTreeView.Create(TheOwner: TComponent);
|
|
begin
|
|
inherited Create(TheOwner);
|
|
FComponentList:=TBackupComponentList.Create;
|
|
Options:=Options+[tvoAllowMultiselect,tvoAutoItemHeight,tvoKeepCollapsedNodes];
|
|
end;
|
|
|
|
destructor TComponentTreeView.Destroy;
|
|
begin
|
|
FreeThenNil(FComponentList);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TComponentTreeView.RebuildComponentNodes;
|
|
var
|
|
Candidates: TAvgLvlTree;
|
|
RootComponent: TComponent;
|
|
|
|
procedure AddChildControls(AControl: TWinControl; ANode: TTreeNode);
|
|
var
|
|
i: Integer;
|
|
CurControl: TControl;
|
|
NewNode: TTreeNode;
|
|
Candidate: TComponentCandidate;
|
|
AVLNode: TAvgLvlTreeNode;
|
|
begin
|
|
if AControl=nil then exit;
|
|
for i:=0 to AControl.ControlCount-1 do begin
|
|
CurControl:=AControl.Controls[i];
|
|
if CurControl.Owner<>AControl.Owner then continue;
|
|
AVLNode:=Candidates.FindKey(CurControl,
|
|
@ComparePersistentWithComponentCandidate);
|
|
Candidate:=TComponentCandidate(AVLNode.Data);
|
|
if Candidate.Added then continue;
|
|
Candidate.Added:=true;
|
|
NewNode:=Items.AddChild(ANode,CreateNodeCaption(CurControl));
|
|
NewNode.Data:=CurControl;
|
|
NewNode.ImageIndex:=-1;
|
|
NewNode.MultiSelected:=Selection.IndexOf(CurControl)>=0;
|
|
if CurControl is TWinControl then
|
|
AddChildControls(TWinControl(CurControl),NewNode);
|
|
end;
|
|
ANode.Expanded:=true;
|
|
end;
|
|
|
|
procedure AddMenuItemChilds(AMenuItem: TMenuItem; ANode: TTreeNode);
|
|
var
|
|
i: Integer;
|
|
CurMenuItem: TMenuItem;
|
|
NewNode: TTreeNode;
|
|
Candidate: TComponentCandidate;
|
|
AVLNode: TAvgLvlTreeNode;
|
|
begin
|
|
if AMenuItem=nil then exit;
|
|
for i:=0 to AMenuItem.Count-1 do begin
|
|
CurMenuItem:=AMenuItem.Items[i];
|
|
if CurMenuItem.Owner<>RootComponent then continue;
|
|
AVLNode:=Candidates.FindKey(CurMenuItem,
|
|
@ComparePersistentWithComponentCandidate);
|
|
Candidate:=TComponentCandidate(AVLNode.Data);
|
|
if Candidate.Added then continue;
|
|
Candidate.Added:=true;
|
|
NewNode:=Items.AddChild(ANode,CreateNodeCaption(CurMenuItem));
|
|
NewNode.Data:=CurMenuItem;
|
|
NewNode.ImageIndex:=-1;
|
|
NewNode.MultiSelected:=Selection.IndexOf(CurMenuItem)>=0;
|
|
AddMenuItemChilds(CurMenuItem,NewNode);
|
|
end;
|
|
ANode.Expanded:=true;
|
|
end;
|
|
|
|
var
|
|
OldExpanded: TTreeNodeExpandedState;
|
|
NewNode: TTreeNode;
|
|
RootObject: TPersistent;
|
|
i: Integer;
|
|
AComponent: TComponent;
|
|
RootNode: TTreeNode;
|
|
AControl: TControl;
|
|
AVLNode: TAvgLvlTreeNode;
|
|
AMenu: TMenu;
|
|
Candidate: TComponentCandidate;
|
|
begin
|
|
BeginUpdate;
|
|
// save old expanded state and clear
|
|
OldExpanded:=TTreeNodeExpandedState.Create(Self);
|
|
Items.Clear;
|
|
|
|
RootObject:=PropertyEditorHook.LookupRoot;
|
|
if RootObject<>nil then begin
|
|
Candidates:=TAvgLvlTree.Create(@CompareComponentCandidates);
|
|
try
|
|
// first add the lookup root
|
|
RootNode:=Items.Add(nil,CreateNodeCaption(RootObject));
|
|
RootNode.Data:=RootObject;
|
|
RootNode.ImageIndex:=-1;
|
|
RootNode.MultiSelected:=Selection.IndexOf(RootObject)>=0;
|
|
|
|
// create candidate nodes for every child
|
|
Candidate:=TComponentCandidate.Create;
|
|
Candidate.APersistent:=RootObject;
|
|
Candidate.Added:=true;
|
|
Candidates.Add(Candidate);
|
|
|
|
if RootObject is TComponent then begin
|
|
RootComponent:=TComponent(RootObject);
|
|
for i:=0 to RootComponent.ComponentCount-1 do begin
|
|
AComponent:=RootComponent.Components[i];
|
|
Candidate:=TComponentCandidate.Create;
|
|
Candidate.APersistent:=AComponent;
|
|
if Candidates.Find(Candidate)<>nil then begin
|
|
writeln('WARNING: TComponentTreeView.RebuildComponentNodes doppelganger found ',AComponent.Name);
|
|
Candidate.Free;
|
|
end else
|
|
Candidates.Add(Candidate);
|
|
end;
|
|
end;
|
|
|
|
// add components in creation order and TControl.Parent relationship
|
|
if RootObject is TComponent then begin
|
|
RootComponent:=TComponent(RootObject);
|
|
for i:=0 to RootComponent.ComponentCount-1 do begin
|
|
AComponent:=RootComponent.Components[i];
|
|
AVLNode:=Candidates.FindKey(AComponent,
|
|
@ComparePersistentWithComponentCandidate);
|
|
Candidate:=TComponentCandidate(AVLNode.Data);
|
|
if Candidate.Added then
|
|
continue;
|
|
if AComponent is TControl then begin
|
|
AControl:=TControl(AComponent);
|
|
if (AControl.Parent<>nil) and (AControl.Parent<>RootComponent) then
|
|
// grand child controls will be added recursively, not here
|
|
continue;
|
|
end
|
|
else if (AComponent is TMenuItem) then begin
|
|
AMenu:=TMenuItem(AComponent).GetParentMenu;
|
|
if (AMenu<>nil) and (AMenu.Owner=RootComponent) then
|
|
// child menuitems will be added recursively, not here
|
|
continue;
|
|
end;
|
|
Candidate.Added:=true;
|
|
NewNode:=Items.AddChild(RootNode,CreateNodeCaption(AComponent));
|
|
NewNode.Data:=AComponent;
|
|
NewNode.ImageIndex:=-1;
|
|
NewNode.MultiSelected:=Selection.IndexOf(AComponent)>=0;
|
|
if AComponent is TWinControl then
|
|
AddChildControls(TWinControl(AComponent),NewNode)
|
|
else if (AComponent is TMenu) then begin
|
|
AddMenuItemChilds(TMenu(AComponent).Items,NewNode);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
Candidates.FreeAndClear;
|
|
Candidates.Free;
|
|
end;
|
|
|
|
RootNode.Expand(true);
|
|
end;
|
|
|
|
// restore old expanded state
|
|
OldExpanded.Apply(Self);
|
|
OldExpanded.Free;
|
|
MakeSelectionVisible;
|
|
EndUpdate;
|
|
end;
|
|
|
|
procedure TComponentTreeView.UpdateComponentNodesValues;
|
|
|
|
procedure UpdateComponentNode(ANode: TTreeNode);
|
|
var
|
|
AComponent: TComponent;
|
|
begin
|
|
if ANode=nil then exit;
|
|
AComponent:=TComponent(ANode.Data);
|
|
ANode.Text:=CreateNodeCaption(AComponent);
|
|
UpdateComponentNode(ANode.GetFirstChild);
|
|
UpdateComponentNode(ANode.GetNextSibling);
|
|
end;
|
|
|
|
begin
|
|
UpdateComponentNode(Items.GetFirstNode);
|
|
end;
|
|
|
|
function TComponentTreeView.CreateNodeCaption(APersistent: TPersistent): string;
|
|
begin
|
|
Result:=APersistent.ClassName;
|
|
if APersistent is TComponent then
|
|
Result:=TComponent(APersistent).Name+': '+Result;
|
|
end;
|
|
|
|
end.
|
|
|