Sign in Join
Wiki copytemporary

Extending the IDE: what changed

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

Title: Extending the IDE

OldNewLine
1+# Extending the IDE
2+
3+## Overview
4+
5+The Lazarus IDE supports several types of plugins:
6+
7+Components
8+These are the items found on the Component Palette. For example, TButton (found on the Standard page) can be used to create Buttons.
9+
10+Component Editors
11+When you double-click a component in the Designer, a component editor is invoked. The component editor may also add an extra item to the Designer's popup menu (accessed when you right-click on a component in the Designer).
12+
13+Property Editors
14+Each row in the grid section of the Object Inspector uses a property editor to allow that property's value to be set or altered.
15+
16+Experts
17+Experts encompass all other plugin types. They might register new menu item(s), register short cuts, or extend other IDE features.
18+
19+There are two possible ways to add your own plugins to the Lazarus IDE:
20+
21+1. Write a package, install it and register your plugin(s) in the 'Register' procedure of a unit.
22+2. Extend the Lazarus code, and send your svn diff to the Lazarus mailing list.
23+
24+## Packages and Makefile Dependencies
25+
26+The following steps are necessary, if adding new package dependencies.
27+
28+- Create/Update the Makefile for the package via Package editor / More ... / Create Makefile.
29+- Fix all dependencies. See tools/updatemakefiles.lpi
30+
31+# Menu items
32+
33+You can add new menu items, hide or move existing ones and add sub menus, sections. For your own forms you can register the TMainMenu/TPopupMenu so that other packages can extend your form further. The unit **MenuIntf** of the package IDEIntf contains all registration for menus and many standard menu items of the IDE itself.
34+
35+## Adding a menu item
36+
37+A single menu item like 'View Object Inspector' is called a **TIDEMenuCommand**. You can create one with **RegisterIDEMenuCommand**, which has two forms with lots of parameters:
38+
39+``` pascal
40+function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
41+ const Name, Caption: string;
42+ const OnClickMethod: TNotifyEvent = nil;
43+ const OnClickProc: TNotifyProcedure = nil;
44+ const Command: TIDECommand = nil;
45+ const ResourceName: String = ''
46+ ): TIDEMenuCommand; overload;
47+function RegisterIDEMenuCommand(const Path, Name, Caption: string;
48+ const OnClickMethod: TNotifyEvent = nil;
49+ const OnClickProc: TNotifyProcedure = nil;
50+ const Command: TIDECommand = nil;
51+ const ResourceName: String = ''
52+ ): TIDEMenuCommand; overload;
53+```
54+
55+The difference between those two is only how you specify the parent menu section. You can give the menu section directly or indirectly via the path. Many standard sections can be found in the *MenuIntf* unit. For example the section **mnuTools**, which is the *Tools* menu of the main IDE bar. It contains a section **itmSecondaryTools**, the recommended section for third party tools.
56+
57+The following registers a new menu command, with the name 'MyTool', the caption 'Start my tool' and when clicked executes the procedure *StartMyTool*:
58+
59+``` pascal
60+procedure StartMyTool;
61+begin
62+ ...executed when menu item is clicked...
63+end;
64+
65+procedure Register;
66+begin
67+ RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
68+end;
69+```
70+
71+If you want to call a method instead of a procedure use the **OnClickMethod** parameter.
72+
73+This menu item does not have a shortcut. If you want a shortcut you must create a **TIDECommand** and pass it in the **Command** parameter. For example:
74+
75+``` pascal
76+uses ... IDECommands, MenuIntf;
77+...
78+var
79+ Key: TIDEShortCut;
80+ Cat: TIDECommandCategory;
81+ CmdMyTool: TIDECommand;
82+begin
83+ // register IDE shortcut and menu item
84+ Key := IDEShortCut(VK_UNKNOWN,[],VK_UNKNOWN,[]);
85+ Cat:=IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
86+ CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
87+ RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
88+end;
89+```
90+
91+# Shortcuts
92+
93+All shortcuts (keys that invoke some action) are registered in the IDECommandList. Here is an example how to find all commands that react on key Ctrl-D or two key combinations starting with Ctrl-D:
94+
95+``` pascal
96+uses
97+ Classes, SysUtils, LCLProc, IDECommands;
98+
99+procedure ListCtrlD;
100+// list all idecommands with shortcut Ctrl-D
101+var
102+ i: integer;
103+ Cmd: TIDECommand;
104+ Commands: TFPList;
105+begin
106+ Commands:=IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D,[],VK_UNKNOWN,[]));
107+ try
108+ for i:=0 to Commands.Count-1 do begin
109+ Cmd:=TIDECommand(Commands[i]);
110+ writeln('Cmd: ',Cmd.Name,
111+ ' A=',dbgs(Cmd.ShortcutA.Shift1),'-',Cmd.ShortcutA.Key1,
112+ ',',dbgs(Cmd.ShortcutA.Shift2),'-',Cmd.ShortcutA.Key2,
113+ ' B=',dbgs(Cmd.ShortcutB.Shift1),'-',Cmd.ShortcutB.Key1,
114+ ',',dbgs(Cmd.ShortcutB.Shift2),'-',Cmd.ShortcutB.Key2
115+ );
116+ end;
117+ finally
118+ Commands.Free;
119+ end;
120+end;
121+```
122+
123+# Config files
124+
125+## Load/Save settings
126+
127+All configuration files of the IDE are stored in one directory as xml files. Packages can add their own files there too. The [[pcp|primary config path]] directory can be read with:
128+
129+``` pascal
130+uses LazIDEIntf;
131+...
132+ Directory := LazarusIDE.GetPrimaryConfigPath;
133+```
134+
135+Packages can create their own xml files with:
136+
137+``` pascal
138+uses
139+ ..., LCLProc, BaseIDEIntf, LazConfigStorage;
140+
141+const
142+ Version = 1;
143+var
144+ Config: TConfigStorage;
145+ SomeValue: String;
146+ SomeInteger: Integer;
147+ SomeBoolean: Boolean;
148+ FileVersion: LongInt;
149+begin
150+ SomeValue:='Default';
151+ SomeInteger:=3;
152+ SomeBoolean:=true;
153+
154+ // save settings
155+ try
156+ Config:=GetIDEConfigStorage('mysettings.xml',false);
157+ try
158+ // store the version number so future extensions can handle old config files
159+ Config.SetDeleteValue('Path/To/The/Version',Version,0);
160+ // store string variable "SomeValue"
161+ // if SomeValue has the default value the entry is not stored,
162+ // so only the differences to the default are stored.
163+ // This way the xml is kept short and defaults may change in future.
164+ Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
165+ Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
166+ Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
167+ // there are more overloads for SetDeleteValue, find out with Ctrl+Space
168+ finally
169+ Config.Free;
170+ end;
171+
172+ except
173+ on E: Exception do begin
174+ DebugLn(['Saving mysettings.xml failed: ',E.Message]);
175+ end;
176+ end;
177+
178+ // load settings
179+ try
180+ Config:=GetIDEConfigStorage('mysettings.xml',true);
181+ try
182+ // read the version of the config
183+ FileVersion:=Config.GetValue('Path/To/The/Version',0);
184+ // read a string variable "SomeValue". If the entry does not exist, use
185+ // the Default
186+ SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
187+ SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
188+ SomeBoolean:=Config.GetValue('Path/To/Some/Boolean',true);
189+ finally
190+ Config.Free;
191+ end;
192+ except
193+ on E: Exception do begin
194+ DebugLn(['Loading mysettings.xml failed: ',E.Message]);
195+ end;
196+ end;
197+end;
198+```
199+
200+Notes:
201+
202+- 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]].
203+- GetIDEConfigStorage when loading checks if the file exists and if not it will copy the template from the secondary config directory.
204+- Since 0.9.31 you can use this function to load a file outside the primary config directory.
205+
206+# Components
207+
208+## Extending existing components
209+
210+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.
211+
212+Add the properties you need with appropriate setter and getter methods and storage fields.
213+
214+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.
215+
216+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).
217+
218+Here is a simple example:
219+
220+``` pascal
221+unit Unit1;
222+
223+{$mode objfpc}{$H+}
224+
225+interface
226+
227+uses
228+ Classes, SysUtils, Forms, StdCtrls;
229+
230+type
231+ TEdX = class(TEdit)
232+ private
233+ FNewIntProp: integer;
234+ FNewStrProp: string;
235+ procedure SetNewIntProp(AValue: integer);
236+ procedure SetNewStrProp(AValue: string);
237+ public
238+ constructor Create(theComponent: TComponent); override;
239+ property NewIntProp: integer read FNewIntProp write SetNewIntProp;
240+ property NewStrProp: string read FNewStrProp write SetNewStrProp;
241+ end;
242+
243+ TForm1 = class(TForm)
244+ procedure FormCreate(Sender: TObject);
245+ private
246+ edX: TEdX;
247+ public
248+ end;
249+
250+var
251+ Form1: TForm1;
252+
253+implementation
254+
255+{ TForm1 }
256+
257+procedure TForm1.FormCreate(Sender: TObject);
258+begin
259+ edX:= TEdX.Create(Self);
260+ edX.Parent:= Self;
261+ edX.Caption:= IntToStr(edX.NewIntProp);
262+ Caption:= edX.NewStrProp;
263+end;
264+
265+{$R *.lfm}
266+
267+{ TEdX }
268+
269+procedure TEdX.SetNewIntProp(AValue: integer);
270+begin
271+ if FNewIntProp=AValue then Exit;
272+ FNewIntProp:=AValue;
273+ // add custom code here
274+end;
275+
276+procedure TEdX.SetNewStrProp(AValue: string);
277+begin
278+ if FNewStrProp=AValue then Exit;
279+ FNewStrProp:=AValue;
280+ // add custom code here
281+end;
282+
283+constructor TEdX.Create(theComponent: TComponent);
284+begin
285+ inherited Create(theComponent);
286+ FNewStrProp:= 'new string property value';
287+ FNewIntProp:= 99;
288+end;
289+
290+end.
291+```
292+
293+## Writing components
294+
295+*Main article: [[How To Write Lazarus Component]]*
296+
297+You can create new components via the package editor.
298+
299+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**.
300+
301+### Giving a new component an icon for the component palette
302+
303+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:
304+
305+``` bash
306+~/lazarus/tools/lazres mybutton.lrs tmybutton.png
307+# note: Laz trunk/1.4+? uses .res resource files instead of .lrs files:
308+~/lazarus/tools/lazres mybutton.res tmybutton.png
309+```
310+
311+This creates an pascal include file, which is used in the initialization section of mybutton.pas:
312+
313+``` pascal
314+initialization
315+ {$I mybutton.lrs}
316+ // or in newer Lazarus version - to docheck syntax
317+ {$R mybutton.res}
318+```
319+
320+Install the package.
321+
322+### Hiding a component in the component palette
323+
324+Package IDEIntf, unit componentreg:
325+
326+`IDEComponentPalette.FindComponent('TButton').Visible:=false;`
327+
328+## Writing component editors
329+
330+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.
331+
332+## Writing property editors
333+
334+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.
335+
336+### Hiding a property in the Object Inspector
337+
338+There is a special property editor for hiding:
339+
340+``` pascal
341+ RegisterPropertyEditor(TypeInfo(TAnchorSide), TControl, 'AnchorSideLeft', THiddenPropertyEditor);
342+```
343+
344+### Find out what property editor is used for a property
345+
346+``` pascal
347+uses Classes, TypInfo, PropEdits;
348+var
349+ Instance: TPersistent; // your instance
350+ PropInfo: PPropInfo;
351+ EdClass: TPropertyEditorClass;
352+begin
353+ PropInfo:=GetPropInfo(Instance.ClassType,'Name'); // replace Name with the property name you are searching for
354+ GetEditorClass(PropInfo, Instance);
355+end;
356+```
357+
358+# Forms
359+
360+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:
361+
362+- create a design time package (or extend an existing one)
363+- your form must be in a unit of this package
364+
365+``` pascal
366+...
367+interface
368+
369+uses FormEditingIntf, Forms;
370+
371+type
372+ TYourForm = class(TForm)
373+ private
374+ FYourProperty: integer;
375+ published
376+ property YourProperty: integer read FYourProperty write FYourProperty;
377+ end;
378+
379+procedure Register;
380+
381+implementation
382+
383+procedure Register;
384+begin
385+ FormEditingHook.RegisterDesignerBaseClass(TYourForm);
386+end;
387+...
388+```
389+
390+- check the "Register" in the package editor for this unit.
391+- Install the package in the IDE
392+
393+# Designer
394+
395+## Writing a designer mediator
396+
397+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/.
398+
399+- 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.
400+- Open the the example project examples/designnonlcl/project/NonLCL1.lpi.
401+- 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.
402+
403+## Create a new unique component name
404+
405+``` pascal
406+uses FormEditingIntf;
407+
408+...
409+
410+NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);
411+
412+// Or if you need to create the name before creating the component:
413+
414+NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
415+// ComponentClassName will be used as prefix, without a leading T.
416+// OwnerComponent is the new owner of the new component.
417+```
418+
419+## Select a component in the designer/object inspector
420+
421+``` pascal
422+uses propedits;
423+..
424+GlobalDesignHook.SelectOnlyThis(AComponent);
425+```
426+
427+## Get unit, designer, form of a file
428+
429+``` pascal
430+uses ProjectIntf, LazIDEIntf, ComponentEditors;
431+...
432+// open file
433+if LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent])<>mrOk then exit;
434+// get file interface
435+aFile:=LazarusIDE.ActiveProject.FindFile(Filename,[]);
436+// get designer
437+aDesigner:=TComponentEditorDesigner(LazarusIDE.GetDesignerWithProjectFile(AFile,true));
438+if aDesigner=nil then
439+ exit; // this unit has no resource (form, frame, datamodule, etc.)
440+// get form
441+aForm:=aDesigner.Form;
442+```
443+
444+## Save unit, designer form
445+
446+``` pascal
447+uses LazIDEIntf;
448+...
449+if LazarusIDE.DoSaveEditorFile(Filename,[])<>mrOk then exit;
450+```
451+
452+## Save designer form as Pascal
453+
454+- Use the function **FormEditingHook.SaveComponentAsPascal** of unit *FormEditingfIntf*. See *examples/pascalstream/CopyAsPasPkg/copyaspasdemounit1.pas*.
455+
456+# Event handlers
457+
458+There are several events in the IDE, for which plugins can add their own handlers.
459+
460+## Designer events
461+
462+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.
463+
464+Examples:
465+
466+- Adding your handler (this is normally done in the constructor of your object):
467+
468+` GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);`
469+
470+- Removing your handler:
471+
472+` GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);`
473+
474+- You can remove all handlers at once. For example, it is a good idea to add this line in the destructor of your object:
475+
476+` GlobalDesignHook.RemoveAllHandlersForObject(Self);`
477+
478+The handlers of GlobalDesignHook:
479+
480+- Lookup root
481+ - ChangeLookupRoot - Called when the "LookupRoot" changed. The "LookupRoot" is the owner object of the currently selected components. Normally this is a TForm.
482+- Methods
483+ - CreateMethod
484+ - GetMethodName
485+ - GetMethods
486+ - MethodExists
487+ - RenameMethod
488+ - ShowMethod
489+ - MethodFromAncestor
490+ - ChainCall
491+- Components
492+ - GetComponent
493+ - GetComponentName
494+ - GetComponentNames
495+ - GetRootClassName
496+ - 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.
497+ - ComponentRenamed - Called when a component was renamed
498+- TPersistent objects
499+ - PersistentAdded - Called when a new TPersistent was added to the LookupRoot
500+ - PersistentDeleting - Called before a TPersistent is freed.
501+ - DeletePersistent - Called by the IDE to delete a TPersistent.
502+ - GetSelectedPersistents - Called when getting the current selection of TPersistent.
503+ - SetSelectedPersistents - Called when setting the current selection of TPersistent.
504+ - GetObject
505+ - GetObjectName
506+ - GetObjectNames
507+- Modifing
508+ - Modified
509+ - Revert
510+ - RefreshPropertyValues
511+- Selection
512+ - SetSelection
513+ - GetSelection
514+
515+### Examples for designer events
516+
517+#### Example: When dropping a new component move control to a child
518+
519+``` pascal
520+...
521+type
522+ TYourDesignerExtension = class
523+ private
524+ function AddClicked(ADesigner: TIDesigner;
525+ MouseDownComponent: TComponent; Button: TMouseButton;
526+ Shift: TShiftState; X, Y: Integer;
527+ var AComponentClass: TComponentClass;
528+ var NewParent: TComponent): boolean of object;
529+ public
530+ constructor Create;
531+ destructor Destroy; override;
532+ end;
533+
534+...
535+constructor TYourDesignerExtension.Create;
536+begin
537+ // register your handler
538+ GlobalDesignHook.AddHandlerAddClicked(@AddClicked);
539+end;
540+
541+destructor TYourDesignerExtension.Destroy;
542+begin
543+ // remove your handler:
544+ GlobalDesignHook.RemoveHandlerAddClicked(@AddClicked);
545+end;
546+
547+function TYourDesignerExtension.AddClicked(ADesigner: TIDesigner;
548+ MouseDownComponent: TComponent; Button: TMouseButton;
549+ Shift: TShiftState; X, Y: Integer;
550+ var AComponentClass: TComponentClass;
551+ var NewParent: TComponent): boolean of object;
552+begin
553+ // in this example TYourControl is a custom control with a ChildControl.
554+ // Whenever the user drops a control on the TYourControl instead of adding
555+ // the new control to the TYourControl, the new control is added as child
556+ // of ChildControl.
557+ // created
558+ if MouseDownComponent is TYourControl then
559+ NewParent:=TYourControl(MouseDownComponent).ChildControl;
560+ Result:=true;
561+end;
562+```
563+
564+### Disabling the designer mouse handler
565+
566+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:
567+
568+``` pascal
569+type
570+ TYourControl = class(TCustomControl)
571+ protected
572+ procedure CMDesignHitTest(var Message: TLMessage); message CM_DESIGNHITTEST;
573+ end;
574+...
575+procedure TYourControl.CMDesignHitTest(var Message: TLMessage);
576+var
577+ p: TSmallPoint;
578+ aShiftState: TShiftState;
579+begin
580+ aShiftState:=KeysToShiftState(PtrUInt(Message.WParam));
581+ p:=TSmallPoint(longint(Message.LParam));
582+ debugln(['TForm1.CMDesignHitTest ShiftState=',dbgs(aShiftState),' x=',p.x,' y=',p.y]);
583+ if SkipDesignerMouseHandler then
584+ Message.Result:=1; // now the designer calls the normal MouseUp, MouseMove and MouseDown methods.
585+end;
586+```
587+
588+## Getting notified when a designer form is modified
589+
590+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:
591+
592+``` pascal
593+procedure TYourAddOn.OnDesignerModified(Sender: TObject);
594+var
595+ IDEDesigner: TComponentEditorDesigner;
596+begin
597+ IDEDesigner:=TComponentEditorDesigner(Sender);
598+ ...
599+end;
600+
601+procedure TYourAddOn.ConnectDesignerForm(Form1: TCustomForm);
602+var
603+ IDEDesigner: TComponentEditorDesigner;
604+begin
605+ IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
606+ IDEDesigner.AddHandlerModified(@OnDesignerModified);
607+end;
608+```
609+
610+## Project events
611+
612+These events are defined in unit LazIDEIntf.
613+
614+- **LazarusIDE.AddHandlerOnProjectClose**: called before a project is closed
615+- **LazarusIDE.AddHandlerOnProjectOpened**: called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
616+- **LazarusIDE.AddHandlerOnSavingAll**: called before IDE saves everything
617+- **LazarusIDE.AddHandlerOnSavedAll**: called after IDE saved everything
618+- **LazarusIDE.AddHandlerOnProjectBuilding**: called before IDE builds the project
619+- **LazarusIDE.AddHandlerOnProjectDependenciesCompiling**: called before IDE compiles package dependencies of project
620+- **LazarusIDE.AddHandlerOnProjectDependenciesCompiled**: called after IDE compiled package dependencies of project
621+- **LazarusIDE.AddHandlerOnProjectBuildingFinished**: called after IDE built the project (successful or not) (since 1.5)
622+- **LazarusIDE.AddHandlerOnLoadSaveCustomData**: called after loading/before saving a project's CustomData or a project file's CustomData. On save the CustomData is cloned to a temporary tree, so it is free for altering before save. Use this (since 2.3)
623+
624+## Other IDE events
625+
626+uses LazIDEIntf;
627+
628+Call these in the Register procedure of your package:
629+
630+- **LazarusIDE.AddHandlerOnIDERestoreWindows**: called when IDE is restores its windows (before opening the first project)
631+- **LazarusIDE.AddHandlerOnIDEClose**: called when IDE is shutting down (after closequery, so no more interactivity)
632+- **LazarusIDE.AddHandlerOnQuickSyntaxCheck**: called when the menu item or the shortcut for Quick syntax check is executed
633+- **LazarusIDE.AddHandlerOnLazarusBuilding**: // called before IDE builds Lazarus IDE (since 1.5)
634+- **LazarusIDE.AddHandlerOnLazarusBuildingFinished**: // called after IDE builds Lazarus IDE (since 1.5)
635+- **LazarusIDE.AddHandlerGetFPCFrontEndParams**: // called when the IDE gets the parameters of the 'fpc' front end tool (since 1.5)
636+- **LazarusIDE.AddHandlerGetFPCFrontEndPath**: // called when the IDE gets the path of the 'fpc' front end tool (since 1.5)
637+- **LazarusIDE.AddHandlerOnUpdateIDEComponentPalette**: ?
638+- **LazarusIDE.AddHandlerOnUpdateComponentPageControl**: ?
639+- **LazarusIDE.AddHandlerOnShowDesignerFormOfSource**: // called after showing a designer form for code editor (AEditor can be nil!) (since 1.5)
640+- **LazarusIDE.AddHandlerOnShowSourceOfActiveDesignerForm**: // called after showing a code of designer form (since 1.5)
641+- **LazarusIDE.AddHandlerOnChangeToolStatus**: //called when IDEToolStatus has changed (e.g. itNone-\>itBuilder etc.) (since 1.5)
642+
643+Call these in the initialization section of one of your units:
644+
645+- **AddBootHandler**
646+ - AddBootHandler(**libhTransferMacrosCreated**,@YourProc): called after IDEMacros were created (since 1.3)
647+ - AddBootHandler(**libhEnvironmentOptionsLoaded**,@YourProc): called after environment options were loaded (since 1.3)
648+
649+# Project
650+
651+## Current Project
652+
653+The current main project can be obtained by LazarusIDE.ActiveProject. (unit LazIDEIntf)
654+
655+## All units of current project
656+
657+Note that the term "units of project" is ambiguous.
658+
659+- 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.
660+- Units from used packages are not project units.
661+
662+### All units of the project inspector
663+
664+To iterate through all pascal units listed in the project inspector you can use for example:
665+
666+``` pascal
667+uses
668+ LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
669+
670+procedure ListProjectUnits;
671+var
672+ LazProject: TLazProject;
673+ i: Integer;
674+ LazFile: TLazProjectFile;
675+begin
676+ LazProject:=LazarusIDE.ActiveProject;
677+ if LazProject<>nil then
678+ for i:=0 to LazProject.FileCount-1 do
679+ begin
680+ LazFile:=LazProject.Files[i];
681+ if LazFile.IsPartOfProject
682+ and FilenameIsPascalUnit(LazFile.Filename)
683+ then
684+ debugln(LazFile.Filename);
685+ end;
686+end;
687+```
688+
689+### All used units of project
690+
691+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.
692+
693+``` pascal
694+uses
695+ LazLoggerBase, LazIDEIntf, ProjectIntf;
696+
697+procedure ListProjectUnits;
698+var
699+ LazProject: TLazProject;
700+ i: Integer;
701+begin
702+ LazProject:=LazarusIDE.ActiveProject;
703+ if LazProject<>nil then
704+ begin
705+ Units:=LazarusIDE.FindUnitsOfOwner(LazProject,[fuooListed,fuooUsed]); // add fuooPackages to include units from packages
706+ try
707+ for i:=0 to Units.Count-1 do
708+ debugln('Filename=',Units[i]);
709+ finally
710+ Units.Free;
711+ end;
712+ end;
713+end;
714+```
715+
716+## The .lpr, .lpi and .lps file of a project
717+
718+``` pascal
719+uses
720+ LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
721+var
722+ LazProject: TLazProject;
723+begin
724+ LazProject:=LazarusIDE.ActiveProject;
725+ // every project has a .lpi file:
726+ DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);
727+
728+ // if the project session information is stored in a separate .lps file:
729+ if LazProject.SessionStorage<>pssNone then
730+ DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);
731+
732+ // If the project has a .lpr file it is the main source file:
733+ if (LazProject.MainFile<>nil)
734+ and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
735+ DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
736+end;
737+```
738+
739+## The executable / target file name of a project
740+
741+There is a macro \$(TargetFile), which can be used in paths and external tools. You can query the macro in code:
742+
743+``` pascal
744+uses MacroIntf;
745+
746+function MyGetProjectTargetFile: string;
747+begin
748+ Result:='$(TargetFile)';
749+ if not IDEMacros.SubstituteMacros(Result) then
750+ raise Exception.Create('unable to retrieve target file of project');
751+end;
752+```
753+
754+See here for more macros: [[IDE Macros in paths and filenames]].
755+
756+## Add your own project type
757+
758+You can add items to the 'New ...' dialog:
759+
760+- See the unit ProjectIntf of the package IDEIntf.
761+- See the [[Project Templates]]
762+
763+## Add your own file type
764+
765+You can add items to the 'New ...' dialog:
766+
767+- See the unit ProjectIntf of the package IDEIntf.
768+- Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.
769+
770+### Add a new file type
771+
772+``` pascal
773+uses ProjectIntf;
774+...
775+ { TFileDescText }
776+
777+ TFileDescMyText = class(TProjectFileDescriptor)
778+ public
779+ constructor Create; override;
780+ function GetLocalizedName: string; override;
781+ function GetLocalizedDescription: string; override;
782+ end;
783+...
784+
785+procedure Register;
786+
787+implementation
788+
789+procedure Register;
790+begin
791+ RegisterProjectFileDescriptor(TFileDescMyText.Create,FileDescGroupName);
792+end;
793+
794+{ TFileDescMyText }
795+
796+constructor TFileDescMyText.Create;
797+begin
798+ inherited Create;
799+ Name:='MyText'; // do not translate this
800+ DefaultFilename:='text.txt';
801+ AddToProject:=false;
802+end;
803+
804+function TFileDescText.GetLocalizedName: string;
805+begin
806+ Result:='My Text'; // replace this with a resourcestring
807+end;
808+
809+function TFileDescText.GetLocalizedDescription: string;
810+begin
811+ Result:='An empty text file';
812+end;
813+```
814+
815+### Add a new form type
816+
817+``` pascal
818+uses ProjectIntf;
819+
820+...
821+ TFileDescPascalUnitWithMyForm = class(TFileDescPascalUnitWithResource)
822+ public
823+ constructor Create; override;
824+ function GetInterfaceUsesSection: string; override;
825+ function GetLocalizedName: string; override;
826+ function GetLocalizedDescription: string; override;
827+ end;
828+...
829+
830+procedure Register;
831+
832+implementation
833+
834+procedure Register;
835+begin
836+ RegisterProjectFileDescriptor(TFileDescPascalUnitWithMyForm.Create,FileDescGroupName);
837+end;
838+
839+{ TFileDescPascalUnitWithMyForm }
840+
841+constructor TFileDescPascalUnitWithMyForm.Create;
842+begin
843+ inherited Create;
844+ Name:='MyForm'; // do not translate this
845+ ResourceClass:=TMyForm;
846+ UseCreateFormStatements:=true;
847+end;
848+
849+function TFileDescPascalUnitWithMyForm.GetInterfaceUsesSection: string;
850+begin
851+ Result:='Classes, SysUtils, MyWidgetSet';
852+end;
853+
854+function TFileDescPascalUnitWithForm.GetLocalizedName: string;
855+begin
856+ Result:='MyForm'; // replace this with a resourcestring
857+end;
858+
859+function TFileDescPascalUnitWithForm.GetLocalizedDescription: string;
860+begin
861+ Result:='Create a new MyForm from example package NotLCLDesigner';
862+end;
863+```
864+
865+## Add new file
866+
867+Adding a new file to the project: uses LazIDEIntf, ProjectIntf;
868+
869+`LazarusIDE.DoNewEditorFile(FileDescriptorUnit,`*`,`*`,[nfIsPartOfProject,nfOpenInEditor]);`
870+
871+The FileDescriptorUnit generates a default code and a default filename. The new file is opened in the source editor and it is not yet saved to disk.
872+
873+You can create a new file with specific filename and custom source:
874+
875+`LazarusIDE.DoNewEditorFile(FileDescriptorUnit,NewFilename,NewSource,[nfIsPartOfProject,nfOpenInEditor]);`
876+
877+Keep in mind that the file is not saved to disk. This is done when the user triggers a *Save*.
878+
879+Alternatively you can create a file via codetools or directly on disk (e.g. TFileStream) and open that in the source editor:
880+
881+` LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent]);`
882+
883+## Store custom values in lpi/lps
884+
885+You can store your own data via name/value pairs in CustomData and CustomSessionData.
886+
887+`LazarusIDE.ActiveProject.CustomData['Name']:=Value; // stored in lpi - x-platform, shared data`\
888+`LazarusIDE.ActiveProject.CustomSessionData['Name']:=Value; // stored in lps - only this machine`
889+
890+### Store custom filenames in lpi/lps
891+
892+Filenames should be stored relative in the lpi and some care must be taken because platform specifics. Here is an example from the pas2jsdsgn package:
893+
894+Hook the OnLoadSaveCustomData handler:
895+
896+``` pascal
897+uses
898+ LazIDEIntf, ProjectIntf, AvgLvlTree;
899+
900+...
901+ // in registration
902+ LazarusIDE.AddHandlerOnLoadSaveCustomData(@OnLoadSaveCustomData);
903+
904+...
905+procedure TPJSController.OnLoadSaveCustomData(Sender: TObject; Load: boolean;
906+ CustomData: TStringToStringTree; PathDelimChanged: boolean);
907+var
908+ fn: String;
909+ aProject: TLazProject;
910+begin
911+ if Sender is TLazProject then
912+ begin
913+ aProject:=TLazProject(Sender);
914+ if CustomData[PJSProjectWebBrowser]='1' then
915+ begin
916+ fn:=CustomData[PJSProjectHTMLFile];
917+ if fn<>'' then
918+ begin
919+ if Load then
920+ aProject.ConvertFromLPIFilename(fn)
921+ else
922+ aProject.ConvertToLPIFilename(fn);
923+ CustomData[PJSProjectHTMLFile]:=fn;
924+ end;
925+ end;
926+ end;
927+ if PathDelimChanged then ;
928+end;
929+```
930+
931+# Packages
932+
933+You can perform various actions with [[Lazarus Packages|Packages]]:
934+
935+## Search in all packages
936+
937+Iterate all packages loaded in the IDE (since 0.9.29).
938+
939+``` pascal
940+uses PackageIntf;
941+...
942+for i:=0 to PackageEditingInterface.GetPackageCount-1 do
943+ writeln(PackageEditingInterface.GetPackages(i).Name);
944+```
945+
946+## Search a package with a name
947+
948+``` pascal
949+uses PackageIntf;
950+...
951+var
952+ Pkg: TIDEPackage;
953+begin
954+ Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
955+ if Pkg<>nil then
956+ ...
957+end;
958+```
959+
960+Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
961+
962+## Get lpk filename of an installed package
963+
964+``` pascal
965+uses PackageIntf;
966+...
967+var
968+ Pkg: TIDEPackage;
969+begin
970+ Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
971+ if Pkg<>nil then
972+ LPKFilename:=Pkg.Filename;
973+end;
974+```
975+
976+## Install packages
977+
978+**Note**: Only install packages with IDE plugins. Installing other packages can make the IDE unstable.
979+
980+``` pascal
981+uses PackageIntf, contnrs;
982+...
983+ PkgList:=TObjectList.create(true);
984+ try
985+ Pkg:=TLazPackageID.Create;
986+ Pkg.Name:='Cody';
987+ PkgList.Add(Pkg);
988+ // check if the IDE can find the cody.lpk and all dependencies
989+ // The IDE will prompt some warnings/confirmations if something looks strange.
990+ if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
991+ exit;
992+ // in this example we have checked already, so skip the warnings
993+ // and rebuild the IDE
994+ if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
995+ exit;
996+ finally
997+ PkgList.Free;
998+ end;
999+```
1000+
1001+## Open a package file (lpk)
1002+
1003+``` pascal
1004+uses PackageIntf, FileUtil;
1005+...
1006+var
1007+ pkg: TIDEPackage;
1008+begin
1009+ if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
1010+ exit;
1011+ Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
1012+ ...
1013+end;
1014+```
1015+
1016+> **Warning:** The IDE automatically closes unused packages on idle. Never keep a reference to an TIDEPackage.
1017+
1018+## Find the package(s) of a unit
1019+
1020+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:
1021+
1022+``` pascal
1023+uses Classes, PackageIntf, ProjectIntf;
1024+...
1025+var
1026+ Owners: TFPList;
1027+ i: Integer;
1028+ o: TObject;
1029+begin
1030+ Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
1031+ if Owners=nil then begin
1032+ // unit is not directly associated with a project/package
1033+ // maybe the unit was for some reason not added, but is reachable
1034+ // search in all unit paths of all projects/packages
1035+ // Beware: This can lead to false hits
1036+ Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
1037+ [piosfExcludeOwned,piosfIncludeSourceDirectories]);
1038+ end;
1039+ if Owners=nil then exit;
1040+ try
1041+ for i:=0 to Owners.Count-1 do begin
1042+ o:=TObject(Owners[i]);
1043+ if o is TIDEPackage then begin
1044+ writeln('Owner is package ',TIDEPackage(o).Name);
1045+ end else if o is TLazProject then begin
1046+ writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
1047+ end;
1048+ end;
1049+ finally
1050+ Owners.Free;
1051+ end;
1052+```
1053+
1054+## Find the required package(s) of a project/package
1055+
1056+You have a project or a package and you want to know which package(s) are required use this:
1057+
1058+``` pascal
1059+uses Classes, PackageIntf, ProjectIntf;
1060+...
1061+var
1062+ lPkgList: TFPList;
1063+ i: Integer;
1064+begin
1065+ PackageEditingInterface.GetRequiredPackages(LazarusIDE.ActiveProject, lPkgList, [pirNotRecursive]);
1066+ try
1067+ for i := 0 to lPkgList.Count - 1 do begin
1068+ writeln('required package is ' + TIDEPackage(lPkgList[i]).Filename);
1069+ end;
1070+ finally
1071+ lPkgList.Free;
1072+ end;
1073+...
1074+```
1075+
1076+# Windows
1077+
1078+There are basically four types of IDE windows.
1079+
1080+- the main IDE bar is the Application.MainForm. It is always present.
1081+- floating/dockable windows like the Source Editors, the Object Inspectors and Messages.
1082+- the modal forms, like the find dialog, options dialogs and questions.
1083+- hints and completion forms
1084+
1085+## Adding a new dockable IDE window
1086+
1087+What is a dockable IDE window: Windows like the Source Editor or the Object Inspector are floating windows, that can be docked if a docking package is installed, and its state, position and size is stored and restored on next IDE start. In order to restore a window the IDE needs a creator as defined in the unit **IDEWindowIntf** of the package IDEIntf. Each dockable window must have a unique name. Do not use generic names like 'FileBrowser' because this will easily clash with other packages. And don't use short names like 'XYZ', because the creator is responsible for all forms beginning with this name.
1088+
1089+### How to register a dockable IDE window
1090+
1091+Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.
1092+
1093+``` pascal
1094+uses SysUtils, IDEWindowIntf;
1095+...
1096+var MyIDEWindow: TMyIDEWindow = nil;
1097+
1098+procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
1099+begin
1100+ // check the name
1101+ if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
1102+ // create the form if not already done and disable autosizing
1103+ IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
1104+ ... init the window ...
1105+ AForm:=MyIDEWindow;
1106+end;
1107+
1108+procedure Register;
1109+begin
1110+ IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
1111+ // the default boundsrect of the form is:
1112+ // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
1113+ // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
1114+end;
1115+```
1116+
1117+### Showing an IDE window
1118+
1119+**Do not use Show**. Use:
1120+
1121+``` pascal
1122+IDEWindowCreators.ShowForm(MyIDEWindow,false);
1123+```
1124+
1125+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.
1126+
1127+### Notes about IDEWindowCreators and SimpleLayoutStorage
1128+
1129+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.
1130+
1131+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.
1132+
1133+# CodeTools
1134+
1135+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]].
1136+
1137+Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:
1138+
1139+``` pascal
1140+uses LazIDEIntf;
1141+...
1142+ // save changes in source editor to codetools
1143+ LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors
1144+```
1145+
1146+## Adding a resource directive to a file
1147+
1148+This adds a {\$R example.res} to a pascal unit:
1149+
1150+``` pascal
1151+procedure AddResourceDirectiveToPascalSource(const Filename: string);
1152+var
1153+ ExpandedFilename: String;
1154+ CodeBuf: TCodeBuffer;
1155+begin
1156+ // make sure the filename is trimmed and contains a full path
1157+ ExpandedFilename:=CleanAndExpandFilename(Filename);
1158+
1159+ // save changes in source editor to codetools
1160+ LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
1161+
1162+ // load the file
1163+ CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);
1164+
1165+ // add the resource directive
1166+ if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
1167+ LazarusIDE.DoJumpToCodeToolBossError;
1168+end;
1169+```
1170+
1171+The codetools provides also functions like FindResourceDirective and RemoveDirective.
1172+
1173+## Getting the search paths for units and include files
1174+
1175+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:
1176+
1177+``` pascal
1178+uses CodeToolManager;
1179+...
1180+
1181+Dir:=''; // the empty directory is for new files and has the same settings as the project directory
1182+
1183+// Getting the search paths for include files:
1184+Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);
1185+
1186+// Getting the search paths for units:
1187+// This search path is passed to the compiler.
1188+// It contains the package output directories, but not the package source directories.
1189+Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);
1190+
1191+// There can be additional unit search paths for the IDE only (not passed to the compiler)
1192+Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);
1193+
1194+// The complete search path contains also all package source paths for units:
1195+Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
1196+```
1197+
1198+# Source Editor
1199+
1200+## Active source editor
1201+
1202+``` pascal
1203+uses SrcEditorIntf;
1204+...
1205+Editor:=SourceEditorManagerIntf.ActiveEditor;
1206+if Editor=nil then exit;
1207+Filename:=Editor.FileName;
1208+ScreenPos:=Editor.CursorScreenXY; // glyph/character and line, both 1-based
1209+TextPos:=Editor.CursorTextXY; // byte and line, both 1-based
1210+```
1211+
1212+## Find source editor
1213+
1214+You can search for a disk file using the expanded filename. Keep in mind, that newly created files usually have filenames without directory.
1215+
1216+``` pascal
1217+uses SrcEditorIntf;
1218+...
1219+Editor:=SourceEditorManagerIntf.SourceEditorIntfWithFilename(aFilename);
1220+if Editor=nil then exit;
1221+Filename:=Editor.FileName; // can be different to what you searchedm e.g. due to UTF-8 and case insensitive file system
1222+```
1223+
1224+## Edit source
1225+
1226+``` pascal
1227+// You can set the whole source editor text and wipe out the undo history
1228+Editor.SourceText:='New content';
1229+
1230+// In order to keep the undo history use the Selection:
1231+Editor.BlockBegin:=Point(1,1);
1232+Editor.BlockEnd:=Point(1,Editor.LineCount+1);
1233+Editor.Selection:='New Content';
1234+```
1235+
1236+# SynEdit
1237+
1238+## Getting the settings for a TSynEdit
1239+
1240+When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:
1241+
1242+``` pascal
1243+uses SrcEditorIntf;
1244+...
1245+SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
1246+```
1247+
1248+## Getting the settings for a SynEdit highlighter
1249+
1250+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:
1251+
1252+``` pascal
1253+uses SrcEditorIntf;
1254+...
1255+SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
1256+```
1257+
1258+See for an example: TSQLStringsPropertyEditorDlg.Create in the unit [SQLStringsPropertyEditorDlg](http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas).
1259+
1260+# Help
1261+
1262+## Adding help for sources
1263+
1264+First create a THelpDatabase:
1265+
1266+``` pascal
1267+HelpDB := TFPDocHTMLHelpDatabase(
1268+ HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
1269+ TFPDocHTMLHelpDatabase,true));
1270+HelpDB.DefaultBaseURL := 'http://your.help.org/';
1271+
1272+FPDocNode := THelpNode.CreateURL(HelpDB,
1273+ 'Package1 - A new package',
1274+ 'file://index.html');
1275+HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// once as TOC
1276+DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
1277+ '*.pp;*.pas',false);// and once as normal page
1278+HelpDB.RegisterItem(DirectoryItem);
1279+```
1280+
1281+## Adding lines to the messages window
1282+
1283+``` pascal
1284+unit IDEMsgIntf, IDEExternToolIntf;
1285+...
1286+var Dir: String;
1287+begin
1288+ Dir:=GetCurrentDir;
1289+ IDEMessagesWindow.AddCustomMessage(mluProgress,'Some important message','file.pas',0,0,'My section');
1290+end;
1291+```
1292+
1293+# IDE options frame
1294+
1295+## Open options in a specific frame
1296+
1297+``` pascal
1298+uses LazIDEIntf;
1299+...
1300+LazarusIDE.DoOpenIDEOptions(TYourIDEOptionsFrame);
1301+```
1302+
1303+# Miscellaneous
1304+
1305+## Adding a macro
1306+
1307+You can add your own IDE macros:
1308+
1309+``` pascal
1310+uses MacroIntf, MacroDefIntf;
1311+
1312+procedure Register;
1313+begin
1314+ // registering a Macro with a fixed value:
1315+ IDEMacros.Add(TTransferMacro.Create('MyMacro1','Value','Description',nil,[]));
1316+end;
1317+
1318+// registering a Macro with a dynamic value requires a method of an object
1319+procedure TForm1.Init;
1320+begin
1321+ IDEMacros.Add(TTransferMacro.Create('MyMacro','Value','Description',@OnResolveMyMacro,[]));
1322+end;
1323+
1324+function TForm1.OnResolveMyMacro(const s: string; const Data: PtrInt;
1325+ var Abort: boolean): string;
1326+// s is the parameter. For example $MyMacro(parameter)
1327+// Data is the querying instance. Can be nil.
1328+// If there is an error set Abort to true.
1329+// The result string can contain macros. For example Result:='$(FPCVer)/$(TargetOS)';
1330+begin
1331+ Result:='MyValue';
1332+end;
1333+```
1334+
1335+## Query an IDE macro
1336+
1337+``` pascal
1338+uses MacroIntf;
1339+
1340+function GetCurrentFPCSourceDirectory: string;
1341+begin
1342+ Result:='$(FPCSrcDir)';
1343+ if not IDEMacros.SubstituteMacros(Result) then
1344+ raise Exception.Create('unable to retrieve FPCSrcDir');
1345+end;
1346+```
1347+
1348+## Debugging the IDE
1349+
1350+- Open a Lazarus instance (called 1st instance of Lazarus).
1351+- Compile the IDE as usual e.g. via Tools/Build Lazarus. Make sure you set up debugging flags like -g or -gw2. Best is to use the profile "Debug IDE" which contains many flags useful for debugging.
1352+- Open the ide\lazarus.lpi project (always with the 1st instance of Lazarus).
1353+- Compile this lazarus project (always with the 1st instance of Lazarus): another instance of Lazarus should appear (called 2nd instance of Lazarus).
1354+- Set breakpoints as needed etc in the opened source code (in the 1st instance of Lazarus), and play with 2nd run-time instance: for example, install a design-time static package *.lpk, then use, interact with this just installed package inside this 2nd instance.
1355+
1356+## Adding a parser for external tool
1357+
1358+There is an example parser for the Delphi dcc32.exe compiler in compilers/delphi/lazdelphi.lpk.
1359+
1360+It parses the output of dcc32.exe for filename and line number, etc, so when you click on an error or hint in the Messages window the IDE jumps to the right position.
1361+
1362+After installing this package, you can call dcc32.exe in any project/package instead of fpc: In Compiler Options / Compiler Commands:
1363+
1364+- **Execute Before**: pathofdcc32.exe params...
1365+- **Parsers**: check "Delphi Compiler"
1366+- **Compiler**: Skip calling compiler
1367+
1368+## See also
1369+
1370+- [[Creating IDE Help]]
1371+- [[Help protocol]]
1372+- [[How To Write Lazarus Component]]
1373+
1374+---
1375+*From the Free Pascal wiki, page "Extending the IDE", as it stood on 2023-12-04 after a change by Jpasj, 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