{ ***************************************************************************** * * * 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.