Difference between revisions of "Extending the IDE"

From Lazarus wiki
Jump to navigationJump to search
m (Text replace - "delphi>" to "syntaxhighlight>")
m (Text replace - "Delphi>" to "syntaxhighlight>")
Line 29: Line 29:
 
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:
 
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:
  
<Delphi>function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
+
<syntaxhighlight>function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
 
                                 const Name, Caption: string;
 
                                 const Name, Caption: string;
 
                                 const OnClickMethod: TNotifyEvent = nil;
 
                                 const OnClickMethod: TNotifyEvent = nil;
Line 41: Line 41:
 
                                 const Command: TIDECommand = nil;
 
                                 const Command: TIDECommand = nil;
 
                                 const ResourceName: String = ''
 
                                 const ResourceName: String = ''
                                 ): TIDEMenuCommand; overload;</Delphi>
+
                                 ): TIDEMenuCommand; overload;</syntaxhighlight>
  
 
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.  
 
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.  
  
 
The following registers a new menu command, with the name 'MyTool', the caption 'Start my tool' and when clicked executes the procedure ''StartMyTool'':
 
The following registers a new menu command, with the name 'MyTool', the caption 'Start my tool' and when clicked executes the procedure ''StartMyTool'':
<Delphi>procedure StartMyTool;
+
<syntaxhighlight>procedure StartMyTool;
 
begin
 
begin
 
   ...executed when menu item is clicked...
 
   ...executed when menu item is clicked...
Line 54: Line 54:
 
begin
 
begin
 
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
 
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
If you want to call a method instead of a procedure use the '''OnClickMethod''' parameter.
 
If you want to call a method instead of a procedure use the '''OnClickMethod''' parameter.
Line 60: Line 60:
 
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:
 
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:
  
<Delphi>uses ... IDECommands, MenuIntf;
+
<syntaxhighlight>uses ... IDECommands, MenuIntf;
 
...
 
...
 
var
 
var
Line 72: Line 72:
 
   CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
 
   CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
 
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
 
   RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
=Shortcuts=
 
=Shortcuts=
Line 78: Line 78:
 
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:
 
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:
  
<Delphi>
+
<syntaxhighlight>
 
uses
 
uses
 
   Classes, SysUtils, LCLProc, IDECommands;
 
   Classes, SysUtils, LCLProc, IDECommands;
Line 104: Line 104:
 
   end;
 
   end;
 
end;
 
end;
</Delphi>
+
</syntaxhighlight>
  
 
=Config files=
 
=Config files=
Line 112: Line 112:
 
All configuration files of the IDE are stored in one directory as xml files. Packages can add their own files there too. The primary config directory can be read with:
 
All configuration files of the IDE are stored in one directory as xml files. Packages can add their own files there too. The primary config directory can be read with:
  
<Delphi>uses LazIDEIntf;
+
<syntaxhighlight>uses LazIDEIntf;
 
...
 
...
   Directory := LazarusIDE.GetPrimaryConfigPath;</Delphi>
+
   Directory := LazarusIDE.GetPrimaryConfigPath;</syntaxhighlight>
  
 
Packages can create their own xml files with:
 
Packages can create their own xml files with:
  
<Delphi>uses
+
<syntaxhighlight>uses
 
   ..., LCLProc, BaseIDEIntf, LazConfigStorage;
 
   ..., LCLProc, BaseIDEIntf, LazConfigStorage;
  
Line 177: Line 177:
 
     end;
 
     end;
 
   end;
 
   end;
end;</Delphi>
+
end;</syntaxhighlight>
  
 
Notes:
 
Notes:
Line 231: Line 231:
 
== Create a new unique component name ==
 
== Create a new unique component name ==
  
<Delphi>
+
<syntaxhighlight>
 
uses FormEditingIntf;
 
uses FormEditingIntf;
  
Line 243: Line 243:
 
// ComponentClassName will be used as prefix, without a leading T.
 
// ComponentClassName will be used as prefix, without a leading T.
 
// OwnerComponent is the new owner of the new component.
 
// OwnerComponent is the new owner of the new component.
</Delphi>
+
</syntaxhighlight>
  
 
== Select a component in the designer/object inspector ==
 
== Select a component in the designer/object inspector ==
  
<Delphi>uses propedits;
+
<syntaxhighlight>uses propedits;
 
..
 
..
GlobalDesignHook.SelectOnlyThis(AComponent);</Delphi>
+
GlobalDesignHook.SelectOnlyThis(AComponent);</syntaxhighlight>
  
 
= Event handlers =
 
= Event handlers =
Line 318: Line 318:
 
For example:
 
For example:
  
<Delphi>procedure TYourAddOn.OnDesignerModified(Sender: TObject);
+
<syntaxhighlight>procedure TYourAddOn.OnDesignerModified(Sender: TObject);
 
