lazarus/designer/componenteditors.pas
2002-02-09 02:30:28 +00:00

514 lines
17 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:
This units defines the component editors used by the designer.
A Component Editor is a plugin used by the designer to add special
functions for component classes.
For more information see the big comment part below.
ToDo:
}
unit ComponentEditors;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, TypInfo, Forms, Controls, PropEdits, Menus;
{ TComponentEditorDesigner }
type
TComponentEditorDesigner = class(TIDesigner)
protected
function GetPropertyEditorHook: TPropertyEditorHook; virtual; abstract;
public
property PropertyEditorHook: TPropertyEditorHook read GetPropertyEditorHook;
end;
{ Component Editor Types }
type
{ TComponentEditor
A component editor is created for each component that is selected in the
form designer based on the component's type (see GetComponentEditor and
RegisterComponentEditor). When the component is double-clicked the Edit
method is called. When the context menu for the component is invoked the
GetVerbCount and GetVerb methods are called to build the menu. If one
of the verbs are selected, ExecuteVerb is called. Paste is called whenever
the component is pasted to the clipboard. You only need to create a
component editor if you wish to add verbs to the context menu, change
the default double-click behavior, or paste an additional clipboard format.
The default component editor (TDefaultEditor) implements Edit to searches the
properties of the component and generates (or navigates to) the OnCreate,
OnChanged, or OnClick event (whichever it finds first). Whenever the
component editor modifies the component, it *must* call Designer.Modified to
inform the designer that the form has been modified. (Or else the user can not
save the changes).
Edit
Called when the user double-clicks the component. The component editor can
bring up a dialog in response to this method, for example, or some kind
of design expert. If GetVerbCount is greater than zero, edit will execute
the first verb in the list (ExecuteVerb(0)).
ExecuteVerb(Index)
The Index'ed verb was selected by the use off the context menu. The
meaning of this is determined by component editor.
GetVerb
The component editor should return a string that will be displayed in the
context menu. It is the responsibility of the component editor to place
the & character and the '...' characters as appropriate.
GetVerbCount
The number of valid indices to GetVerb and Execute verb. The index is
assumed to be zero based (i.e. 0..GetVerbCount - 1).
PrepareItem
While constructing the context menu PrepareItem will be called for
each verb. It will be passed the menu item that will be used to represent
the verb. The component editor can customize the menu item as it sees fit,
including adding subitems. If you don't want that particular menu item
to be shown, don't free it, simply set its Visible property to False.
Copy
Called when the component is being copied to the clipboard. The
component's filed image is already on the clipboard. This gives the
component editor a chance to paste a different type of format which is
ignored by the designer but might be recognized by another application.
IsInInlined
Determines whether Component is in the Designer which owns it.
Essentially, Components should not be able to be added to a Frame
instance (collections are fine though) so this function checks to
determine whether the currently selected component is within a Frame
instance or not.
GetComponent
Returns the edited component.
GetDesigner
Returns the current Designer for the form owning the component.
}
{ TComponentEditor
All component editors are assumed derived from TBaseComponentEditor.
Create(AComponent, ADesigner)
Called to create the component editor. AComponent is the component to
be edited by the editor. ADesigner is an interface to the designer to
find controls and create methods (this is not used often). If a component
editor modifies the component in any way it *must* call
ADesigner.Modified. }
TBaseComponentEditor = class
protected
public
constructor Create(AComponent: TComponent;
ADesigner: TComponentEditorDesigner); virtual;
procedure Edit; virtual; abstract;
procedure ExecuteVerb(Index: Integer); virtual; abstract;
function GetVerb(Index: Integer): string; virtual; abstract;
function GetVerbCount: Integer; virtual; abstract;
procedure PrepareItem(Index: Integer; const AItem: TMenuItem); virtual; abstract;
procedure Copy; virtual; abstract;
function IsInInlined: Boolean; virtual; abstract;
function GetComponent: TComponent; virtual; abstract;
function GetDesigner: TComponentEditorDesigner; virtual; abstract;
end;
TComponentEditorClass = class of TBaseComponentEditor;
{ TComponentEditor
This class provides a default implementation for the IComponentEditor
interface. There is no assumption by the designer that you use this class
only that your class derive from TBaseComponentEditor and implement
IComponentEditor. This class is provided to help you implement a class
that meets those requirements. }
TComponentEditor = class(TBaseComponentEditor)
private
FComponent: TComponent;
FDesigner: TComponentEditorDesigner;
public
constructor Create(AComponent: TComponent;
ADesigner: TComponentEditorDesigner); override;
procedure Edit; override;
procedure ExecuteVerb(Index: Integer); override;
function GetComponent: TComponent; override;
function GetDesigner: TComponentEditorDesigner; override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
function IsInInlined: Boolean; override;
procedure Copy; virtual; override;
procedure PrepareItem(Index: Integer; const AItem: TMenuItem); override;
property Component: TComponent read FComponent;
property Designer: TComponentEditorDesigner read GetDesigner;
end;
{ TDefaultComponentEditor
An editor that provides default behavior for the double-click that will
iterate through the properties looking for the most appropriate method
property to edit }
TDefaultComponentEditor = class(TComponentEditor)
private
FFirst: TPropertyEditor;
FBest: TPropertyEditor;
FContinue: Boolean;
procedure CheckEdit(Prop: TPropertyEditor);
protected
procedure EditProperty(const Prop: TPropertyEditor;
var Continue: Boolean); virtual;
public
procedure Edit; override;
end;
{ Register a component editor to be created when a component derived from
ComponentClass is the only selection in the designer }
type
TRegisterComponentEditorProc =
procedure (ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);
var
RegisterComponentEditorProc: TRegisterComponentEditorProc;
procedure RegisterComponentEditor(ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);
function GetComponentEditor(Component: TComponent;
const Designer: TComponentEditorDesigner): TBaseComponentEditor;
type
TPropertyEditorFilterFunc =
function(const ATestEditor: TPropertyEditor): Boolean of object;
procedure GetComponentProperties(Components: TComponentSelectionList;
Filter: TTypeKinds; Hook: TPropertyEditorHook; Proc: TGetPropEditProc;
EditorFilterFunc: TPropertyEditorFilterFunc);
implementation
procedure GetComponentProperties(Components: TComponentSelectionList;
Filter: TTypeKinds; Hook: TPropertyEditorHook; Proc: TGetPropEditProc;
EditorFilterFunc: TPropertyEditorFilterFunc);
var
I, J, CompCount: Integer;
CompType: TClass;
Candidates: TPropInfoList;
PropLists: TList;
PropEditor: TPropertyEditor;
EdClass: TPropertyEditorClass;
PropInfo: PPropInfo;
AddEditor: Boolean;
Obj: TComponent;
begin
if (Components = nil) or (Components.Count = 0) then Exit;
CompCount := Components.Count;
Obj := Components[0];
CompType := Components[0].ClassType;
// Create a property candidate list of all properties that can be found in
// every component in the list and in the Filter
Candidates := TPropInfoList.Create(Components[0], Filter);
try
// check each property candidate
for I := Candidates.Count - 1 downto 0 do
begin
PropInfo := Candidates[I];
// check if property is readable
if (PropInfo^.GetProc=nil)
or (not GShowReadOnlyProps and ((PropInfo^.PropType^.Kind <> tkClass)
and (PropInfo^.SetProc = nil)))
then begin
Candidates.Delete(I);
continue;
end;
EdClass := GetEditorClass(PropInfo, Obj);
if EdClass = nil then
Candidates.Delete(I)
else
begin
// create a test property editor for the property
PropEditor := EdClass.Create(Hook,Components,1);
PropEditor.SetPropEntry(0, Components[0], PropInfo);
PropEditor.Initialize;
with PropInfo^ do
// check for multiselection, ValueAvailable and customfilter
if ((CompCount > 1)
and not (paMultiSelect in PropEditor.GetAttributes))
or not PropEditor.ValueAvailable
or (Assigned(EditorFilterFunc) and not EditorFilterFunc(PropEditor))
then
Candidates.Delete(I);
end;
end;
PropLists := TList.Create;
try
PropLists.Capacity := CompCount;
// Create a property info list for each component in the selection
for I := 0 to CompCount - 1 do
PropLists.Add(TPropInfoList.Create(Components[I], Filter));
// Eliminate each property in Candidates that is not in all property lists
for I := 0 to CompCount - 1 do
Candidates.Intersect(TPropInfoList(PropLists[I]));
// Eliminate each property in the property list that are not in Candidates
for I := 0 to CompCount - 1 do
TPropInfoList(PropLists[I]).Intersect(Candidates);
// PropList now has a matrix of PropInfo's.
// -> create property editors for each property
// with given each the array of PropInfos
for I := 0 to Candidates.Count - 1 do
begin
EdClass := GetEditorClass(Candidates[I], Obj);
if EdClass = nil then Continue;
PropEditor := EdClass.Create(Hook, Components, CompCount);
AddEditor := True;
for J := 0 to CompCount - 1 do
begin
if (Components[J].ClassType <> CompType) and
(GetEditorClass(TPropInfoList(PropLists[J])[I],
Components[J]) <> EdClass) then
begin
AddEditor := False;
Break;
end;
PropEditor.SetPropEntry(J, Components[J],
TPropInfoList(PropLists[J])[I]);
end;
if AddEditor then
begin
PropEditor.Initialize;
if PropEditor.ValueAvailable then Proc(PropEditor);
end;
end;
finally
for I := 0 to PropLists.Count - 1 do TPropInfoList(PropLists[I]).Free;
PropLists.Free;
end;
finally
Candidates.Free;
end;
end;
{ RegisterComponentEditor }
type
PComponentClassRec = ^TComponentClassRec;
TComponentClassRec = record
Group: Integer;
ComponentClass: TComponentClass;
EditorClass: TComponentEditorClass;
end;
const
ComponentClassList: TList = nil;
procedure DefaultRegisterComponentEditorProc(ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);
var
P: PComponentClassRec;
begin
if ComponentClassList = nil then
ComponentClassList := TList.Create;
New(P);
P^.Group := -1;//CurrentGroup;
P^.ComponentClass := ComponentClass;
P^.EditorClass := ComponentEditor;
ComponentClassList.Insert(0, P);
end;
procedure RegisterComponentEditor(ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);
begin
if Assigned(RegisterComponentEditorProc) then
RegisterComponentEditorProc(ComponentClass, ComponentEditor);
end;
function GetComponentEditor(Component: TComponent;
const Designer: TComponentEditorDesigner): TBaseComponentEditor;
var
P: PComponentClassRec;
I: Integer;
ComponentClass: TComponentClass;
EditorClass: TComponentEditorClass;
begin
ComponentClass := TComponentClass(TPersistent);
EditorClass := TDefaultComponentEditor;
if ComponentClassList <> nil then
for I := 0 to ComponentClassList.Count-1 do
begin
P := PComponentClassRec(ComponentClassList[I]);
if (Component is P^.ComponentClass) and
(P^.ComponentClass <> ComponentClass) and
(P^.ComponentClass.InheritsFrom(ComponentClass)) then
begin
EditorClass := P^.EditorClass;
ComponentClass := P^.ComponentClass;
end;
end;
Result := EditorClass.Create(Component, Designer);
end;
{ Component Editors -----------------------------------------------------------}
{ TBaseComponentEditor }
constructor TBaseComponentEditor.Create(AComponent: TComponent;
ADesigner: TComponentEditorDesigner);
begin
inherited Create;
end;
{ TComponentEditor }
constructor TComponentEditor.Create(AComponent: TComponent;
ADesigner: TComponentEditorDesigner);
begin
inherited Create(AComponent, ADesigner);
FComponent := AComponent;
FDesigner := ADesigner;
end;
procedure TComponentEditor.Edit;
begin
if GetVerbCount > 0 then ExecuteVerb(0);
end;
function TComponentEditor.GetComponent: TComponent;
begin
Result := FComponent;
end;
function TComponentEditor.GetDesigner: TComponentEditorDesigner;
begin
Result := FDesigner;
end;
function TComponentEditor.GetVerbCount: Integer;
begin
// Intended for descendents to implement
Result := 0;
end;
function TComponentEditor.GetVerb(Index: Integer): string;
begin
// Intended for descendents to implement
Result:='';
end;
procedure TComponentEditor.ExecuteVerb(Index: Integer);
begin
// Intended for descendents to implement
end;
procedure TComponentEditor.Copy;
begin
// Intended for descendents to implement
end;
function TComponentEditor.IsInInlined: Boolean;
begin
Result := csInline in Component.Owner.ComponentState;
end;
procedure TComponentEditor.PrepareItem(Index: Integer;
const AItem: TMenuItem);
begin
// Intended for descendents to implement
end;
{ TDefaultComponentEditor }
procedure TDefaultComponentEditor.CheckEdit(Prop: TPropertyEditor);
begin
if FContinue then
EditProperty(Prop, FContinue);
end;
procedure TDefaultComponentEditor.EditProperty(const Prop: TPropertyEditor;
var Continue: Boolean);
var
PropName: string;
BestName: string;
procedure ReplaceBest;
begin
FBest := Prop;
if FFirst = FBest then FFirst := nil;
end;
begin
if not Assigned(FFirst)
and (Prop is TMethodPropertyEditor) then
FFirst := Prop;
PropName := Prop.GetName;
BestName := '';
if Assigned(FBest) then BestName := FBest.GetName;
if CompareText(PropName, 'ONCREATE') = 0 then
ReplaceBest
else if CompareText(BestName, 'ONCREATE') <> 0 then
if CompareText(PropName, 'ONCHANGE') = 0 then
ReplaceBest
else if CompareText(BestName, 'ONCHANGE') <> 0 then
if CompareText(PropName, 'ONCLICK') = 0 then
ReplaceBest;
end;
procedure TDefaultComponentEditor.Edit;
var
Components: TComponentSelectionList;
PropertyEditorHook: TPropertyEditorHook;
begin
PropertyEditorHook:=Designer.PropertyEditorHook;
if PropertyEditorHook=nil then exit;
Components := TComponentSelectionList.Create;
FContinue := True;
Components.Add(Component);
FFirst := nil;
FBest := nil;
try
GetComponentProperties(Components, tkAny, PropertyEditorHook,
@CheckEdit, nil);
if FContinue then
if Assigned(FBest) then
FBest.Edit
else if Assigned(FFirst) then
FFirst.Edit;
finally
FFirst := nil;
FBest := nil;
end;
end;
//------------------------------------------------------------------------------
initialization
RegisterComponentEditorProc:=@DefaultRegisterComponentEditorProc;
end.