mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-29 09:22:44 +02:00
514 lines
17 KiB
ObjectPascal
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.
|
|
|