var
 
var
 
   IDEDesigner: TComponentEditorDesigner;
 
   IDEDesigner: TComponentEditorDesigner;
Line 332: Line 332:
 
   IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
 
   IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
 
   IDEDesigner.AddHandlerModified(@OnDesignerModified);
 
   IDEDesigner.AddHandlerModified(@OnDesignerModified);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
== Project events ==
 
== Project events ==
Line 362: Line 362:
 
To iterate through all pascal units of the current main project of the IDE you can use for example:
 
To iterate through all pascal units of the current main project of the IDE you can use for example:
  
<Delphi>uses  
+
<syntaxhighlight>uses  
 
   LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
 
   LCLProc, FileUtil, LazIDEIntf, ProjectIntf;
  
Line 381: Line 381:
 
         debugln(LazFile.Filename);
 
         debugln(LazFile.Filename);
 
     end;
 
     end;
end;</Delphi>
+
end;</syntaxhighlight>
  
 
==The .lpr, .lpi and .lps file of a project==
 
==The .lpr, .lpi and .lps file of a project==
  
<Delphi>uses  
+
<syntaxhighlight>uses  
 
   LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
 
   LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
 
var
 
var
Line 402: Line 402:
 
   and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
 
   and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
 
     DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
 
     DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
end;</Delphi>
+
end;</syntaxhighlight>
  
 
==The executable / target file name of a project==
 
==The executable / target file name of a project==
Line 409: Line 409:
 
You can query the macro in code:
 
You can query the macro in code:
  
<Delphi>uses MacroIntf;
+
<syntaxhighlight>uses MacroIntf;
  
 
function MyGetProjectTargetFile: string;
 
function MyGetProjectTargetFile: string;
Line 416: Line 416:
 
   if not IDEMacros.SubstituteMacros(Result) then
 
   if not IDEMacros.SubstituteMacros(Result) then
 
     raise Exception.Create('unable to retrieve target file of project');
 
     raise Exception.Create('unable to retrieve target file of project');
end;</Delphi>
+
end;</syntaxhighlight>
  
 
See here for more macros: [[IDE Macros in paths and filenames]].
 
See here for more macros: [[IDE Macros in paths and filenames]].
Line 437: Line 437:
 
===Add a new file type===
 
===Add a new file type===
  
<Delphi>uses ProjectIntf;
+
<syntaxhighlight>uses ProjectIntf;
 
...
 
...
 
   { TFileDescText }
 
   { TFileDescText }
Line 476: Line 476:
 
begin
 
begin
 
   Result:='An empty text file';
 
   Result:='An empty text file';
end;</Delphi>
+
end;</syntaxhighlight>
  
 
===Add a new form type===
 
===Add a new form type===
  
<Delphi>uses ProjectIntf;
+
<syntaxhighlight>uses ProjectIntf;
  
 
...
 
...
Line 524: Line 524:
 
begin
 
begin
 
   Result:='Create a new MyForm from example package NotLCLDesigner';
 
   Result:='Create a new MyForm from example package NotLCLDesigner';
end;</Delphi>
+
end;</syntaxhighlight>
  
 
=Packages=
 
=Packages=
Line 532: Line 532:
 
Iterate all packages loaded in the IDE (since 0.9.29).
 
Iterate all packages loaded in the IDE (since 0.9.29).
  
<Delphi>uses PackageIntf;
+
<syntaxhighlight>uses PackageIntf;
 
...
 
...
 
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
 
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
   writeln(PackageEditingInterface.GetPackages(i).Name);</Delphi>
+
   writeln(PackageEditingInterface.GetPackages(i).Name);</syntaxhighlight>
  
 
==Search a package with a name==
 
==Search a package with a name==
  
<Delphi>uses PackageIntf;
+
<syntaxhighlight>uses PackageIntf;
 
...
 
...
 
var
 
var
Line 547: Line 547:
 
   if Pkg<>nil then  
 
   if Pkg<>nil then  
 
     ...
 
     ...
end;</Delphi>
+
end;</syntaxhighlight>
  
 
Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
 
Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.
Line 555: Line 555:
 
'''Note''': Only install packages with IDE plugins. Installing other packages can make the IDE unstable.
 
'''Note''': Only install packages with IDE plugins. Installing other packages can make the IDE unstable.
  
<Delphi>
+
<syntaxhighlight>
 
uses PackageIntf, contnrs;
 
uses PackageIntf, contnrs;
 
...
 
...
Line 574: Line 574:
 
     PkgList.Free;
 
     PkgList.Free;
 
   end;
 
   end;
</Delphi>
+
</syntaxhighlight>
  
 
==Open a package file (lpk)==
 
==Open a package file (lpk)==
  
<Delphi>
+
<syntaxhighlight>
 
uses PackageIntf, FileUtil;
 
uses PackageIntf, FileUtil;
 
