Sign in Join
Wiki copytemporary

Extending the IDE: what changed

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

OldNewLine
11# Extending the IDE
22
33## Overview
44
55The Lazarus IDE supports several types of plugins:
66
77Components
88These are the items found on the Component Palette. For example, TButton (found on the Standard page) can be used to create Buttons.
99
1010Component Editors
1111When 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).
1212
1313Property Editors
1414Each row in the grid section of the Object Inspector uses a property editor to allow that property's value to be set or altered.
1515
1616Experts
1717Experts encompass all other plugin types. They might register new menu item(s), register short cuts, or extend other IDE features.
1818
1919There are two possible ways to add your own plugins to the Lazarus IDE:
2020
21211. Write a package, install it and register your plugin(s) in the 'Register' procedure of a unit.
22222. Extend the Lazarus code, and send your svn diff to the Lazarus mailing list.
2323
2424## Packages and Makefile Dependencies
2525
2626The following steps are necessary, if adding new package dependencies.
2727
2828- Create/Update the Makefile for the package via Package editor / More ... / Create Makefile.
2929- Fix all dependencies. See tools/updatemakefiles.lpi
3030
3131# Menu items
3232
3333You 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.
3434
3535## Adding a menu item
3636
3737A 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:
3838
3939``` pascal
4040function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
4141 const Name, Caption: string;
4242 const OnClickMethod: TNotifyEvent = nil;
4343 const OnClickProc: TNotifyProcedure = nil;
4444 const Command: TIDECommand = nil;
4545 const ResourceName: String = ''
4646 ): TIDEMenuCommand; overload;
4747function RegisterIDEMenuCommand(const Path, Name, Caption: string;
4848 const OnClickMethod: TNotifyEvent = nil;
4949 const OnClickProc: TNotifyProcedure = nil;
5050 const Command: TIDECommand = nil;
5151 const ResourceName: String = ''
5252 ): TIDEMenuCommand; overload;
5353```
5454
5555The 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.
5656
5757The following registers a new menu command, with the name 'MyTool', the caption 'Start my tool' and when clicked executes the procedure *StartMyTool*:
5858
5959``` pascal
6060procedure StartMyTool;
6161begin
6262 ...executed when menu item is clicked...
6363end;
6464
6565procedure Register;
6666begin
6767 RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
6868end;
6969```
7070
7171If you want to call a method instead of a procedure use the **OnClickMethod** parameter.
7272
7373This 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:
7474
7575``` pascal
7676uses ... IDECommands, MenuIntf;
7777...
7878var
7979 Key: TIDEShortCut;
8080 Cat: TIDECommandCategory;
8181 CmdMyTool: TIDECommand;
8282begin
8383 // register IDE shortcut and menu item
8484 Key := IDEShortCut(VK_UNKNOWN,[],VK_UNKNOWN,[]);
8585 Cat:=IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
8686 CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
8787 RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
8888end;
8989```
9090
9191# Shortcuts
9292
9393All 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:
9494
9595``` pascal
9696uses
9797 Classes, SysUtils, LCLProc, IDECommands;
9898
9999procedure ListCtrlD;
100100// list all idecommands with shortcut Ctrl-D
101101var
102102 i: integer;
103103 Cmd: TIDECommand;
104104 Commands: TFPList;
105105begin
106106 Commands:=IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D,[],VK_UNKNOWN,[]));
107107 try
108108 for i:=0 to Commands.Count-1 do begin
109109 Cmd:=TIDECommand(Commands[i]);
110110 writeln('Cmd: ',Cmd.Name,
111111 ' A=',dbgs(Cmd.ShortcutA.Shift1),'-',Cmd.ShortcutA.Key1,
112112 ',',dbgs(Cmd.ShortcutA.Shift2),'-',Cmd.ShortcutA.Key2,
113113 ' B=',dbgs(Cmd.ShortcutB.Shift1),'-',Cmd.ShortcutB.Key1,
114114 ',',dbgs(Cmd.ShortcutB.Shift2),'-',Cmd.ShortcutB.Key2
115115 );
116116 end;
117117 finally
118118 Commands.Free;
119119 end;
120120end;
121121```
122122
123123# Config files
124124
125125## Load/Save settings
126126
127127All 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:
128128
129129``` pascal
130130uses LazIDEIntf;
131131...
132132 Directory := LazarusIDE.GetPrimaryConfigPath;
133133```
134134
135135Packages can create their own xml files with:
136136
137137``` pascal
138138uses
139139 ..., LCLProc, BaseIDEIntf, LazConfigStorage;
140140
141141const
142142 Version = 1;
143143var
144144 Config: TConfigStorage;
145145 SomeValue: String;
146146 SomeInteger: Integer;
147147 SomeBoolean: Boolean;
148148 FileVersion: LongInt;
149149begin
150150 SomeValue:='Default';
151151 SomeInteger:=3;
152152 SomeBoolean:=true;
153153
154154 // save settings
155155 try
156156 Config:=GetIDEConfigStorage('mysettings.xml',false);
157157 try
158158 // store the version number so future extensions can handle old config files
159159 Config.SetDeleteValue('Path/To/The/Version',Version,0);
160160 // store string variable "SomeValue"
161161 // if SomeValue has the default value the entry is not stored,
162162 // so only the differences to the default are stored.
163163 // This way the xml is kept short and defaults may change in future.
164164 Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
165165 Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
166166 Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
167167 // there are more overloads for SetDeleteValue, find out with Ctrl+Space
168168 finally
169169 Config.Free;
170170 end;
171171
172172 except
173173 on E: Exception do begin
174174 DebugLn(['Saving mysettings.xml failed: ',E.Message]);
175175 end;
176176 end;
177177
178178 // load settings
179179 try
180180 Config:=GetIDEConfigStorage('mysettings.xml',true);
181181 try
182182 // read the version of the config
183183 FileVersion:=Config.GetValue('Path/To/The/Version',0);
184184 // read a string variable "SomeValue". If the entry does not exist, use
185185 // the Default
186186 SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
187187 SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
188188 SomeBoolean:=Config.GetValue('Path/To/Some/Boolean',true);
189189 finally
190190 Config.Free;
191191 end;
192192 except
193193 on E: Exception do begin
194194 DebugLn(['Loading mysettings.xml failed: ',E.Message]);
195195 end;
196196 end;
197197end;
198198```
199199
200200Notes:
201201
202202- 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]].
203203- GetIDEConfigStorage when loading checks if the file exists and if not it will copy the template from the secondary config directory.
204204- Since 0.9.31 you can use this function to load a file outside the primary config directory.
205205
206206# Components
207207
208208## Extending existing components
209209
210210Source: [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.
211211
212212Add the properties you need with appropriate setter and getter methods and storage fields.
213213
214214Then 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.
215215
216216When 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).
217217
218218Here is a simple example:
219219
220220``` pascal
221221unit Unit1;
222222
223223{$mode objfpc}{$H+}
224224
225225interface
226226
227227uses
228228 Classes, SysUtils, Forms, StdCtrls;
229229
230230type
231231 TEdX = class(TEdit)
232232 private
233233 FNewIntProp: integer;
234234 FNewStrProp: string;
235235 procedure SetNewIntProp(AValue: integer);
236236 procedure SetNewStrProp(AValue: string);
237237 public
238238 constructor Create(theComponent: TComponent); override;
239239 property NewIntProp: integer read FNewIntProp write SetNewIntProp;
240240 property NewStrProp: string read FNewStrProp write SetNewStrProp;
241241 end;
242242
243243 TForm1 = class(TForm)
244244 procedure FormCreate(Sender: TObject);
245245 private
246246 edX: TEdX;
247247 public
248248 end;
249249
250250var
251251 Form1: TForm1;
252252
253253implementation
254254
255255{ TForm1 }
256256
257257procedure TForm1.FormCreate(Sender: TObject);
258258begin
259259 edX:= TEdX.Create(Self);
260260 edX.Parent:= Self;
261261 edX.Caption:= IntToStr(edX.NewIntProp);
262262 Caption:= edX.NewStrProp;
263263end;
264264
265265{$R *.lfm}
266266
267267{ TEdX }
268268
269269procedure TEdX.SetNewIntProp(AValue: integer);
270270begin
271271 if FNewIntProp=AValue then Exit;
272272 FNewIntProp:=AValue;
273273 // add custom code here
274274end;
275275
276276procedure TEdX.SetNewStrProp(AValue: string);
277277begin
278278 if FNewStrProp=AValue then Exit;
279279 FNewStrProp:=AValue;
280280 // add custom code here
281281end;
282282
283283constructor TEdX.Create(theComponent: TComponent);
284284begin
285285 inherited Create(theComponent);
286286 FNewStrProp:= 'new string property value';
287287 FNewIntProp:= 99;
288288end;
289289
290290end.
291291```
292292
293293## Writing components
294294
295295*Main article: [[How To Write Lazarus Component]]*
296296
297297You can create new components via the package editor.
298298
299299Create 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**.
300300
301301### Giving a new component an icon for the component palette
302302
303303Create 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:
304304
305305``` bash
306306~/lazarus/tools/lazres mybutton.lrs tmybutton.png
307307# note: Laz trunk/1.4+? uses .res resource files instead of .lrs files:
308308~/lazarus/tools/lazres mybutton.res tmybutton.png
309309```
310310
311311This creates an pascal include file, which is used in the initialization section of mybutton.pas:
312312
313313``` pascal
314314initialization
315315 {$I mybutton.lrs}
316316 // or in newer Lazarus version - to docheck syntax
317317 {$R mybutton.res}
318318```
319319
320320Install the package.
321321
322322### Hiding a component in the component palette
323323
324324Package IDEIntf, unit componentreg:
325325
326-`IDEComponentPalette.FindComponent('TButton').Visible:=false;`
326+``` pascal
327+IDEComponentPalette.FindComponent('TButton').Visible:=false;
328+```
327329
328330## Writing component editors
329331
330332A 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.
331333
332334## Writing property editors
333335
334336Each 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.
335337
336338### Hiding a property in the Object Inspector
337339
338340There is a special property editor for hiding:
339341
340342``` pascal
341343 RegisterPropertyEditor(TypeInfo(TAnchorSide), TControl, 'AnchorSideLeft', THiddenPropertyEditor);
342344```
343345
344346### Find out what property editor is used for a property
345347
346348``` pascal
347349uses Classes, TypInfo, PropEdits;
348350var
349351 Instance: TPersistent; // your instance
350352 PropInfo: PPropInfo;
351353 EdClass: TPropertyEditorClass;
352354begin
353355 PropInfo:=GetPropInfo(Instance.ClassType,'Name'); // replace Name with the property name you are searching for
354356 GetEditorClass(PropInfo, Instance);
355357end;
356358```
357359
358360# Forms
359361
360362The 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:
361363
362364- create a design time package (or extend an existing one)
363365- your form must be in a unit of this package
364366
365367``` pascal
366368...
367369interface
368370
369371uses FormEditingIntf, Forms;
370372
371373type
372374 TYourForm = class(TForm)
373375 private
374376 FYourProperty: integer;
375377 published
376378 property YourProperty: integer read FYourProperty write FYourProperty;
377379 end;
378380
379381procedure Register;
380382
381383implementation
382384
383385procedure Register;
384386begin
385387 FormEditingHook.RegisterDesignerBaseClass(TYourForm);
386388end;
387389...
388390```
389391
390392- check the "Register" in the package editor for this unit.
391393- Install the package in the IDE
392394
393395# Designer
394396
395397## Writing a designer mediator
396398
397399The 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/.
398400
399401- 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.
400402- Open the the example project examples/designnonlcl/project/NonLCL1.lpi.
401403- 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.
402404
403405## Create a new unique component name
404406
405407``` pascal
406408uses FormEditingIntf;
407409
408410...
409411
410412NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);
411413
412414// Or if you need to create the name before creating the component:
413415
414416NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
415417// ComponentClassName will be used as prefix, without a leading T.
416418// OwnerComponent is the new owner of the new component.
417419```
418420
419421## Select a component in the designer/object inspector
420422
421423``` pascal
422424uses propedits;
423425..
424426GlobalDesignHook.SelectOnlyThis(AComponent);
425427```
426428
427429## Get unit, designer, form of a file
428430
429431``` pascal
430432uses ProjectIntf, LazIDEIntf, ComponentEditors;
431433...
432434// open file
433435if LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent])<>mrOk then exit;
434436// get file interface
435437aFile:=LazarusIDE.ActiveProject.FindFile(Filename,[]);
436438// get designer
437439aDesigner:=TComponentEditorDesigner(LazarusIDE.GetDesignerWithProjectFile(AFile,true));
438440if aDesigner=nil then
439441 exit; // this unit has no resource (form, frame, datamodule, etc.)
440442// get form
441443aForm:=aDesigner.Form;
442444```
443445
444446## Save unit, designer form
445447
446448``` pascal
447449uses LazIDEIntf;
448450...
449451if LazarusIDE.DoSaveEditorFile(Filename,[])<>mrOk then exit;
450452```
451453
452454## Save designer form as Pascal
453455
454456- Use the function **FormEditingHook.SaveComponentAsPascal** of unit *FormEditingfIntf*. See *examples/pascalstream/CopyAsPasPkg/copyaspasdemounit1.pas*.
455457
456458# Event handlers
457459
458460There are several events in the IDE, for which plugins can add their own handlers.
459461
460462## Designer events
461463
462464In 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.
463465
464466Examples:
465467
466468- Adding your handler (this is normally done in the constructor of your object):
467469
468-` GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);`
470+```text
471+ GlobalDesignHook.AddHandlerPersistentAdded(@YourOnPersistentAdded);
472+```
469473
470474- Removing your handler:
471475
472-` GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);`
476+```text
477+ GlobalDesignHook.RemoveHandlerPersistentAdded(@YourOnPersistentAdded);
478+```
473479
474480- You can remove all handlers at once. For example, it is a good idea to add this line in the destructor of your object:
475481
476-` GlobalDesignHook.RemoveAllHandlersForObject(Self);`
482+```text
483+ GlobalDesignHook.RemoveAllHandlersForObject(Self);
484+```
477485
478486The handlers of GlobalDesignHook:
479487
480488- Lookup root
481489 - ChangeLookupRoot - Called when the "LookupRoot" changed. The "LookupRoot" is the owner object of the currently selected components. Normally this is a TForm.
482490- Methods
483491 - CreateMethod
484492 - GetMethodName
485493 - GetMethods
486494 - MethodExists
487495 - RenameMethod
488496 - ShowMethod
489497 - MethodFromAncestor
490498 - ChainCall
491499- Components
492500 - GetComponent
493501 - GetComponentName
494502 - GetComponentNames
495503 - GetRootClassName
496504 - 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.
497505 - ComponentRenamed - Called when a component was renamed
498506- TPersistent objects
499507 - PersistentAdded - Called when a new TPersistent was added to the LookupRoot
500508 - PersistentDeleting - Called before a TPersistent is freed.
501509 - DeletePersistent - Called by the IDE to delete a TPersistent.
502510 - GetSelectedPersistents - Called when getting the current selection of TPersistent.
503511 - SetSelectedPersistents - Called when setting the current selection of TPersistent.
504512 - GetObject
505513 - GetObjectName
506514 - GetObjectNames
507515- Modifing
508516 - Modified
509517 - Revert
510518 - RefreshPropertyValues
511519- Selection
512520 - SetSelection
513521 - GetSelection
514522
515523### Examples for designer events
516524
517525#### Example: When dropping a new component move control to a child
518526
519527``` pascal
520528...
521529type
522530 TYourDesignerExtension = class
523531 private
524532 function AddClicked(ADesigner: TIDesigner;
525533 MouseDownComponent: TComponent; Button: TMouseButton;
526534 Shift: TShiftState; X, Y: Integer;
527535 var AComponentClass: TComponentClass;
528536 var NewParent: TComponent): boolean of object;
529537 public
530538 constructor Create;
531539 destructor Destroy; override;
532540 end;
533541
534542...
535543constructor TYourDesignerExtension.Create;
536544begin
537545 // register your handler
538546 GlobalDesignHook.AddHandlerAddClicked(@AddClicked);
539547end;
540548
541549destructor TYourDesignerExtension.Destroy;
542550begin
543551 // remove your handler:
544552 GlobalDesignHook.RemoveHandlerAddClicked(@AddClicked);
545553end;
546554
547555function TYourDesignerExtension.AddClicked(ADesigner: TIDesigner;
548556 MouseDownComponent: TComponent; Button: TMouseButton;
549557 Shift: TShiftState; X, Y: Integer;
550558 var AComponentClass: TComponentClass;
551559 var NewParent: TComponent): boolean of object;
552560begin
553561 // in this example TYourControl is a custom control with a ChildControl.
554562 // Whenever the user drops a control on the TYourControl instead of adding
555563 // the new control to the TYourControl, the new control is added as child
556564 // of ChildControl.
557565 // created
558566 if MouseDownComponent is TYourControl then
559567 NewParent:=TYourControl(MouseDownComponent).ChildControl;
560568 Result:=true;
561569end;
562570```
563571
564572### Disabling the designer mouse handler
565573
566574Normally 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:
567575
568576``` pascal
569577type
570578 TYourControl = class(TCustomControl)
571579 protected
572580 procedure CMDesignHitTest(var Message: TLMessage); message CM_DESIGNHITTEST;
573581 end;
574582...
575583procedure TYourControl.CMDesignHitTest(var Message: TLMessage);
576584var
577585 p: TSmallPoint;
578586 aShiftState: TShiftState;
579587begin
580588 aShiftState:=KeysToShiftState(PtrUInt(Message.WParam));
581589 p:=TSmallPoint(longint(Message.LParam));
582590 debugln(['TForm1.CMDesignHitTest ShiftState=',dbgs(aShiftState),' x=',p.x,' y=',p.y]);
583591 if SkipDesignerMouseHandler then
584592 Message.Result:=1; // now the designer calls the normal MouseUp, MouseMove and MouseDown methods.
585593end;
586594```
587595
588596## Getting notified when a designer form is modified
589597
590598Every designed LCL form has a Designer of type TIDesigner. The IDE creates designers of type TComponentEditorDesigner defined in the IDEIntf unit componenteditors. For example:
591599
592600``` pascal
593601procedure TYourAddOn.OnDesignerModified(Sender: TObject);
594602var
595603 IDEDesigner: TComponentEditorDesigner;
596604begin
597605 IDEDesigner:=TComponentEditorDesigner(Sender);
598606 ...
599607end;
600608
601609procedure TYourAddOn.ConnectDesignerForm(Form1: TCustomForm);
602610var
603611 IDEDesigner: TComponentEditorDesigner;
604612begin
605613 IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
606614 IDEDesigner.AddHandlerModified(@OnDesignerModified);
607615end;
608616```
609617
610618## Project events
611619
612620These events are defined in unit LazIDEIntf.
613621
614622- **LazarusIDE.AddHandlerOnProjectClose**: called before a project is closed
615623- **LazarusIDE.AddHandlerOnProjectOpened**: called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
616624- **LazarusIDE.AddHandlerOnSavingAll**: called before IDE saves everything
617625- **LazarusIDE.AddHandlerOnSavedAll**: called after IDE saved everything
618626- **LazarusIDE.AddHandlerOnProjectBuilding**: called before IDE builds the project
619627- **LazarusIDE.AddHandlerOnProjectDependenciesCompiling**: called before IDE compiles package dependencies of project
620628- **LazarusIDE.AddHandlerOnProjectDependenciesCompiled**: called after IDE compiled package dependencies of project
621629- **LazarusIDE.AddHandlerOnProjectBuildingFinished**: called after IDE built the project (successful or not) (since 1.5)
622630- **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)
623631
624632## Other IDE events
625633
626634uses LazIDEIntf;
627635
628636Call these in the Register procedure of your package:
629637
630638- **LazarusIDE.AddHandlerOnIDERestoreWindows**: called when IDE is restores its windows (before opening the first project)
631639- **LazarusIDE.AddHandlerOnIDEClose**: called when IDE is shutting down (after closequery, so no more interactivity)
632640- **LazarusIDE.AddHandlerOnQuickSyntaxCheck**: called when the menu item or the shortcut for Quick syntax check is executed
633641- **LazarusIDE.AddHandlerOnLazarusBuilding**: // called before IDE builds Lazarus IDE (since 1.5)
634642- **LazarusIDE.AddHandlerOnLazarusBuildingFinished**: // called after IDE builds Lazarus IDE (since 1.5)
635643- **LazarusIDE.AddHandlerGetFPCFrontEndParams**: // called when the IDE gets the parameters of the 'fpc' front end tool (since 1.5)
636644- **LazarusIDE.AddHandlerGetFPCFrontEndPath**: // called when the IDE gets the path of the 'fpc' front end tool (since 1.5)
637645- **LazarusIDE.AddHandlerOnUpdateIDEComponentPalette**: ?
638646- **LazarusIDE.AddHandlerOnUpdateComponentPageControl**: ?
639647- **LazarusIDE.AddHandlerOnShowDesignerFormOfSource**: // called after showing a designer form for code editor (AEditor can be nil!) (since 1.5)
640648- **LazarusIDE.AddHandlerOnShowSourceOfActiveDesignerForm**: // called after showing a code of designer form (since 1.5)
641649- **LazarusIDE.AddHandlerOnChangeToolStatus**: //called when IDEToolStatus has changed (e.g. itNone-\>itBuilder etc.) (since 1.5)
642650
643651Call these in the initialization section of one of your units:
644652
645653- **AddBootHandler**
646654 - AddBootHandler(**libhTransferMacrosCreated**,@YourProc): called after IDEMacros were created (since 1.3)
647655 - AddBootHandler(**libhEnvironmentOptionsLoaded**,@YourProc): called after environment options were loaded (since 1.3)
648656
649657# Project
650658
651659## Current Project
652660
653661The current main project can be obtained by LazarusIDE.ActiveProject. (unit LazIDEIntf)
654662
655663## All units of current project
656664
657665Note that the term "units of project" is ambiguous.
658666
659667- 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.
660668- Units from used packages are not project units.
661669
662670### All units of the project inspector
663671
664672To iterate through all pascal units listed in the project inspector you can use for example:
665673
666674``` pascal
667675uses
668676 LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
669677
670678procedure ListProjectUnits;
671679var
672680 LazProject: TLazProject;
673681 i: Integer;
674682 LazFile: TLazProjectFile;
675683begin
676684 LazProject:=LazarusIDE.ActiveProject;
677685 if LazProject<>nil then
678686 for i:=0 to LazProject.FileCount-1 do
679687 begin
680688 LazFile:=LazProject.Files[i];
681689 if LazFile.IsPartOfProject
682690 and FilenameIsPascalUnit(LazFile.Filename)
683691 then
684692 debugln(LazFile.Filename);
685693 end;
686694end;
687695```
688696
689697### All used units of project
690698
691699To 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.
692700
693701``` pascal
694702uses
695703 LazLoggerBase, LazIDEIntf, ProjectIntf;
696704
697705procedure ListProjectUnits;
698706var
699707 LazProject: TLazProject;
700708 i: Integer;
701709begin
702710 LazProject:=LazarusIDE.ActiveProject;
703711 if LazProject<>nil then
704712 begin
705713 Units:=LazarusIDE.FindUnitsOfOwner(LazProject,[fuooListed,fuooUsed]); // add fuooPackages to include units from packages
706714 try
707715 for i:=0 to Units.Count-1 do
708716 debugln('Filename=',Units[i]);
709717 finally
710718 Units.Free;
711719 end;
712720 end;
713721end;
714722```
715723
716724## The .lpr, .lpi and .lps file of a project
717725
718726``` pascal
719727uses
720728 LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
721729var
722730 LazProject: TLazProject;
723731begin
724732 LazProject:=LazarusIDE.ActiveProject;
725733 // every project has a .lpi file:
726734 DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);
727735
728736 // if the project session information is stored in a separate .lps file:
729737 if LazProject.SessionStorage<>pssNone then
730738 DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);
731739
732740 // If the project has a .lpr file it is the main source file:
733741 if (LazProject.MainFile<>nil)
734742 and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
735743 DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
736744end;
737745```
738746
739747## The executable / target file name of a project
740748
741749There is a macro \$(TargetFile), which can be used in paths and external tools. You can query the macro in code:
742750
743751``` pascal
744752uses MacroIntf;
745753
746754function MyGetProjectTargetFile: string;
747755begin
748756 Result:='$(TargetFile)';
749757 if not IDEMacros.SubstituteMacros(Result) then
750758 raise Exception.Create('unable to retrieve target file of project');
751759end;
752760```
753761
754762See here for more macros: [[IDE Macros in paths and filenames]].
755763
756764## Add your own project type
757765
758766You can add items to the 'New ...' dialog:
759767
760768- See the unit ProjectIntf of the package IDEIntf.
761769- See the [[Project Templates]]
762770
763771## Add your own file type
764772
765773You can add items to the 'New ...' dialog:
766774
767775- See the unit ProjectIntf of the package IDEIntf.
768776- Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.
769777
770778### Add a new file type
771779
772780``` pascal
773781uses ProjectIntf;
774782...
775783 { TFileDescText }
776784
777785 TFileDescMyText = class(TProjectFileDescriptor)
778786 public
779787 constructor Create; override;
780788 function GetLocalizedName: string; override;
781789 function GetLocalizedDescription: string; override;
782790 end;
783791...
784792
785793procedure Register;
786794
787795implementation
788796
789797procedure Register;
790798begin
791799 RegisterProjectFileDescriptor(TFileDescMyText.Create,FileDescGroupName);
792800end;
793801
794802{ TFileDescMyText }
795803
796804constructor TFileDescMyText.Create;
797805begin
798806 inherited Create;
799807 Name:='MyText'; // do not translate this
800808 DefaultFilename:='text.txt';
801809 AddToProject:=false;
802810end;
803811
804812function TFileDescText.GetLocalizedName: string;
805813begin
806814 Result:='My Text'; // replace this with a resourcestring
807815end;
808816
809817function TFileDescText.GetLocalizedDescription: string;
810818begin
811819 Result:='An empty text file';
812820end;
813821```
814822
815823### Add a new form type
816824
817825``` pascal
818826uses ProjectIntf;
819827
820828...
821829 TFileDescPascalUnitWithMyForm = class(TFileDescPascalUnitWithResource)
822830 public
823831 constructor Create; override;
824832 function GetInterfaceUsesSection: string; override;
825833 function GetLocalizedName: string; override;
826834 function GetLocalizedDescription: string; override;
827835 end;
828836...
829837
830838procedure Register;
831839
832840implementation
833841
834842procedure Register;
835843begin
836844 RegisterProjectFileDescriptor(TFileDescPascalUnitWithMyForm.Create,FileDescGroupName);
837845end;
838846
839847{ TFileDescPascalUnitWithMyForm }
840848
841849constructor TFileDescPascalUnitWithMyForm.Create;
842850begin
843851 inherited Create;
844852 Name:='MyForm'; // do not translate this
845853 ResourceClass:=TMyForm;
846854 UseCreateFormStatements:=true;
847855end;
848856
849857function TFileDescPascalUnitWithMyForm.GetInterfaceUsesSection: string;
850858begin
851859 Result:='Classes, SysUtils, MyWidgetSet';
852860end;
853861
854862function TFileDescPascalUnitWithForm.GetLocalizedName: string;
855863begin
856864 Result:='MyForm'; // replace this with a resourcestring
857865end;
858866
859867function TFileDescPascalUnitWithForm.GetLocalizedDescription: string;
860868begin
861869 Result:='Create a new MyForm from example package NotLCLDesigner';
862870end;
863871```
864872
865873## Add new file
866874
867875Adding a new file to the project: uses LazIDEIntf, ProjectIntf;
868876
869-`LazarusIDE.DoNewEditorFile(FileDescriptorUnit,`*`,`*`,[nfIsPartOfProject,nfOpenInEditor]);`
877+```text
878+LazarusIDE.DoNewEditorFile(FileDescriptorUnit,,,[nfIsPartOfProject,nfOpenInEditor]);
879+```
870880
871881The 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.
872882
873883You can create a new file with specific filename and custom source:
874884
875-`LazarusIDE.DoNewEditorFile(FileDescriptorUnit,NewFilename,NewSource,[nfIsPartOfProject,nfOpenInEditor]);`
885+```text
886+LazarusIDE.DoNewEditorFile(FileDescriptorUnit,NewFilename,NewSource,[nfIsPartOfProject,nfOpenInEditor]);
887+```
876888
877889Keep in mind that the file is not saved to disk. This is done when the user triggers a *Save*.
878890
879891Alternatively you can create a file via codetools or directly on disk (e.g. TFileStream) and open that in the source editor:
880892
881-` LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent]);`
893+```text
894+ LazarusIDE.DoOpenEditorFile(Filename,-1,-1,[ofAddToRecent]);
895+```
882896
883897## Store custom values in lpi/lps
884898
885899You can store your own data via name/value pairs in CustomData and CustomSessionData.
886900
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`
901+``` pascal
902+LazarusIDE.ActiveProject.CustomData['Name']:=Value; // stored in lpi - x-platform, shared data
903+LazarusIDE.ActiveProject.CustomSessionData['Name']:=Value; // stored in lps - only this machine
904+```
889905
890906### Store custom filenames in lpi/lps
891907
892908Filenames should be stored relative in the lpi and some care must be taken because platform specifics. Here is an example from the pas2jsdsgn package:
893909
894910Hook the OnLoadSaveCustomData handler:
895911
896912``` pascal
897913uses
898914 LazIDEIntf, ProjectIntf, AvgLvlTree;
899915
900916...
901917 // in registration
902918 LazarusIDE.AddHandlerOnLoadSaveCustomData(@OnLoadSaveCustomData);
903919
904920...
905921procedure TPJSController.OnLoadSaveCustomData(Sender: TObject; Load: boolean;
906922 CustomData: TStringToStringTree; PathDelimChanged: boolean);
907923var
908924 fn: String;
909925 aProject: TLazProject;
910926begin
911927 if Sender is TLazProject then
912928 begin
913929 aProject:=TLazProject(Sender);
914930 if CustomData[PJSProjectWebBrowser]='1' then
915931 begin
916932 fn:=CustomData[PJSProjectHTMLFile];
917933 if fn<>'' then
918934 begin
919935 if Load then
920936 aProject.ConvertFromLPIFilename(fn)
921937 else
922938 aProject.ConvertToLPIFilename(fn);
923939 CustomData[PJSProjectHTMLFile]:=fn;
924940 end;
925941 end;
926942 end;
927943 if PathDelimChanged then ;
928944end;
929945```
930946
931947# Packages
932948
933949You can perform various actions with [[Lazarus Packages|Packages]]:
934950
935951## Search in all packages
936952
937953Iterate all packages loaded in the IDE (since 0.9.29).
938954
939955``` pascal
940956uses PackageIntf;
941957...
942958for i:=0 to PackageEditingInterface.GetPackageCount-1 do
943959 writeln(PackageEditingInterface.GetPackages(i).Name);
944960```
945961
946962## Search a package with a name
947963
948964``` pascal
949965uses PackageIntf;
950966...
951967var
952968 Pkg: TIDEPackage;
953969begin
954970 Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
955971 if Pkg<>nil then
956972 ...
957973end;
958974```
959975
960976Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
961977
962978## Get lpk filename of an installed package
963979
964980``` pascal
965981uses PackageIntf;
966982...
967983var
968984 Pkg: TIDEPackage;
969985begin
970986 Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
971987 if Pkg<>nil then
972988 LPKFilename:=Pkg.Filename;
973989end;
974990```
975991
976992## Install packages
977993
978994**Note**: Only install packages with IDE plugins. Installing other packages can make the IDE unstable.
979995
980996``` pascal
981997uses PackageIntf, contnrs;
982998...
983999 PkgList:=TObjectList.create(true);
9841000 try
9851001 Pkg:=TLazPackageID.Create;
9861002 Pkg.Name:='Cody';
9871003 PkgList.Add(Pkg);
9881004 // check if the IDE can find the cody.lpk and all dependencies
9891005 // The IDE will prompt some warnings/confirmations if something looks strange.
9901006 if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
9911007 exit;
9921008 // in this example we have checked already, so skip the warnings
9931009 // and rebuild the IDE
9941010 if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
9951011 exit;
9961012 finally
9971013 PkgList.Free;
9981014 end;
9991015```
10001016
10011017## Open a package file (lpk)
10021018
10031019``` pascal
10041020uses PackageIntf, FileUtil;
10051021...
10061022var
10071023 pkg: TIDEPackage;
10081024begin
10091025 if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
10101026 exit;
10111027 Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
10121028 ...
10131029end;
10141030```
10151031
10161032> **Warning:** The IDE automatically closes unused packages on idle. Never keep a reference to an TIDEPackage.
10171033
10181034## Find the package(s) of a unit
10191035
10201036You 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:
10211037
10221038``` pascal
10231039uses Classes, PackageIntf, ProjectIntf;
10241040...
10251041var
10261042 Owners: TFPList;
10271043 i: Integer;
10281044 o: TObject;
10291045begin
10301046 Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
10311047 if Owners=nil then begin
10321048 // unit is not directly associated with a project/package
10331049 // maybe the unit was for some reason not added, but is reachable
10341050 // search in all unit paths of all projects/packages
10351051 // Beware: This can lead to false hits
10361052 Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
10371053 [piosfExcludeOwned,piosfIncludeSourceDirectories]);
10381054 end;
10391055 if Owners=nil then exit;
10401056 try
10411057 for i:=0 to Owners.Count-1 do begin
10421058 o:=TObject(Owners[i]);
10431059 if o is TIDEPackage then begin
10441060 writeln('Owner is package ',TIDEPackage(o).Name);
10451061 end else if o is TLazProject then begin
10461062 writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
10471063 end;
10481064 end;
10491065 finally
10501066 Owners.Free;
10511067 end;
10521068```
10531069
10541070## Find the required package(s) of a project/package
10551071
10561072You have a project or a package and you want to know which package(s) are required use this:
10571073
10581074``` pascal
10591075uses Classes, PackageIntf, ProjectIntf;
10601076...
10611077var
10621078 lPkgList: TFPList;
10631079 i: Integer;
10641080begin
10651081 PackageEditingInterface.GetRequiredPackages(LazarusIDE.ActiveProject, lPkgList, [pirNotRecursive]);
10661082 try
10671083 for i := 0 to lPkgList.Count - 1 do begin
10681084 writeln('required package is ' + TIDEPackage(lPkgList[i]).Filename);
10691085 end;
10701086 finally
10711087 lPkgList.Free;
10721088 end;
10731089...
10741090```
10751091
10761092# Windows
10771093
10781094There are basically four types of IDE windows.
10791095
10801096- the main IDE bar is the Application.MainForm. It is always present.
10811097- floating/dockable windows like the Source Editors, the Object Inspectors and Messages.
10821098- the modal forms, like the find dialog, options dialogs and questions.
10831099- hints and completion forms
10841100
10851101## Adding a new dockable IDE window
10861102
10871103What 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.
10881104
10891105### How to register a dockable IDE window
10901106
10911107Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.
10921108
10931109``` pascal
10941110uses SysUtils, IDEWindowIntf;
10951111...
10961112var MyIDEWindow: TMyIDEWindow = nil;
10971113
10981114procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
10991115begin
11001116 // check the name
11011117 if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
11021118 // create the form if not already done and disable autosizing
11031119 IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
11041120 ... init the window ...
11051121 AForm:=MyIDEWindow;
11061122end;
11071123
11081124procedure Register;
11091125begin
11101126 IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
11111127 // the default boundsrect of the form is:
11121128 // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
11131129 // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
11141130end;
11151131```
11161132
11171133### Showing an IDE window
11181134
11191135**Do not use Show**. Use:
11201136
11211137``` pascal
11221138IDEWindowCreators.ShowForm(MyIDEWindow,false);
11231139```
11241140
11251141This 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.
11261142
11271143### Notes about IDEWindowCreators and SimpleLayoutStorage
11281144
11291145The 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.
11301146
11311147The 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.
11321148
11331149# CodeTools
11341150
11351151The 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]].
11361152
11371153Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:
11381154
11391155``` pascal
11401156uses LazIDEIntf;
11411157...
11421158 // save changes in source editor to codetools
11431159 LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors
11441160```
11451161
11461162## Adding a resource directive to a file
11471163
11481164This adds a {\$R example.res} to a pascal unit:
11491165
11501166``` pascal
11511167procedure AddResourceDirectiveToPascalSource(const Filename: string);
11521168var
11531169 ExpandedFilename: String;
11541170 CodeBuf: TCodeBuffer;
11551171begin
11561172 // make sure the filename is trimmed and contains a full path
11571173 ExpandedFilename:=CleanAndExpandFilename(Filename);
11581174
11591175 // save changes in source editor to codetools
11601176 LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
11611177
11621178 // load the file
11631179 CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);
11641180
11651181 // add the resource directive
11661182 if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
11671183 LazarusIDE.DoJumpToCodeToolBossError;
11681184end;
11691185```
11701186
11711187The codetools provides also functions like FindResourceDirective and RemoveDirective.
11721188
11731189## Getting the search paths for units and include files
11741190
11751191There 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:
11761192
11771193``` pascal
11781194uses CodeToolManager;
11791195...
11801196
11811197Dir:=''; // the empty directory is for new files and has the same settings as the project directory
11821198
11831199// Getting the search paths for include files:
11841200Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);
11851201
11861202// Getting the search paths for units:
11871203// This search path is passed to the compiler.
11881204// It contains the package output directories, but not the package source directories.
11891205Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);
11901206
11911207// There can be additional unit search paths for the IDE only (not passed to the compiler)
11921208Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);
11931209
11941210// The complete search path contains also all package source paths for units:
11951211Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
11961212```
11971213
11981214# Source Editor
11991215
12001216## Active source editor
12011217
12021218``` pascal
12031219uses SrcEditorIntf;
12041220...
12051221Editor:=SourceEditorManagerIntf.ActiveEditor;
12061222if Editor=nil then exit;
12071223Filename:=Editor.FileName;
12081224ScreenPos:=Editor.CursorScreenXY; // glyph/character and line, both 1-based
12091225TextPos:=Editor.CursorTextXY; // byte and line, both 1-based
12101226```
12111227
12121228## Find source editor
12131229
12141230You can search for a disk file using the expanded filename. Keep in mind, that newly created files usually have filenames without directory.
12151231
12161232``` pascal
12171233uses SrcEditorIntf;
12181234...
12191235Editor:=SourceEditorManagerIntf.SourceEditorIntfWithFilename(aFilename);
12201236if Editor=nil then exit;
12211237Filename:=Editor.FileName; // can be different to what you searchedm e.g. due to UTF-8 and case insensitive file system
12221238```
12231239
12241240## Edit source
12251241
12261242``` pascal
12271243// You can set the whole source editor text and wipe out the undo history
12281244Editor.SourceText:='New content';
12291245
12301246// In order to keep the undo history use the Selection:
12311247Editor.BlockBegin:=Point(1,1);
12321248Editor.BlockEnd:=Point(1,Editor.LineCount+1);
12331249Editor.Selection:='New Content';
12341250```
12351251
12361252# SynEdit
12371253
12381254## Getting the settings for a TSynEdit
12391255
12401256When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:
12411257
12421258``` pascal
12431259uses SrcEditorIntf;
12441260...
12451261SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
12461262```
12471263
12481264## Getting the settings for a SynEdit highlighter
12491265
12501266When 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:
12511267
12521268``` pascal
12531269uses SrcEditorIntf;
12541270...
12551271SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
12561272```
12571273
12581274See for an example: TSQLStringsPropertyEditorDlg.Create in the unit [SQLStringsPropertyEditorDlg](http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas).
12591275
12601276# Help
12611277
12621278## Adding help for sources
12631279
12641280First create a THelpDatabase:
12651281
12661282``` pascal
12671283HelpDB := TFPDocHTMLHelpDatabase(
12681284 HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
12691285 TFPDocHTMLHelpDatabase,true));
12701286HelpDB.DefaultBaseURL := 'http://your.help.org/';
12711287
12721288FPDocNode := THelpNode.CreateURL(HelpDB,
12731289 'Package1 - A new package',
12741290 'file://index.html');
12751291HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// once as TOC
12761292DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
12771293 '*.pp;*.pas',false);// and once as normal page
12781294HelpDB.RegisterItem(DirectoryItem);
12791295```
12801296
12811297## Adding lines to the messages window
12821298
12831299``` pascal
12841300unit IDEMsgIntf, IDEExternToolIntf;
12851301...
12861302var Dir: String;
12871303begin
12881304 Dir:=GetCurrentDir;
12891305 IDEMessagesWindow.AddCustomMessage(mluProgress,'Some important message','file.pas',0,0,'My section');
12901306end;
12911307```
12921308
12931309# IDE options frame
12941310
12951311## Open options in a specific frame
12961312
12971313``` pascal
12981314uses LazIDEIntf;
12991315...
13001316LazarusIDE.DoOpenIDEOptions(TYourIDEOptionsFrame);
13011317```
13021318
13031319# Miscellaneous
13041320
13051321## Adding a macro
13061322
13071323You can add your own IDE macros:
13081324
13091325``` pascal
13101326uses MacroIntf, MacroDefIntf;
13111327
13121328procedure Register;
13131329begin
13141330 // registering a Macro with a fixed value:
13151331 IDEMacros.Add(TTransferMacro.Create('MyMacro1','Value','Description',nil,[]));
13161332end;
13171333
13181334// registering a Macro with a dynamic value requires a method of an object
13191335procedure TForm1.Init;
13201336begin
13211337 IDEMacros.Add(TTransferMacro.Create('MyMacro','Value','Description',@OnResolveMyMacro,[]));
13221338end;
13231339
13241340function TForm1.OnResolveMyMacro(const s: string; const Data: PtrInt;
13251341 var Abort: boolean): string;
13261342// s is the parameter. For example $MyMacro(parameter)
13271343// Data is the querying instance. Can be nil.
13281344// If there is an error set Abort to true.
13291345// The result string can contain macros. For example Result:='$(FPCVer)/$(TargetOS)';
13301346begin
13311347 Result:='MyValue';
13321348end;
13331349```
13341350
13351351## Query an IDE macro
13361352
13371353``` pascal
13381354uses MacroIntf;
13391355
13401356function GetCurrentFPCSourceDirectory: string;
13411357begin
13421358 Result:='$(FPCSrcDir)';
13431359 if not IDEMacros.SubstituteMacros(Result) then
13441360 raise Exception.Create('unable to retrieve FPCSrcDir');
13451361end;
13461362```
13471363
13481364## Debugging the IDE
13491365
13501366- Open a Lazarus instance (called 1st instance of Lazarus).
13511367- 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.
13521368- Open the ide\lazarus.lpi project (always with the 1st instance of Lazarus).
13531369- Compile this lazarus project (always with the 1st instance of Lazarus): another instance of Lazarus should appear (called 2nd instance of Lazarus).
13541370- 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.
13551371
13561372## Adding a parser for external tool
13571373
13581374There is an example parser for the Delphi dcc32.exe compiler in compilers/delphi/lazdelphi.lpk.
13591375
13601376It 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.
13611377
13621378After installing this package, you can call dcc32.exe in any project/package instead of fpc: In Compiler Options / Compiler Commands:
13631379
13641380- **Execute Before**: pathofdcc32.exe params...
13651381- **Parsers**: check "Delphi Compiler"
13661382- **Compiler**: Skip calling compiler
13671383
13681384## See also
13691385
13701386- [[Creating IDE Help]]
13711387- [[Help protocol]]
13721388- [[How To Write Lazarus Component]]
13731389
13741390---
13751391*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