Sign in Join
Wiki copytemporary

Extending the IDE/es: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Extending the IDE/es

OldNewLine
1+# Extendiendo el IDE
2+
3+## Visión general
4+
5+El IDE de Lazarus soporta varios tipos de plugins:
6+
7+Componentes
8+Estos son los elementos que se encuentrar en la Paleta de Componentes. Por ejemplo, TButton (que se encuentra en la solapa Standard) que se utiliza para crear Buttons.
9+
10+Component Editors
11+Cuando haces doble click sobre un componente en el Diseñador, se invoka un editor de componente. El editor de componente puede además añadir un elemento extra al menú emergente (accedido cuando haces click en el pulsador derecho en un componente dentro del Diseñador).
12+
13+Property Editors
14+Cada fila en la sección de la rejilla (grid) del Inspector de Objetos utiliza un editor de propiedades que permite establecer o alterar el valor de su propiedad.
15+
16+Experts
17+Experts abarca todos los demás tipos de plugin. Pueden registrar nuevos elementos de menú, registrar atajos (short cuts), o extender otras características del IDE.
18+
19+Hay dos maneras posibles de añadir tus propios plugins al IDE de Lazarus:
20+
21+1. Escribir un paquete, instalarlo y registrar tu plugin(s) en el procedimiento 'Register' procedure de una unit.
22+2. Extender el cógigo de Lazarus, y enviar tu svn diff a la lista de correo de Lazarus.
23+
24+## Paquetes y Dependencias MakeFile
25+
26+Los siguientes pasos son necesarios para añadir nuevas dependencias de paquete.
27+
28+- Crear/Actualizar el MakeFile para el paquete vía Editor de Paquete / Más ... / Crear Makefile.
29+- Corregir todas las dependencias. Ver tools/updatemakefiles.lpi
30+
31+# Elementos del Menú
32+
33+Puedes añadir nuevos elementos al menú, ocultar o mover los existentes y añadir sub menús y secciones. Puedes registrar TMainMenu/TPopupMenu para tus propios formularios de forma que otros paquetes puedan extender más tu formulario. La unidad **MenuIntf** del paquete IDEIntf contiene todos todos los registros para menús y algunos elementos menú estandar del propio IDE.
34+
35+## Añadiendo un elemento al menú
36+
37+Un simple elemento de menú tal como 'View Object Inspector' se llama **TIDEMenuCommand**. Puedes crear uno con **RegisterIDEMenuCommand**, que tiene dos fomas con un montón de parámetros:
38+
39+``` pascal
40+
41+function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
42+ const Name, Caption: string;
43+ const OnClickMethod: TNotifyEvent = nil;
44+ const OnClickProc: TNotifyProcedure = nil;
45+ const Command: TIDECommand = nil;
46+ const ResourceName: String = ''
47+ ): TIDEMenuCommand; overload;
48+function RegisterIDEMenuCommand(const Path, Name, Caption: string;
49+ const OnClickMethod: TNotifyEvent = nil;
50+ const OnClickProc: TNotifyProcedure = nil;
51+ const Command: TIDECommand = nil;
52+ const ResourceName: String = ''
53+ ): TIDEMenuCommand; overload;
54+```
55+
56+La diferencia entre los dos consiste solamente en como se especifica la sección padre del menú. Puedes dar la sección del menú directamente o bien indirectamente a través de su trayecto. Algunas secciones estandar las puedes encontrar en la unidad *MenuIntf* Por ejemplo, la sección **mnuTools**, la cual es el menú *Tools* de la barra principal del IDE. Contiene una secciónon **itmSecondaryTools**, que es la sección recomendada para utilidades de terceros.
57+
58+Lo siguiente registra un nuevo mandato de menú con el nombre 'MyTool', el caption 'Arranca mi utilidad' y cuando es clickeada ejecuta el procedimiento *StartMyTool*:
59+
60+``` pascal
61+procedure StartMyTool;
62+begin
63+ ...ejecutada cuando el elemento del menú es clickeado...
64+end;
65+
66+procedure Register;
67+begin
68+ RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Aranca mi utilidad',nil,@StartMyTool);
69+end;
70+```
71+
72+If you want to call a method instead of a procedure use the **OnClickMethod** parameter.
73+
74+Este elemento de menú no tiene un atajo. En caso de necesitar uno debes crear un **TIDECommand** y pasarlo en el parámetro **Command**. Por ejemplo:
75+
76+``` pascal
77+uses ... IDECommands, MenuIntf;
78+...
79+var
80+ Key: TIDEShortCut;
81+ Cat: TIDECommandCategory;
82+ CmdMyTool: TIDECommand;
83+begin
84+ // register IDE shortcut and menu item
85+ Key := IDEShortCut(VK_UNKNOWN,[],VK_UNKNOWN,[]);
86+ Cat:=IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
87+ CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
88+ RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
89+end;
90+```
91+
92+# Atajos (Shortcuts)
93+
94+Todos los atajos (teclas que invocan alguna acción) se encuentran registradas en el IDECommandList. Este es un ejemplo sobre como encontrar todos los mandatos que reaccionan a la pulsación de Ctrl-D o la combinación de teclas pulsadas que comiencen por Ctrl-D:
95+
96+``` pascal
97+uses
98+ Classes, SysUtils, LCLProc, IDECommands;
99+
100+procedure ListCtrlD;
101+// Lista todos los idecommands con el atajo Ctrl-D
102+var
103+ i: integer;
104+ Cmd: TIDECommand;
105+ Commands: TFPList;
106+begin
107+ Commands:=IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D,[],VK_UNKNOWN,[])); // VK = Virtual Key
108+ try
109+ for i:=0 to Commands.Count-1 do begin
110+ Cmd:=TIDECommand(Commands[i]);
111+ writeln('Cmd: ',Cmd.Name,
112+ ' A=',dbgs(Cmd.ShortcutA.Shift1),'-',Cmd.ShortcutA.Key1,
113+ ',',dbgs(Cmd.ShortcutA.Shift2),'-',Cmd.ShortcutA.Key2,
114+ ' B=',dbgs(Cmd.ShortcutB.Shift1),'-',Cmd.ShortcutB.Key1,
115+ ',',dbgs(Cmd.ShortcutB.Shift2),'-',Cmd.ShortcutB.Key2
116+ );
117+ end;
118+ finally
119+ Commands.Free;
120+ end;
121+end;
122+```
123+
124+# Config files
125+
126+## Cargar/Salvar configuraciones
127+
128+Todos los ficheros de configuración del IDE se encuentran almacenados en un directorio como ficheros xml. Los paquetes también también pueden añadir sus propios ficheros en dicho directorio. El [[pcp|primary config path]] se puede leer con:
129+
130+``` pascal
131+uses LazIDEIntf;
132+...
133+ Directory := LazarusIDE.GetPrimaryConfigPath;
134+```
135+
136+Los paquetes pueden crear su propios ficheros xml con:
137+
138+``` pascal
139+uses
140+ ..., LCLProc, BaseIDEIntf, LazConfigStorage;
141+
142+const
143+ Version = 1;
144+var
145+ Config: TConfigStorage;
146+ SomeValue: String;
147+ SomeInteger: Integer;
148+ SomeBoolean: Boolean;
149+ FileVersion: LongInt;
150+begin
151+ SomeValue:='Default';
152+ SomeInteger:=3;
153+ SomeBoolean:=true;
154+
155+ // Salva configuraciones.
156+ try
157+ Config:=GetIDEConfigStorage('mysettings.xml',false);
158+ try
159+ // Almacena el número de versión de forma que las extensiones futuras puedan manejar ficheros de configuración antiguos.
160+ Config.SetDeleteValue('Path/To/The/Version',Version,0);
161+ // Almacena la variable de cadena "SomeValue".
162+ // Si SomeValue tiene el valor por defecto entonces la entrada no se almacena,
163+ // de forma que únicamente se almacena las diferencias al valor por defecto.
164+ // Así el xml se mantiene pequeño y su valor por defecto puede cambiar en el futuro.
165+ Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
166+ Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
167+ Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
168+ // there are more overloads for SetDeleteValue, find out with Ctrl+Space
169+ finally
170+ Config.Free;
171+ end;
172+
173+ except
174+ on E: Exception do begin
175+ DebugLn(['Saving mysettings.xml failed: ',E.Message]);
176+ end;
177+ end;
178+
179+ // load settings
180+ try
181+ Config:=GetIDEConfigStorage('mysettings.xml',true);
182+ try
183+ // read the version of the config
184+ FileVersion:=Config.GetValue('Path/To/The/Version',0);
185+ // read a string variable "SomeValue". If the entry does not exist, use
186+ // the Default
187+ SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
188+ SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
189+ SomeBoolean:=Config.GetValue('Path/To/Some/Boolean',true);
190+ finally
191+ Config.Free;
192+ end;
193+ except
194+ on E: Exception do begin
195+ DebugLn(['Loading mysettings.xml failed: ',E.Message]);
196+ end;
197+ end;
198+end;
199+```
200+
201+Notes:
202+
203+- Every IDE plugin should use its own config file. Especially you must not use the standard IDE option files like [[editoroptions.xml]] or [[environmentoptions.xml]].
204+- GetIDEConfigStorage when loading checks if the file exists and if not it will copy the template from the secondary config directory.
205+- Since 0.9.31 you can use this function to load a file outside the primary config directory.
206+
207+# Components
208+
209+## Extending existing components
210+
211+Source: [1](http://lazarus.freepascal.org/index.php/topic,17733.msg98425.html#msg98425) It is straightforward to customise existing LCL components, e.g. if you want to add properties.
212+
213+Add the properties you need with appropriate setter and getter methods and storage fields.
214+
215+Then instantiate them 'by hand', giving an owner component (to handle later destruction), and set Parent and possibly Top and Left and any other defaults your component needs when initialized.
216+
217+When you are satisfied that your customised component is debugged, add a RegisterComponent() call to stick it on the IDE Component Palette and save you having to do the setup code 'by hand' (look at any component's source to see how to do this).
218+
219+Here is a simple example:
220+
221+``` pascal
222+unit Unit1;
223+
224+{$mode objfpc}{$H+}
225+
226+interface
227+
228+uses
229+ Classes, SysUtils, Forms, StdCtrls;
230+
231+type
232+ TEdX = class(TEdit)
233+ private
234+ FNewIntProp: integer;
235+ FNewStrProp: string;
236+ procedure SetNewIntProp(AValue: integer);
237+ procedure SetNewStrProp(AValue: string);
238+ public
239+ constructor Create(theComponent: TComponent); override;
240+ property NewIntProp: integer read FNewIntProp write SetNewIntProp;
241+ property NewStrProp: string read FNewStrProp write SetNewStrProp;
242+ end;
243+
244+ TForm1 = class(TForm)
245+ procedure FormCreate(Sender: TObject);
246+ private
247+ edX: TEdX;
248+ public
249+ end;
250+
251+var
252+ Form1: TForm1;
253+
254+implementation
255+
256+{ TForm1 }
257+
258+procedure TForm1.FormCreate(Sender: TObject);
259+begin
260+ edX:= TEdX.Create(Self);
261+ edX.Parent:= Self;
262+ edX.Caption:= IntToStr(edX.NewIntProp);
263+ Caption:= edX.NewStrProp;
264+end;
265+
266+{$R *.lfm}
267+
268+{ TEdX }
269+
270+procedure TEdX.SetNewIntProp(AValue: integer);
271+begin
272+ if FNewIntProp=AValue then Exit;
273+ FNewIntProp:=AValue;
274+ // add custom code here
275+end;
276+
277+procedure TEdX.SetNewStrProp(AValue: string);
278+begin
279+ if FNewStrProp=AValue then Exit;
280+ FNewStrProp:=AValue;
281+ // add custom code here
282+end;
283+
284+constructor TEdX.Create(theComponent: TComponent);
285+begin
286+ inherited Create(theComponent);
287+ FNewStrProp:= 'new string property value';
288+ FNewIntProp:= 99;
289+end;
290+
291+end.
292+```
293+
294+## Writing components
295+
296+*Main article: [[How To Write Lazarus Component]]*
297+
298+You can create new components via the package editor.
299+
300+Create or open a package, click on **add**, click on **New Component**, fill in the items: Ancestor Type = TButton, New class name = TMyButton, palette page = Misc, Unit file name = mybutton.pas (this file will be created), unit name MyButton and click **ok**.
301+
302+### Giving a new component an icon for the component palette
303+
304+Create an image file of the format .bmp, .xpm or .png with the same name as the component class. For example *tmybutton.png* and save it in the package source directory. The image can be created by any graphic program (e.g. gimp) and should be no bigger than 24x24 pixel. Then convert the image to a resource file with the lazres tool, which can be found in the lazarus/tools directory:
305+
306+``` bash
307+~/lazarus/tools/lazres mybutton.lrs tmybutton.png
308+# note: Laz trunk/1.4+? uses .res resource files instead of .lrs files:
309+~/lazarus/tools/lazres mybutton.res tmybutton.png
310+```
311+
312+This creates an pascal include file, which is used in the initialization section of mybutton.pas:
313+
314+``` pascal
315+initialization
316+ {$I mybutton.lrs}
317+ // or in newer Lazarus version - to docheck syntax
318+ {$R mybutton.res}
319+```
320+
321+Install the package.
322+
323+### Hiding a component in the component palette
324+
325+Package IDEIntf, unit componentreg:
326+
327+`IDEComponentPalette.FindComponent('TButton').Visible:=false;`
328+
329+## Writing component editors
330+
331+A component editor handles things like double clicking on a component in a designer or adding menu items when right clicking on a component. Writing a component editor is easy. See the unit componenteditors.pas of the package IDEIntf for lots of examples. For instance to show an editor when double clicking see TCheckListBoxComponentEditor.
332+
333+## Writing property editors
334+
335+Each type of property (integer, string, TFont, ...) in the Object Inspector needs a property editor. If your component has a property *Password* of type string, then you can define a property editor for your specific component class, with a property named *Password* of type string. The unit propedits.pp of the package IDEIntf contains many of the standard property editors used by the IDE itself. For example the TStringPropertyEditor is the default property editor for all strings, while TComponentNamePropertyEditor is more specific and handles only TComponent.Name. See the unit for plenty of examples.
336+
337+### Hiding a property in the Object Inspector
338+
339+There is a special property editor for hiding: <Code>
340+
341+` RegisterPropertyEditor(TypeInfo(TAnchorSide), TControl, 'AnchorSideLeft', THiddenPropertyEditor);`
342+
343+</Code>
344+
345+# Forms
346+
347+The IDE allows to easily create descendants of forms and frames. These forms can have new published components and new published methods. But you when you add new published properties, they are not accessible via the Object Inspector. They work only at runtime. The methods in the IDE are fake, they can not be executed. The IDE can create the components, because their classes are registered in the component palette. Your new properties only exist in your source code. The IDE can not fake them yet. That's why you have to register the new class. That means:
348+
349+- create a design time package (or extend an existing one)
350+- your form must be in a unit of this package
351+
352+``` pascal
353+...
354+interface
355+
356+uses FormEditingIntf, Forms;
357+
358+type
359+ TYourForm = class(TForm)
360+ private
361+ FYourProperty: integer;
362+ published
363+ property YourProperty: integer read FYourProperty write FYourProperty;
364+ end;
365+
366+procedure Register;
367+
368+implementation
369+
370+procedure Register;
371+begin
372+ FormEditingHook.RegisterDesignerBaseClass(TYourForm);
373+end;
374+...
375+```
376+
377+- check the "Register" in the package editor for this unit.
378+- Install the package in the IDE
379+
380+# Designer
381+
382+## Writing a designer mediator
383+
384+The standard designer allows to visually edit LCL controls, while all others are shown as icons(like TOpenDialog or TDataSource). To visually edit non LCL control, you need to create a **designer mediator**. This can be used to design webpages, UML diagrams or other widgetsets like [[fpGUI]]. There is a complete example in examples/designnonlcl/.
385+
386+- Install the example package examples/designnonlcl/notlcldesigner.lpk and restart the IDE. This will register the designer mediator for TMyWidget components and add the new components TMyButton, TMyGroupBox to the component palette.
387+- Open the the example project examples/designnonlcl/project/NonLCL1.lpi.
388+- Open the unit1.pas and show the designer form (F12). You should see the components as red rectangles, which can be selected, moved and resized like LCL controls.
389+
390+## Create a new unique component name
391+
392+``` pascal
393+uses FormEditingIntf;
394+
395+...
396+
397+NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);
398+
399+// Or if you need to create the name before creating the component:
400+
401+NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
402+// ComponentClassName will be used as prefix, without a leading T.
403+// OwnerComponent is the new owner of the new component.
404+```
405+
406+## Select a component in the designer/object inspector
407+
408+``` pascal
409+uses propedits;
410+..
411+GlobalDesignHook.SelectOnlyThis(AComponent);
412+```
413+
414+## Get unit, designer, form of a file
415+
416+``` pascal
417+uses ProjectIntf, LazIDEIntf, ComponentEditors;
418+...
419+// open file
420+if LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent])<>mrOk then exit;
421+// get file interface
422+aFile:=LazarusIDE.ActiveProject.FindFile(Filename,[]);
423+// get designer
424+aDesigner:=TComponentEditorDesigner(LazarusIDE.GetDesignerWithProjectFile(AFile,true));
425+if aDesigner=nil then
426+ exit; // this unit has no resource (form, frame, datamodule, etc.)
427+// get form
428+aForm:=aDesigner.Form;
429+```
430+
431+## Save unit, designer form
432+
433+``` pascal
434+uses LazIDEIntf;
435+...
436+if LazarusIDE.DoSaveEditorFile(Filename,[])<>mrOk then exit;
437+```
438+
439+# Event handlers
440+
441+There are several events in the IDE, for which plugins can add their own handlers.
442+
443+## Designer events
444+
445+In propedits.pp there is a "GlobalDesignHook" object, which maintains several events for designing. Each event calls a list of handlers. The default handlers are added by the IDE. You can add your own handlers with the AddHandlerXXX and RemoveHandlerXXX methods. They will be called before the default handlers.
446+
447+Examples:
448+
449+- Adding your handler (this is normally done in the constructor of your object):
450+
451+` GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);`
452+
453+- Removing your handler:
454+
455+` GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);`
456+
457+- You can remove all handlers at once. For example, it is a good idea to add this line in the destructor of your object:
458+
459+` GlobalDesignHook.RemoveAllHandlersForObject(Self);`
460+
461+The handlers of GlobalDesignHook:
462+
463+- Lookup root
464+ - ChangeLookupRoot - Called when the "LookupRoot" changed. The "LookupRoot" is the owner object of the currently selected components. Normally this is a TForm.
465+- Methods
466+ - CreateMethod
467+ - GetMethodName
468+ - GetMethods
469+ - MethodExists
470+ - RenameMethod
471+ - ShowMethod
472+ - MethodFromAncestor
473+ - ChainCall
474+- Components
475+ - GetComponent
476+ - GetComponentName
477+ - GetComponentNames
478+ - GetRootClassName
479+ - AddClicked - Called when the user selected a component class and clicked on the designer to add a new component. You can change the component class and the parent.
480+ - ComponentRenamed - Called when a component was renamed
481+- TPersistent objects
482+ - PersistentAdded - Called when a new TPersistent was added to the LookupRoot
483+ - PersistentDeleting - Called before a TPersistent is freed.
484+ - DeletePersistent - Called by the IDE to delete a TPersistent.
485+ - GetSelectedPersistents - Called when getting the current selection of TPersistent.
486+ - SetSelectedPersistents - Called when setting the current selection of TPersistent.
487+ - GetObject
488+ - GetObjectName
489+ - GetObjectNames
490+- Modifing
491+ - Modified
492+ - Revert
493+ - RefreshPropertyValues
494+- Selection
495+ - SetSelection
496+ - GetSelection
497+
498+### Examples for designer events
499+
500+#### Example: When dropping a new component move control to a child
501+
502+``` pascal
503+...
504+type
505+ TYourDesignerExtension = class
506+ private
507+ function AddClicked(ADesigner: TIDesigner;
508+ MouseDownComponent: TComponent; Button: TMouseButton;
509+ Shift: TShiftState; X, Y: Integer;
510+ var AComponentClass: TComponentClass;
511+ var NewParent: TComponent): boolean of object;
512+ public
513+ constructor Create;
514+ destructor Destroy; override;
515+ end;
516+
517+...
518+constructor TYourDesignerExtension.Create;
519+begin
520+ // register your handler
521+ GlobalDesignHook.AddHandlerAddClicked(@AddClicked);
522+end;
523+
524+destructor TYourDesignerExtension.Destroy;
525+begin
526+ // remove your handler:
527+ GlobalDesignHook.RemoveHandlerAddClicked(@AddClicked);
528+end;
529+
530+function TYourDesignerExtension.AddClicked(ADesigner: TIDesigner;
531+ MouseDownComponent: TComponent; Button: TMouseButton;
532+ Shift: TShiftState; X, Y: Integer;
533+ var AComponentClass: TComponentClass;
534+ var NewParent: TComponent): boolean of object;
535+begin
536+ // in this example TYourControl is a custom control with a ChildControl.
537+ // Whenever the user drops a control on the TYourControl instead of adding
538+ // the new control to the TYourControl, the new control is added as child
539+ // of ChildControl.
540+ // created
541+ if MouseDownComponent is TYourControl then
542+ NewParent:=TYourControl(MouseDownComponent).ChildControl;
543+ Result:=true;
544+end;
545+```
546+
547+### Disabling the designer mouse handler
548+
549+Normally the designer catches all mouse events of a component. If your custom control should handle the mouse events handle you can either set the **csDesignInteractive** **ControlStyle** or you can fine control it via the message *CM_DESIGNHITTEST* in your control:
550+
551+``` pascal
552+type
553+ TYourControl = class(TCustomControl)
554+ protected
555+ procedure CMDesignHitTest(var Message: TLMessage); message CM_DESIGNHITTEST;
556+ end;
557+...
558+procedure TYourControl.CMDesignHitTest(var Message: TLMessage);
559+var
560+ p: TSmallPoint;
561+ aShiftState: TShiftState;
562+begin
563+ aShiftState:=KeysToShiftState(PtrUInt(Message.WParam));
564+ p:=TSmallPoint(longint(Message.LParam));
565+ debugln(['TForm1.CMDesignHitTest ShiftState=',dbgs(aShiftState),' x=',p.x,' y=',p.y]);
566+ if SkipDesignerMouseHandler then
567+ Message.Result:=1; // now the designer calls the normal MouseUp, MouseMove and MouseDown methods.
568+end;
569+```
570+
571+## Getting notified when a designer form is modified
572+
573+Every designed LCL form has a Designer of type TIDesigner. The IDE creates designers of type TComponentEditorDesigner defined in the IDEIntf unit componenteditors. For example:
574+
575+``` pascal
576+procedure TYourAddOn.OnDesignerModified(Sender: TObject);
577+var
578+ IDEDesigner: TComponentEditorDesigner;
579+begin
580+ IDEDesigner:=TComponentEditorDesigner(Sender);
581+ ...
582+end;
583+
584+procedure TYourAddOn.ConnectDesignerForm(Form1: TCustomForm);
585+var
586+ IDEDesigner: TComponentEditorDesigner;
587+begin
588+ IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
589+ IDEDesigner.AddHandlerModified(@OnDesignerModified);
590+end;
591+```
592+
593+## Project events
594+
595+These events are defined in unit LazIDEIntf.
596+
597+- **LazarusIDE.AddHandlerOnProjectClose**: called before a project is closed
598+- **LazarusIDE.AddHandlerOnProjectOpened**: called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
599+- **LazarusIDE.AddHandlerOnSavingAll**: called before IDE saves everything
600+- **LazarusIDE.AddHandlerOnSavedAll**: called after IDE saved everything
601+- **LazarusIDE.AddHandlerOnProjectBuilding**: called before IDE builds the project
602+- **LazarusIDE.AddHandlerOnProjectDependenciesCompiling**: called before IDE compiles package dependencies of project
603+- **LazarusIDE.AddHandlerOnProjectDependenciesCompiled**: called after IDE compiled package dependencies of project
604+- **LazarusIDE.AddHandlerOnProjectBuildingFinished**: called after IDE built the project (successful or not) (since 1.5)
605+
606+## Other IDE events
607+
608+uses LazIDEIntf;
609+
610+Call these in the Register procedure of your package:
611+
612+- **LazarusIDE.AddHandlerOnIDERestoreWindows**: called when IDE is restores its windows (before opening the first project)
613+- **LazarusIDE.AddHandlerOnIDEClose**: called when IDE is shutting down (after closequery, so no more interactivity)
614+- **LazarusIDE.AddHandlerOnQuickSyntaxCheck**: called when the menu item or the shortcut for Quick syntax check is executed
615+- **LazarusIDE.AddHandlerOnLazarusBuilding**: // called before IDE builds Lazarus IDE (since 1.5)
616+- **LazarusIDE.AddHandlerOnLazarusBuildingFinished**: // called after IDE builds Lazarus IDE (since 1.5)
617+- **LazarusIDE.AddHandlerGetFPCFrontEndParams**: // called when the IDE gets the parameters of the 'fpc' front end tool (since 1.5)
618+- **LazarusIDE.AddHandlerGetFPCFrontEndPath**: // called when the IDE gets the path of the 'fpc' front end tool (since 1.5)
619+- **LazarusIDE.AddHandlerOnUpdateIDEComponentPalette**: ?
620+- **LazarusIDE.AddHandlerOnUpdateComponentPageControl**: ?
621+- **LazarusIDE.AddHandlerOnShowDesignerFormOfSource**: // called after showing a designer form for code editor (AEditor can be nil!) (since 1.5)
622+- **LazarusIDE.AddHandlerOnShowSourceOfActiveDesignerForm**: // called after showing a code of designer form (since 1.5)
623+- **LazarusIDE.AddHandlerOnChangeToolStatus**: //called when IDEToolStatus has changed (e.g. itNone-\>itBuilder etc.) (since 1.5)
624+
625+Call these in the initialization section of one of your units:
626+
627+- **AddBootHandler**
628+ - AddBootHandler(**libhTransferMacrosCreated**,@YourProc): called after IDEMacros were created (since 1.3)
629+ - AddBootHandler(**libhEnvironmentOptionsLoaded**,@YourProc): called after environment options were loaded (since 1.3)
630+
631+# Project
632+
633+## Current Project
634+
635+The current main project can be obtained by LazarusIDE.ActiveProject. (unit LazIDEIntf)
636+
637+## All units of current project
638+
639+Note that the term "units of project" is ambiguous.
640+
641+- The project inspector shows the list of the .lpi file. The project might not use all these units on all platforms and maybe the developer forgot to add all used units.
642+- Units from used packages are not project units.
643+
644+### All units of the project inspector
645+
646+To iterate through all pascal units listed in the project inspector you can use for example:
647+
648+``` pascal
649+uses
650+ LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
651+
652+procedure ListProjectUnits;
653+var
654+ LazProject: TLazProject;
655+ i: Integer;
656+ LazFile: TLazProjectFile;
657+begin
658+ LazProject:=LazarusIDE.ActiveProject;
659+ if LazProject<>nil then
660+ for i:=0 to LazProject.FileCount-1 do
661+ begin
662+ LazFile:=LazProject.Files[i];
663+ if LazFile.IsPartOfProject
664+ and FilenameIsPascalUnit(LazFile.Filename)
665+ then
666+ debugln(LazFile.Filename);
667+ end;
668+end;
669+```
670+
671+### All used units of project
672+
673+To find all currently used units of a project/package you can use the following. Note that the IDE needs to parse all units, so it can take some time.
674+
675+``` pascal
676+uses
677+ LazLoggerBase, LazIDEIntf, ProjectIntf;
678+
679+procedure ListProjectUnits;
680+var
681+ LazProject: TLazProject;
682+ i: Integer;
683+begin
684+ LazProject:=LazarusIDE.ActiveProject;
685+ if LazProject<>nil then
686+ begin
687+ Units:=LazarusIDE.FindUnitsOfOwner(LazProject,[fuooListed,fuooUsed]); // add fuooPackages to include units from packages
688+ try
689+ for i:=0 to Units.Count-1 do
690+ debugln('Filename=',Units[i]);
691+ finally
692+ Units.Free;
693+ end;
694+ end;
695+end;
696+```
697+
698+## The .lpr, .lpi and .lps file of a project
699+
700+``` pascal
701+uses
702+ LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
703+var
704+ LazProject: TLazProject;
705+begin
706+ LazProject:=LazarusIDE.ActiveProject;
707+ // every project has a .lpi file:
708+ DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);
709+
710+ // if the project session information is stored in a separate .lps file:
711+ if LazProject.SessionStorage<>pssNone then
712+ DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);
713+
714+ // If the project has a .lpr file it is the main source file:
715+ if (LazProject.MainFile<>nil)
716+ and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
717+ DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
718+end;
719+```
720+
721+## The executable / target file name of a project
722+
723+There is a macro \$(TargetFile), which can be used in paths and external tools. You can query the macro in code:
724+
725+``` pascal
726+uses MacroIntf;
727+
728+function MyGetProjectTargetFile: string;
729+begin
730+ Result:='$(TargetFile)';
731+ if not IDEMacros.SubstituteMacros(Result) then
732+ raise Exception.Create('unable to retrieve target file of project');
733+end;
734+```
735+
736+See here for more macros: [[IDE Macros in paths and filenames]].
737+
738+## Add your own project type
739+
740+You can add items to the 'New ...' dialog:
741+
742+- See the unit ProjectIntf of the package IDEIntf.
743+- See the [[Project Templates]]
744+
745+## Add your own file type
746+
747+You can add items to the 'New ...' dialog:
748+
749+- See the unit ProjectIntf of the package IDEIntf.
750+- Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.
751+
752+### Add a new file type
753+
754+``` pascal
755+uses ProjectIntf;
756+...
757+ { TFileDescText }
758+
759+ TFileDescMyText = class(TProjectFileDescriptor)
760+ public
761+ constructor Create; override;
762+ function GetLocalizedName: string; override;
763+ function GetLocalizedDescription: string; override;
764+ end;
765+...
766+
767+procedure Register;
768+
769+implementation
770+
771+procedure Register;
772+begin
773+ RegisterProjectFileDescriptor(TFileDescMyText.Create,FileDescGroupName);
774+end;
775+
776+{ TFileDescMyText }
777+
778+constructor TFileDescMyText.Create;
779+begin
780+ inherited Create;
781+ Name:='MyText'; // do not translate this
782+ DefaultFilename:='text.txt';
783+ AddToProject:=false;
784+end;
785+
786+function TFileDescText.GetLocalizedName: string;
787+begin
788+ Result:='My Text'; // replace this with a resourcestring
789+end;
790+
791+function TFileDescText.GetLocalizedDescription: string;
792+begin
793+ Result:='An empty text file';
794+end;
795+```
796+
797+### Add a new form type
798+
799+``` pascal
800+uses ProjectIntf;
801+
802+...
803+ TFileDescPascalUnitWithMyForm = class(TFileDescPascalUnitWithResource)
804+ public
805+ constructor Create; override;
806+ function GetInterfaceUsesSection: string; override;
807+ function GetLocalizedName: string; override;
808+ function GetLocalizedDescription: string; override;
809+ end;
810+...
811+
812+procedure Register;
813+
814+implementation
815+
816+procedure Register;
817+begin
818+ RegisterProjectFileDescriptor(TFileDescPascalUnitWithMyForm.Create,FileDescGroupName);
819+end;
820+
821+{ TFileDescPascalUnitWithMyForm }
822+
823+constructor TFileDescPascalUnitWithMyForm.Create;
824+begin
825+ inherited Create;
826+ Name:='MyForm'; // do not translate this
827+ ResourceClass:=TMyForm;
828+ UseCreateFormStatements:=true;
829+end;
830+
831+function TFileDescPascalUnitWithMyForm.GetInterfaceUsesSection: string;
832+begin
833+ Result:='Classes, SysUtils, MyWidgetSet';
834+end;
835+
836+function TFileDescPascalUnitWithForm.GetLocalizedName: string;
837+begin
838+ Result:='MyForm'; // replace this with a resourcestring
839+end;
840+
841+function TFileDescPascalUnitWithForm.GetLocalizedDescription: string;
842+begin
843+ Result:='Create a new MyForm from example package NotLCLDesigner';
844+end;
845+```
846+
847+# Packages
848+
849+You can perform various actions with [[Lazarus Packages|Packages]]:
850+
851+## Search in all packages
852+
853+Iterate all packages loaded in the IDE (since 0.9.29).
854+
855+``` pascal
856+uses PackageIntf;
857+...
858+for i:=0 to PackageEditingInterface.GetPackageCount-1 do
859+ writeln(PackageEditingInterface.GetPackages(i).Name);
860+```
861+
862+## Search a package with a name
863+
864+``` pascal
865+uses PackageIntf;
866+...
867+var
868+ Pkg: TIDEPackage;
869+begin
870+ Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
871+ if Pkg<>nil then
872+ ...
873+end;
874+```
875+
876+Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
877+
878+## Obtener el nombre de fichero .lpk de un paquete instalado
879+
880+``` pascal
881+uses PackageIntf;
882+...
883+var
884+ Pkg: TIDEPackage;
885+begin
886+ Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
887+ if Pkg<>nil then
888+ LPKFilename:=Pkg.Filename;
889+end;
890+```
891+
892+## Instalar paquetes
893+
894+**Note**: Instalar únicamente paquetes con plugins IDE. Instalar otros paquetes puede hacer el IDE inestable.
895+
896+``` pascal
897+uses PackageIntf, contnrs;
898+...
899+ PkgList:=TObjectList.create(true);
900+ try
901+ Pkg:=TLazPackageID.Create;
902+ Pkg.Name:='Cody';
903+ PkgList.Add(Pkg);
904+ // Chequear si el IDE puede encontrar cody.lpk y todas las dependencias.
905+ // El IDE will muestra algunas advertencias/confirmaciones si algo parece extraño.
906+ if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
907+ exit;
908+ // in this example we have checked already, so skip the warnings
909+ // and rebuild the IDE
910+ if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
911+ exit;
912+ finally
913+ PkgList.Free;
914+ end;
915+```
916+
917+## Abrir un fichero de paquete (lpk)
918+
919+``` pascal
920+uses PackageIntf, FileUtil;
921+...
922+var
923+ pkg: TIDEPackage;
924+begin
925+ if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
926+ exit;
927+ Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
928+ ...
929+end;
930+```
931+
932+> **Warning:** El IDE cierra automáticamentes paquetes no utilizados on idle. Nunca mantener referencias a un TIDEPackage.
933+
934+## Find the package(s) of a unit
935+
936+You have the file name of a unit, e.g. '/home/user/unit.pas', and you want to know to which package(s) and project(s) it belongs use this:
937+
938+``` pascal
939+uses Classes, PackageIntf, ProjectIntf;
940+...
941+var
942+ Owners: TFPList;
943+ i: Integer;
944+ o: TObject;
945+begin
946+ Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
947+ if Owners=nil then begin
948+ // unit is not directly associated with a project/package
949+ // maybe the unit was for some reason not added, but is reachable
950+ // search in all unit paths of all projects/packages
951+ // Beware: This can lead to false hits
952+ Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
953+ [piosfExcludeOwned,piosfIncludeSourceDirectories]);
954+ end;
955+ if Owners=nil then exit;
956+ try
957+ for i:=0 to Owners.Count-1 do begin
958+ o:=TObject(Owners[i]);
959+ if o is TIDEPackage then begin
960+ writeln('Owner is package ',TIDEPackage(o).Name);
961+ end else if o is TLazProject then begin
962+ writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
963+ end;
964+ end;
965+ finally
966+ Owners.Free;
967+ end;
968+```
969+
970+# Ventanas
971+
972+Hay básicamente cuatro tipos de ventandas del IDE:
973+
974+- La barra principal del IDE bar que es Application.MainForm. Siempre está presente.
975+- Ventanas flotantes/acoplables tales como el editor del código fuente, the Object Inspectors and Messages.
976+- Fomas modales, tales como los diálogos de búsqueda, opciones y preguntas.
977+- Sugerencias y formas de completado.
978+
979+## Añadiendo una nueva ventana de acoplado en el IDE
980+
981+Que es una ventana del IDE acoplable: Ventanas tales como el Editor de Código Fuente o el Inspector de Objetos son ventanas flotantes que se pueden acoplar si se instala un paquete de acoplamiento, y su estado, posición y tamaño se almacena y restaura en el siguiente inicio del IDE. En orden a restaurar una ventana el IDE necesita un creador como se definie en la unidad **IDEWindowIntf** del paquete IDEIntf. Cada ventana acoplable debe tener un único nombre. No usar por tanto nombres genéricos como 'FileBrowser' porque haría que entre fácilmente en conflicto con otros paquetes. Y no usar nombres cortos como 'XYZ' porque el creador es responsable de todas las formas que comiencen con dicho nombre.
982+
983+### Como registrar una ventana acoplable del IDE
984+
985+Recuerda utilizar un nombre largo única que sea un identificador pascal válido. Tus ventanas pueden tener cualquier caption que necesites.
986+
987+``` pascal
988+uses SysUtils, IDEWindowIntf;
989+...
990+var MyIDEWindow: TMyIDEWindow = nil;
991+
992+procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
993+begin
994+ // check the name
995+ if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
996+ // create the form if not already done and disable autosizing
997+ IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
998+ ... init the window ...
999+ AForm:=MyIDEWindow;
1000+end;
1001+
1002+procedure Register;
1003+begin
1004+ IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
1005+ // the default boundsrect of the form is:
1006+ // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
1007+ // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
1008+end;
1009+```
1010+
1011+### Showing an IDE window
1012+
1013+**Do not use Show**. Use:
1014+
1015+``` pascal
1016+IDEWindowCreators.ShowForm(MyIDEWindow,false);
1017+```
1018+
1019+This will work with docking. The docking system might wrap the form into a docking site. The BringToFront parameter tells the docking system to make the form and all its parent sites visible and bring the top level site to the front.
1020+
1021+### Notes about IDEWindowCreators and SimpleLayoutStorage
1022+
1023+The IDEWindowCreators.SimpleLayoutStorage simply stores the BoundsRect and WindowState of all forms that were once opened. It is used as fallback if no dockmaster is installed. It stores the state even if a DockMaster is installed, so that when the dockmaster is uninstalled the forms bounds are restored.
1024+
1025+The IDEWindowCreators is used by all dockable forms to register themselves and to show forms. When showing a form the Creator checks if a IDEDockMaster is installed and will delegate the showing to it. If no IDEDockMaster is installed it simply shows the form. The IDEDockMaster can use the information in the IDEWindowCreators to create forms by names and get an idea where to place a form when showing it for the first time. For more details see the packages AnchorDockingDsgn and EasyDockMgDsgn.
1026+
1027+# CodeTools
1028+
1029+The CodeTools is a package providing a vast amount of functions to parse, search and change pascal sources and some basic functions for other languages as well. You can use them without the IDE too. It is recommended that you read first about the CodeTools in general before using them in the IDE: [[Codetools]].
1030+
1031+Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:
1032+
1033+``` pascal
1034+uses LazIDEIntf;
1035+...
1036+ // save changes in source editor to codetools
1037+ LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors
1038+```
1039+
1040+## Adding a resource directive to a file
1041+
1042+This adds a {\$R example.res} to a pascal unit:
1043+
1044+``` pascal
1045+procedure AddResourceDirectiveToPascalSource(const Filename: string);
1046+var
1047+ ExpandedFilename: String;
1048+ CodeBuf: TCodeBuffer;
1049+begin
1050+ // make sure the filename is trimmed and contains a full path
1051+ ExpandedFilename:=CleanAndExpandFilename(Filename);
1052+
1053+ // save changes in source editor to codetools
1054+ LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
1055+
1056+ // load the file
1057+ CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);
1058+
1059+ // add the resource directive
1060+ if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
1061+ LazarusIDE.DoJumpToCodeToolBossError;
1062+end;
1063+```
1064+
1065+The codetools provides also functions like FindResourceDirective and RemoveDirective.
1066+
1067+## Getting the search paths for units and include files
1068+
1069+There are many different search paths in the IDE from projects, packages, the fpc and lazarus directory and there are many types of paths: before or after resolving macros, with or without inherited search paths, as relative or absolute paths. All files in a directory share the same set of search paths. You can get the search paths for each directory fully resolved by asking the codetools:
1070+
1071+``` pascal
1072+uses CodeToolManager;
1073+...
1074+
1075+Dir:=''; // the empty directory is for new files and has the same settings as the project directory
1076+
1077+// Getting the search paths for include files:
1078+Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);
1079+
1080+// Getting the search paths for units:
1081+// This search path is passed to the compiler.
1082+// It contains the package output directories, but not the package source directories.
1083+Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);
1084+
1085+// There can be additional unit search paths for the IDE only (not passed to the compiler)
1086+Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);
1087+
1088+// The complete search path contains also all package source paths for units:
1089+Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
1090+```
1091+
1092+# Source Editor
1093+
1094+## Active source editor
1095+
1096+``` pascal
1097+uses SrcEditorIntf;
1098+...
1099+Editor:=SourceEditorManagerIntf.ActiveEditor;
1100+if Editor=nil then exit;
1101+Filename:=Editor.FileName;
1102+ScreenPos:=Editor.CursorScreenXY;
1103+TextPos:=Editor.CursorTextXY;
1104+```
1105+
1106+# SynEdit
1107+
1108+## Getting the settings for a TSynEdit
1109+
1110+When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:
1111+
1112+``` pascal
1113+uses SrcEditorIntf;
1114+...
1115+SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
1116+```
1117+
1118+## Getting the settings for a SynEdit highlighter
1119+
1120+When you have a dialog using a TSynEdit with a highlighter and you want to use the same colors like the source editor highlighter for this language use:
1121+
1122+``` pascal
1123+uses SrcEditorIntf;
1124+...
1125+SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
1126+```
1127+
1128+See for an example: TSQLStringsPropertyEditorDlg.Create in the unit [SQLStringsPropertyEditorDlg](http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas).
1129+
1130+# Help
1131+
1132+## Adding help for sources
1133+
1134+First create a THelpDatabase:
1135+
1136+``` pascal
1137+HelpDB := TFPDocHTMLHelpDatabase(
1138+ HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
1139+ TFPDocHTMLHelpDatabase,true));
1140+HelpDB.DefaultBaseURL := 'http://your.help.org/';
1141+
1142+FPDocNode := THelpNode.CreateURL(HelpDB,
1143+ 'Package1 - A new package',
1144+ 'file://index.html');
1145+HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// once as TOC
1146+DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
1147+ '*.pp;*.pas',false);// and once as normal page
1148+HelpDB.RegisterItem(DirectoryItem);
1149+```
1150+
1151+## Adding lines to the messages window
1152+
1153+``` pascal
1154+unit IDEMsgIntf;
1155+...
1156+var Dir: String;
1157+begin
1158+ Dir:=GetCurrentDir;
1159+ IDEMessagesWindow.BeginBlock;
1160+ IDEMessagesWindow.AddMsg('unit1.pas(30,4) Error: Identifier not found "a"',Dir,0);
1161+ IDEMessagesWindow.EndBlock;
1162+end;
1163+```
1164+
1165+# Miscellaneous
1166+
1167+## Adding a macro
1168+
1169+You can add your own IDE macros:
1170+
1171+``` pascal
1172+uses MacroIntf, MacroDefIntf;
1173+
1174+procedure Register;
1175+begin
1176+ // registering a Macro with a fixed value:
1177+ IDEMacros.Add(TTransferMacro.Create('MyMacro1','Value','Description',nil,[]));
1178+end;
1179+
1180+// registering a Macro with a dynamic value requires a method of an object
1181+procedure TForm1.Init;
1182+begin
1183+ IDEMacros.Add(TTransferMacro.Create('MyMacro','Value','Description',@OnResolveMyMacro,[]));
1184+end;
1185+
1186+function TForm1.OnResolveMyMacro(const s: string; const Data: PtrInt;
1187+ var Abort: boolean): string;
1188+// s is the parameter. For example $MyMacro(parameter)
1189+// Data is the querying instance. Can be nil.
1190+// If there is an error set Abort to true.
1191+// The result string can contain macros. For example Result:='$(FPCVer)/$(TargetOS)';
1192+begin
1193+ Result:='MyValue';
1194+end;
1195+```
1196+
1197+## Depurando el IDE
1198+
1199+- Compila el IDE como habitualmente e.g. via Tools/Build de Lazarus. Asegúrate de establecer los flags de depuración tales como -g o -gw2. Lo mejor es utilizar el profile "Debug IDE" que contiene algunos flags útiles para depuración.
1200+- Abre el proyecto ide\lazarus.lpi. No se puede compilar directamente este proyecto.
1201+- Establece los puntos de ruptura (breakpoints) acorde a tus necesidades etc y ejecuta el proyecto.
1202+
1203+## See also
1204+
1205+- [[Creating IDE Help]]
1206+- [[Help protocol]]
1207+- [[How To Write Lazarus Component]]
1208+
1209+---
1210+*From the Free Pascal wiki, page "Extending the IDE/es", as it stood on 2020-02-14 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history