...
 
...
Line 589: Line 589:
 
   ...
 
   ...
 
end;
 
end;
</Delphi>
+
</syntaxhighlight>
  
 
'''Warning:''' The IDE automatically closes unused packages on idle. Never keep a reference to an TIDEPackage.
 
'''Warning:''' The IDE automatically closes unused packages on idle. Never keep a reference to an TIDEPackage.
Line 597: Line 597:
 
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:
 
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:
  
<Delphi>
+
<syntaxhighlight>
 
uses Classes, PackageIntf, ProjectIntf;
 
uses Classes, PackageIntf, ProjectIntf;
 
...
 
...
Line 627: Line 627:
 
     Owners.Free;
 
     Owners.Free;
 
   end;
 
   end;
</Delphi>
+
</syntaxhighlight>
  
 
=Windows=
 
=Windows=
Line 647: Line 647:
 
Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.
 
Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.
  
<Delphi>uses SysUtils, IDEWindowIntf;
+
<syntaxhighlight>uses SysUtils, IDEWindowIntf;
 
...
 
...
 
var MyIDEWindow: TMyIDEWindow = nil;  
 
var MyIDEWindow: TMyIDEWindow = nil;  
Line 667: Line 667:
 
   // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
 
   // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
 
   // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
 
   // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
end;</Delphi>
+
end;</syntaxhighlight>
  
 
===Showing an IDE window===
 
===Showing an IDE window===
Line 673: Line 673:
 
'''Do not use Show'''. Use:
 
'''Do not use Show'''. Use:
  
<Delphi>IDEWindowCreators.ShowForm(MyIDEWindow,false);</Delphi>
+
<syntaxhighlight>IDEWindowCreators.ShowForm(MyIDEWindow,false);</syntaxhighlight>
  
 
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.
 
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.
Line 689: Line 689:
 
Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:
 
Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:
  
<Delphi>uses LazIDEIntf;
+
<syntaxhighlight>uses LazIDEIntf;
 
...
 
...
 
   // save changes in source editor to codetools
 
   // save changes in source editor to codetools
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors</Delphi>
+
   LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors</syntaxhighlight>
  
 
==Adding a resource directive to a file==
 
==Adding a resource directive to a file==
Line 698: Line 698:
 
This adds a {$R example.res} to a pascal unit:
 
This adds a {$R example.res} to a pascal unit:
  
<Delphi>procedure AddResourceDirectiveToPascalSource(const Filename: string);
+
<syntaxhighlight>procedure AddResourceDirectiveToPascalSource(const Filename: string);
 
var
 
var
 
   ExpandedFilename: String;
 
   ExpandedFilename: String;
Line 715: Line 715:
 
   if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
 
   if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
 
     LazarusIDE.DoJumpToCodeToolBossError;
 
     LazarusIDE.DoJumpToCodeToolBossError;
end;</Delphi>
+
end;</syntaxhighlight>
  
 
The codetools provides also functions like FindResourceDirective and RemoveDirective.
 
The codetools provides also functions like FindResourceDirective and RemoveDirective.
Line 723: Line 723:
 
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:
 
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:
  
<Delphi>
+
<syntaxhighlight>
 
uses CodeToolManager;
 
uses CodeToolManager;
 
...
 
...
Line 742: Line 742:
 
// The complete search path contains also all package source paths for units:
 
// The complete search path contains also all package source paths for units:
 
Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
 
Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);
</Delphi>
+
</syntaxhighlight>
  
 
=Source Editor=
 
=Source Editor=
Line 748: Line 748:
 
==Active source editor==
 
==Active source editor==
  
<Delphi>
+
<syntaxhighlight>
 
uses SrcEditorIntf;
 
uses SrcEditorIntf;
 
...
 
...
Line 756: Line 756:
 
ScreenPos:=Editor.CursorScreenXY;
 
ScreenPos:=Editor.CursorScreenXY;
 
TextPos:=Editor.CursorTextXY;
 
TextPos:=Editor.CursorTextXY;
</Delphi>
+
</syntaxhighlight>
  
 
=SynEdit=
 
=SynEdit=
Line 764: Line 764:
 
When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:
 
When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:
  
<Delphi>
+
<syntaxhighlight>
 
uses SrcEditorIntf;
 
uses SrcEditorIntf;
 
...
 
...
 
SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
 
SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);
</Delphi>
+
</syntaxhighlight>
  
 
==Getting the settings for a SynEdit highlighter==
 
==Getting the settings for a SynEdit highlighter==
Line 774: Line 774:
 
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:
 
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:
  
<Delphi>
+
<syntaxhighlight>
 
uses SrcEditorIntf;
 
uses SrcEditorIntf;
 
...
 
...
 
SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
 
SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);
</Delphi>
+
</syntaxhighlight>
  
 
See for an example: TSQLStringsPropertyEditorDlg.Create in the unit [http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas SQLStringsPropertyEditorDlg].
 
See for an example: TSQLStringsPropertyEditorDlg.Create in the unit [http://svn.freepascal.org/svn/lazarus/trunk/components/sqldb/sqlstringspropertyeditordlg.pas SQLStringsPropertyEditorDlg].
Line 802: Line 802:
 
==Adding lines to the messages window==
 
==Adding lines to the messages window==
  
<Delphi>unit IDEMsgIntf;
+
<syntaxhighlight>unit IDEMsgIntf;
 
...
 
...
 
var Dir: String;
 
var Dir: String;
Line 810: Line 810:
 
   IDEMessagesWindow.AddMsg('unit1.pas(30,4) Error: Identifier not found "a"',Dir,0);
 
   IDEMessagesWindow.AddMsg('unit1.pas(30,4) Error: Identifier not found "a"',Dir,0);
 
   IDEMessagesWindow.EndBlock;
 
   IDEMessagesWindow.EndBlock;
end;</Delphi>
+
end;</syntaxhighlight>
  
 
==Overview of the various help items==
 
==Overview of the various help items==

Revision as of 00:47, 25 March 2012

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Extending the IDE

Overview

The Lazarus IDE supports several types of plugins:

Components
These are the items found on the Component Palette. For example, TButton (found on the Standard page) can be used to create Buttons.
Component Editors
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).
Property Editors
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.
Experts
Experts encompass all other plugin types. They might register new menu item(s), register short cuts, or extend other IDE features.


There are two possible ways to add your own plugins to the Lazarus IDE:

  1. Write a package, install it and register your plugin(s) in the 'Register' procedure of a unit.
  2. Extend the Lazarus code, and send your svn diff to the Lazarus mailing list.

Menu items

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.

Adding a menu item

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:

function RegisterIDEMenuCommand(Parent: TIDEMenuSection;
                                const Name, Caption: string;
                                const OnClickMethod: TNotifyEvent = nil;
                                const OnClickProc: TNotifyProcedure = nil;
                                const Command: TIDECommand = nil;
                                const ResourceName: String = ''
                                ): TIDEMenuCommand; overload;
function RegisterIDEMenuCommand(const Path, Name, Caption: string;
                                const OnClickMethod: TNotifyEvent = nil;
                                const OnClickProc: TNotifyProcedure = nil;
                                const Command: TIDECommand = nil;
                                const ResourceName: String = ''
                                ): TIDEMenuCommand; overload;

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.

The following registers a new menu command, with the name 'MyTool', the caption 'Start my tool' and when clicked executes the procedure StartMyTool:

procedure StartMyTool;
begin
  ...executed when menu item is clicked...
end;

procedure Register;
begin
  RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool','Start my tool',nil,@StartMyTool);
end;

If you want to call a method instead of a procedure use the OnClickMethod parameter.

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:

uses ... IDECommands, MenuIntf;
...
var
  Key: TIDEShortCut;
  Cat: TIDECommandCategory;
  CmdMyTool: TIDECommand;
begin
  // register IDE shortcut and menu item
  Key := IDEShortCut(VK_UNKNOWN,[],VK_UNKNOWN,[]);
  Cat:=IDECommandList.FindCategoryByName(CommandCategoryToolMenuName);
  CmdMyTool := RegisterIDECommand(Cat,'Start my tool', 'Starts my tool to do some stuff', Key, nil, @StartMyTool);
  RegisterIDEMenuCommand(itmSecondaryTools, 'MyTool', 'Start my tool', nil, nil, CmdMyTool);
end;

Shortcuts

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:

uses
  Classes, SysUtils, LCLProc, IDECommands;

procedure ListCtrlD;
// list all idecommands with shortcut Ctrl-D
var
  i: integer;
  Cmd: TIDECommand;
  Commands: TFPList;
begin
  Commands:=IDECommandList.FindCommandsByShortCut(IDEShortCut(VK_D,[],VK_UNKNOWN,[]));
  try
    for i:=0 to Commands.Count-1 do begin
      Cmd:=TIDECommand(Commands[i]);
      writeln('Cmd: ',Cmd.Name,
        ' A=',dbgs(Cmd.ShortcutA.Shift1),'-',Cmd.ShortcutA.Key1,
        ',',dbgs(Cmd.ShortcutA.Shift2),'-',Cmd.ShortcutA.Key2,
        ' B=',dbgs(Cmd.ShortcutB.Shift1),'-',Cmd.ShortcutB.Key1,
        ',',dbgs(Cmd.ShortcutB.Shift2),'-',Cmd.ShortcutB.Key2
        );
    end;
  finally
    Commands.Free;
  end;
end;

Config files

Load/Save settings

All configuration files of the IDE are stored in one directory as xml files. Packages can add their own files there too. The primary config directory can be read with:

