lazarus/ideintf/componenttreeview.pas
2004-05-21 18:12:17 +00:00

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.