uses LazIDEIntf;
...
  Directory := LazarusIDE.GetPrimaryConfigPath;

Packages can create their own xml files with:

uses
  ..., LCLProc, BaseIDEIntf, LazConfigStorage;

const
  Version = 1;
var
  Config: TConfigStorage;
  SomeValue: String;
  SomeInteger: Integer;
  SomeBoolean: Boolean;
  FileVersion: LongInt;
begin
  SomeValue:='Default';
  SomeInteger:=3;
  SomeBoolean:=true;

  // save settings
  try
    Config:=GetIDEConfigStorage('mysettings.xml',false);
    try
      // store the version number so future extensions can handle old config files
      Config.SetDeleteValue('Path/To/The/Version',Version,0);
      // store string variable "SomeValue"
      // if SomeValue has the default value the entry is not stored,
      // so only the differences to the default are stored.
      // This way the xml is kept short and defaults may change in future.
      Config.SetDeleteValue('Path/To/Some/Value',SomeValue,'Default');
      Config.SetDeleteValue('Path/To/Some/Integer',SomeInteger,3);
      Config.SetDeleteValue('Path/To/Some/Boolean',SomeBoolean,true);
      // there are more overloads for SetDeleteValue, find out with Ctrl+Space
    finally
      Config.Free;
    end;

  except
    on E: Exception do begin
      DebugLn(['Saving mysettings.xml failed: ',E.Message]);
    end;
  end;

  // load settings
  try
    Config:=GetIDEConfigStorage('mysettings.xml',true);
    try
      // read the version of the config
      FileVersion:=Config.GetValue('Path/To/The/Version',0);
      // read a string variable "SomeValue". If the entry does not exist, use
      // the Default
      SomeValue:=Config.GetValue('Path/To/Some/Value','Default');
      SomeInteger:=Config.GetValue('Path/To/Some/Integer',3);
      SomeBoolean:=Config.GetValue('Path/To/Some/Boolean',true);
    finally
      Config.Free;
    end;
  except
    on E: Exception do begin
      DebugLn(['Loading mysettings.xml failed: ',E.Message]);
    end;
  end;
end;

Notes:

  • 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.
  • GetIDEConfigStorage when loading checks if the file exists and if not it will copy the template from the secondary config directory.
  • Since 0.9.31 you can use this function to load a file outside the primary config directory.

Components

Writing components

You can create new components via the package editor. For example: 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.

Giving a new component an icon for the component palette

For example give TMyButton an icon. 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 .lrs file with the lazres tool, which can be found in the lazarus/tools directory:

 ~/lazarus/tools/lazres mybutton.lrs tmybutton.png

This creates an pascal include file, which is used in the initialization section of mybutton.pas:

 initialization
   {$I mybutton.lrs}

Install the package.

Hiding a component in the component palette

Package IDEIntf, unit componentreg:

IDEComponentPalette.FindComponent('TButton').Visible:=false;

Writing component editors

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.

Writing property editors

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.

Designer

Writing a designer mediator

The standard designer allows to visually edit LCL controls, while all others are shown as icons. To visually edit non LCL control, you can 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/.

  • 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.
  • Open the the example project examples/designnonlcl/project/NonLCL1.lpi.
  • 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.

Create a new unique component name

uses FormEditingIntf;

...

NewName:=FormEditingHook.CreateUniqueComponentName(AComponent);

// Or if you need to create the name before creating the component:

NewName:=FormEditingHook.CreateUniqueComponentName(ComponentClassName,OwnerComponent);
// ComponentClassName will be used as prefix, without a leading T.
// OwnerComponent is the new owner of the new component.

Select a component in the designer/object inspector

uses propedits;
..
GlobalDesignHook.SelectOnlyThis(AComponent);

Event handlers

There are several events in the IDE, for which plugins can add their own handlers.

Designer events

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.

Examples:

 Adding your handler (this is normally done in the constructor of your object):
   GlobalDesignHook.AddHandlerComponentAdded(@YourOnComponentAdded);
Removing your handler: GlobalDesignHook.RemoveHandlerComponentAdded(@YourOnComponentAdded);
You can remove all handlers at once. For example, it is a good idea to add this line in the destructor of object: GlobalDesignHook.RemoveAllHandlersForObject(Self);

The handlers of GlobalDesignHook:

 // lookup root
 ChangeLookupRoot
   Called when the "LookupRoot" changed.
   The "LookupRoot" is the owner object of the currently selected components.
   Normally this is a TForm.
// methods CreateMethod GetMethodName GetMethods MethodExists RenameMethod ShowMethod Called MethodFromAncestor ChainCall
// components GetComponent GetComponentName GetComponentNames GetRootClassName ComponentRenamed Called when a component was renamed ComponentAdded Called when a new component was added to the LookupRoot ComponentDeleting Called before a component is freed. DeleteComponent Called by the IDE to delete a component. GetSelectedComponents Get the current selection of components.
// persistent objects GetObject GetObjectName GetObjectNames
// modifing Modified Revert RefreshPropertyValues // Selection SetSelection GetSelection

Getting notified when a designer form is modified

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:

procedure TYourAddOn.OnDesignerModified(Sender: TObject);
var
  IDEDesigner: TComponentEditorDesigner;
begin
  IDEDesigner:=TComponentEditorDesigner(Sender);
  ...
end;

procedure TYourAddOn.ConnectDesignerForm(Form1: TCustomForm);
var
  IDEDesigner: TComponentEditorDesigner;
begin
  IDEDesigner:=TComponentEditorDesigner(Form1.Designer);
  IDEDesigner.AddHandlerModified(@OnDesignerModified);
end;

Project events

These events are defined in unit LazIDEIntf.

  • LazarusIDE.AddHandlerOnProjectClose: called before a project is closed
  • LazarusIDE.AddHandlerOnProjectOpened: called after the project was completely opened (for example all required packages were loaded, units were opened in the source editor)
  • LazarusIDE.AddHandlerOnSavingAll: called before IDE saves everything
  • LazarusIDE.AddHandlerOnSavedAll: called after IDE saved everything
  • LazarusIDE.AddHandlerOnProjectBuilding: called before IDE builds the project
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiling: called before IDE compiles package dependencies of project
  • LazarusIDE.AddHandlerOnProjectDependenciesCompiled: called after IDE compiled package dependencies of project

Other IDE events

  • LazarusIDE.AddHandlerOnIDERestoreWindows: called when IDE is restores its windows (before opening the first project)
  • LazarusIDE.AddHandlerOnIDEClose: called when IDE is shutting down (after closequery, so no more interactivity)
  • LazarusIDE.AddHandlerOnQuickSyntaxCheck: called when the menu item or the shortcut for Quick syntax check is executed

Project

Current Project

The current main project can be obtained by LazarusIDE.ActiveProject. (unit LazIDEIntf)

All units of current project

To iterate through all pascal units of the current main project of the IDE you can use for example:

uses 
  LCLProc, FileUtil, LazIDEIntf, ProjectIntf;

procedure ListProjectUnits;
var
  LazProject: TLazProject;
  i: Integer;
  LazFile: TLazProjectFile;
begin
  LazProject:=LazarusIDE.ActiveProject;
  if LazProject<>nil then
    for i:=0 to LazProject.FileCount-1 do
    begin
      LazFile:=LazProject.Files[i];
      if LazFile.IsPartOfProject
      and FilenameIsPascalUnit(LazFile.Filename)
      then
        debugln(LazFile.Filename);
    end;
end;

The .lpr, .lpi and .lps file of a project

uses 
  LCLProc, FileUtil, ProjectIntf, LazIDEIntf;
var
  LazProject: TLazProject;
begin
  LazProject:=LazarusIDE.ActiveProject;
  // every project has a .lpi file:
  DebugLn(['Project'' lpi file: ',LazProject.ProjectInfoFile]);

  // if the project session information is stored in a separate .lps file:
  if LazProject.SessionStorage<>pssNone then
    DebugLn(['Project'' lps file: ',LazProject.ProjectSessionFile]);

  // If the project has a .lpr file it is the main source file:
  if (LazProject.MainFile<>nil)
  and (CompareFileExt(LazProject.MainFile.Filename,'lpr')=0) then
    DebugLn(['Project has lpr file: ',LazProject.MainFile.Filename]);
end;

The executable / target file name of a project

There is a macro $(TargetFile), which can be used in paths and external tools. You can query the macro in code:

uses MacroIntf;

function MyGetProjectTargetFile: string;
begin
  Result:='$(TargetFile)';
  if not IDEMacros.SubstituteMacros(Result) then
    raise Exception.Create('unable to retrieve target file of project');
end;

See here for more macros: IDE Macros in paths and filenames.

Add your own project type

You can add items to the 'New ...' dialog:


Add your own file type

You can add items to the 'New ...' dialog:

  • See the unit ProjectIntf of the package IDEIntf.
  • Choose a base class TFileDescPascalUnit for normal units or TFileDescPascalUnitWithResource for a new form/datamodule.

Add a new file type

uses ProjectIntf;
...
  { TFileDescText }

  TFileDescMyText = class(TProjectFileDescriptor)
  public
    constructor Create; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
  end;
...

procedure Register;

implementation

procedure Register;
begin
  RegisterProjectFileDescriptor(TFileDescMyText.Create,FileDescGroupName);
end;

{ TFileDescMyText }

constructor TFileDescMyText.Create;
begin
  inherited Create;
  Name:='MyText'; // do not translate this
  DefaultFilename:='text.txt';
  AddToProject:=false;
end;

function TFileDescText.GetLocalizedName: string;
begin
  Result:='My Text'; // replace this with a resourcestring
end;

function TFileDescText.GetLocalizedDescription: string;
begin
  Result:='An empty text file';
end;

Add a new form type

uses ProjectIntf;

...
  TFileDescPascalUnitWithMyForm = class(TFileDescPascalUnitWithResource)
  public
    constructor Create; override;
    function GetInterfaceUsesSection: string; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
  end;
...

procedure Register;

implementation

procedure Register;
begin
  RegisterProjectFileDescriptor(TFileDescPascalUnitWithMyForm.Create,FileDescGroupName);
end;

{ TFileDescPascalUnitWithMyForm }

constructor TFileDescPascalUnitWithMyForm.Create;
begin
  inherited Create;
  Name:='MyForm'; // do not translate this
  ResourceClass:=TMyForm;
  UseCreateFormStatements:=true;
end;

function TFileDescPascalUnitWithMyForm.GetInterfaceUsesSection: string;
begin
  Result:='Classes, SysUtils, MyWidgetSet';
end;

function TFileDescPascalUnitWithForm.GetLocalizedName: string;
begin
  Result:='MyForm'; // replace this with a resourcestring
end;

function TFileDescPascalUnitWithForm.GetLocalizedDescription: string;
begin
  Result:='Create a new MyForm from example package NotLCLDesigner';
end;

Packages

Search in all packages

Iterate all packages loaded in the IDE (since 0.9.29).

uses PackageIntf;
...
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
  writeln(PackageEditingInterface.GetPackages(i).Name);

Search a package with a name

uses PackageIntf;
...
var
  Pkg: TIDEPackage;
begin
  Pkg:=PackageEditingInterface.FindPackageWithName('LCL');
  if Pkg<>nil then 
    ...
end;

Note: FindPackageWithName does not open the package editor. For that use DoOpenPackageWithName.

Install packages

Note: Only install packages with IDE plugins. Installing other packages can make the IDE unstable.

uses PackageIntf, contnrs;
...
  PkgList:=TObjectList.create(true);
  try
    Pkg:=TLazPackageID.Create;
    Pkg.Name:='Cody';
    PkgList.Add(Pkg);
    // check if the IDE can find the cody.lpk and all dependencies
    // The IDE will prompt some warnings/confirmations if something looks strange.
    if not PackageEditingInterface.CheckInstallPackageList(PkgList,[]) then
      exit;
    // in this example we have checked already, so skip the warnings
    // and rebuild the IDE
    if PackageEditingInterface.InstallPackages(PkgList,[piiifSkipChecks,piiifRebuildIDE])<>mrOK then
      exit;
  finally
    PkgList.Free;
  end;

Open a package file (lpk)

uses PackageIntf, FileUtil;
...
var
  pkg: TIDEPackage;
begin
  if PackageEditingInterface.DoOpenPackageFile(LPKFilename,[pofAddToRecent],false)<>mrOk then
    exit;
  Pkg:=PackageEditingInterface.FindPackageWithName(ExtractFilenameOnly(LPKFilename));
  ...
end;

Warning: The IDE automatically closes unused packages on idle. Never keep a reference to an TIDEPackage.

Find the package(s) of a unit

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:

uses Classes, PackageIntf, ProjectIntf;
...
var
  Owners: TFPList;
  i: Integer;
  o: TObject;
begin
  Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',[]);
  if Owners=nil then begin
    // unit is not directly associated with a project/package
    // maybe the unit was for some reason not added, but is reachable
    // search in all unit paths of all projects/packages
    // Beware: This can lead to false hits
    Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit('/full/path/of/unit.pas',
      [piosfExcludeOwned,piosfIncludeSourceDirectories]);
  end;
  if Owners=nil then exit;
  try
    for i:=0 to Owners.Count-1 do begin
      o:=TObject(Owners[i]);
      if o is TIDEPackage then begin
        writeln('Owner is package ',TIDEPackage(o).Name);
      end else if o is TLazProject then begin
        writeln('Owner is project ',TLazProject(o).ProjectInfoFile);
      end;
    end;
  finally
    Owners.Free;
  end;

Windows

There are basically four types of IDE windows.

  • the main IDE bar is the Application.MainForm. It is always present.
  • floating/dockable windows like the Source Editors, the Object Inspectors and Messages.
  • the modal forms, like the find dialog, options dialogs and questions.
  • hints and completion forms

Adding a new dockable IDE window

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.

How to register a dockable IDE window

Remember to choose a long unique name that is a valid pascal identifier. Your window can have any caption you want.

uses SysUtils, IDEWindowIntf;
...
var MyIDEWindow: TMyIDEWindow = nil; 

procedure CreateMyIDEWindow(Sender: TObject; aFormName: string; var AForm: TCustomForm; DoDisableAutoSizing: boolean);
begin
  // check the name
  if CompareText(aFormName,MyIDEWindowName)<>0 then exit;
  // create the form if not already done and disable autosizing
  IDEWindowCreators.CreateForm(MyIDEWindow,TMyIDEWindowm,DoDisableAutosizing,Application);
  ... init the window ...
  AForm:=MyIDEWindow;
end;

procedure Register;
begin
  IDEWindowCreators.Add('MyIDEWindow',@CreateMyIDEWindow,nil,'100','50%','+300','+20%');
  // the default boundsrect of the form is:
  // Left=100, Top=50% of Screen.Height, Width=300, Height=20% of Screen.Height
  // when the IDE needs an instance of this window it calls the procedure CreateMyIDEWindow.
end;

Showing an IDE window

Do not use Show. Use:

IDEWindowCreators.ShowForm(MyIDEWindow,false);

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.

Notes about IDEWindowCreators and SimpleLayoutStorage

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.

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.

CodeTools

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.

Before calling any of the CodeTools functions in the IDE you should commit the current changes of the source editor to the CodeTools buffers:

uses LazIDEIntf;
...
  // save changes in source editor to codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1); // -1: commit all source editors

Adding a resource directive to a file

This adds a {$R example.res} to a pascal unit:

procedure AddResourceDirectiveToPascalSource(const Filename: string);
var
  ExpandedFilename: String;
  CodeBuf: TCodeBuffer;
begin
  // make sure the filename is trimmed and contains a full path
  ExpandedFilename:=CleanAndExpandFilename(Filename);
  
  // save changes in source editor to codetools
  LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);

  // load the file
  CodeBuf:=CodeToolBoss.LoadFile(ExpandedFilename,true,false);

  // add the resource directive
  if not CodeToolBoss.AddResourceDirective(CodeBuf,'example.res') then
    LazarusIDE.DoJumpToCodeToolBossError;
end;

The codetools provides also functions like FindResourceDirective and RemoveDirective.

Getting the search paths for units and include files

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:

uses CodeToolManager;
...

Dir:=''; // the empty directory is for new files and has the same settings as the project directory

// Getting the search paths for include files:
Path:=CodeToolBoss.GetIncludePathForDirectory(Dir);

// Getting the search paths for units:
// This search path is passed to the compiler.
// It contains the package output directories, but not the package source directories.
Path:=CodeToolBoss.GetUnitPathForDirectory(Dir);

// There can be additional unit search paths for the IDE only (not passed to the compiler)
Path:=CodeToolBoss.GetSrcPathForDirectory(Dir);

// The complete search path contains also all package source paths for units:
Path:=CodeToolBoss.GetCompleteSrcPathForDirectory(Dir);

Source Editor

Active source editor

uses SrcEditorIntf;
...
Editor:=SourceEditorManagerIntf.ActiveEditor;
if Editor=nil then exit;
Filename:=Editor.FileName;
ScreenPos:=Editor.CursorScreenXY;
TextPos:=Editor.CursorTextXY;

SynEdit

Getting the settings for a TSynEdit

When you have a dialog using a TSynEdit and you want to use the same font and settings like the source editor use:

uses SrcEditorIntf;
...
SourceEditorManagerIntf.GetEditorControlSettings(ASynEdit);

Getting the settings for a SynEdit highlighter

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:

uses SrcEditorIntf;
...
SourceEditorManagerIntf.GetHighlighterSettings(ASynHighlighter);

See for an example: TSQLStringsPropertyEditorDlg.Create in the unit SQLStringsPropertyEditorDlg.

Help

Adding help for sources

First create a THelpDatabase:

HelpDB := TFPDocHTMLHelpDatabase(
  HelpDatabases.CreateHelpDatabase('ANameOfYourChoiceForTheDatabase',
  TFPDocHTMLHelpDatabase,true));
HelpDB.DefaultBaseURL := 'http://your.help.org/';

FPDocNode := THelpNode.CreateURL(HelpDB,
  'Package1 - A new package',
  'file://index.html');
HelpDB.TOCNode := THelpNode.Create(HelpDB,FPDocNode);// once as TOC
DirectoryItem := THelpDBISourceDirectory.Create(FPDocNode,'$(PkgDir)/lcl',
  '*.pp;*.pas',false);// and once as normal page
HelpDB.RegisterItem(DirectoryItem);

Adding lines to the messages window

unit IDEMsgIntf;
...
var Dir: String;
begin
  Dir:=GetCurrentDir;
  IDEMessagesWindow.BeginBlock;
  IDEMessagesWindow.AddMsg('unit1.pas(30,4) Error: Identifier not found "a"',Dir,0);
  IDEMessagesWindow.EndBlock;
end;

Overview of the various help items

Creating IDE Help