Using the Open Tools API in Kylix
How to extend and customize the Kylix IDE.
By Ray Lischner, Tempest Software
I have good news and bad news concerning the Open Tools API in Kylix. The good news is that the Tools API has
not changed since Delphi 5. The bad news is that the Tools API has not changed since Delphi 5. This article introduces
the Tools API -- in case it is new to some of you -- and then examines how the Tools API works in Kylix, and how it
doesn't work. I'll turn on the Fasten Seat Belts sign because we're in for a bumpy ride.
OVERVIEW OF THE TOOLS API
If you have never worked with the Open Tools API in Delphi or C++ Builder, this section briefly describes what
you've been missing. If you are already familiar with the Tools API, skip ahead to the section, Tools
API in Kylix.
The Open Tools API, or just Tools API for short, is the programming interface for extending and customizing
the IDE. Kylix lets you add menu items to the main menu bar, add buttons to tool bars, create new design windows,
and more.
The Tools API is not documented, but information abounds on the web, at the annual Borland Conference, in magazine
articles, and in newsgroups. The first place to look, though, is in the source code. The sourcetoolsapi
directory contains several files, but the important one is ToolsAPI.pas. This unit contains all the
declarations for the Tools API. You probably don't have Kylix sitting in front of you right now, but the file is
almost exactly the same as it is in Delphi 5, so if you can, take a peek at Delphi 5's ToolsAPI.pas.
The ToolsAPI unit contains a few constants, some record and related declarations, but mostly interface
declarations. The bulk of the Tools API is a set of interfaces that describe some of the capabilities of the IDE.
The interfaces come in two varieties: those implemented by Borland and those implemented by the programmer.
The first kind provide information and permit your code to perform actions in the IDE. The second kind are used
for notifications and to provide information to the IDE. You must write a class to implement the interface,
and the Tools API calls your methods at appropriate times. The following sections describe this and other details
of the Tools API in more depth.
SERVICES
Several of the interfaces are called services. These are the primary entry points for working with the Tools
API. Different kinds of services perform different kinds of actions. For example, IOTAModuleServices
(OTA stands for Open Tools API) provides services for dealing with modules--open files. IOTADebuggerServices
lets you interact with the debugger: examine processes and threads, set breakpoints, and so on.
To use a service, cast the BorlandIDEServices variable to the desired service. You can do this
inline, or save the result in a variable. Because you are working with interfaces, you don't need to concern yourself
with memory management. Kylix will automatically clean up the interface's memory when that memory is no longer
needed.
For example, you can obtain the module interface for an open file by calling the FindModule method.
With a module interface, you have full access to the files for that module. You can save them, edit them, show
them in their respective editors (source files in the source editor, form files in the form editor or as text in
the source editor), and so on.
var
Module: IOTAModule;
begin
Module := (BorlandIDEServices as IOTAModuleServices).FindModule(FileName);
Most of the services are at least partially commented in ToolsAPI.pas, so you can read more about
them there. Look for the interfaces that have Services in their names.
WIZARDS
To write an IDE extension, you usually implement one or more wizard interfaces. These interfaces inherit from
IOTAWizard. You have four choices, as described in the following table.
|
Interface
|
Description
|
IOTAWizard |
Base wizard interface; every wizard must implement this. |
IOTAMenuWizard |
Wizard adds a menu item to the Help menu. Useful for tests and prototypes. |
IOTARepositoryWizard |
Base interface for project and form wizards. You must also implement one of the following interfaces: |
IOTAFormWizard |
Wizard to create a new unit, form, or other file. |
IOTAProjectWizard |
Wizard to create a new project. |
The base interface, IOTAWizard has four methods that you must implement. Only two are important, though:
- Execute procedure: Basic wizards never call this method, only menu, form, and projects wizards do. They call
Execute
when the user invokes the wizard (by choosing the menu item, or selecting the form or project to be created).
- GetIDString function: Returns a unique identifier string. You can ensure uniqueness by using your company or organization name, and
a unique name for this wizard. By convention, these are separated by a dot.
- GetName function: Returns a user-friendly name for your wizard.
- GetState function: Only menu wizards call
GetState--to learn the state of the menu item (enabled or not, checked
or not).
Menu wizards require one additional method, GetMenuText, which is the menu item caption. The GetState
method in IOTAWizard returns the state of the menu item.
Form and project wizards require an author's name, a one sentence comment, and an icon handle, which the IDE
displays in the New Items dialog box. Choose View Details in the context menu to see examples of the comment and
author. If the page name is an empty string, the default page name is Wizards. The IDE will also choose a default
icon if your wizard returns 0 from GetGlyph.
The difference between a form and a project wizard is that the user can choose a form wizard to be the default
form when creating a new form or when creating the main form of a default project. The user can choose a project
wizard to be the default application. What the wizards actually do is up to you--you don't need to create a form
in a form wizard, but it would be confusing if you don't.
Notice that the basic wizard, one that implements only the IOTAWizard interface, has no means of
invocation. The means of invoking the wizard is entirely up to you: you can add a button to the IDE's tool bars,
add a menu item to the menu bar, create modal or modeless windows, or hide in the background. In fact, any wizard
can do any of the above.
To create a wizard class, derive a class from TNotifierObject and implement IOTAWizard
and any of the other wizard interfaces you need. For example:
type
TAutoSaveWizard = class(TNotifierObject, IOTAWizard)
public
constructor Create;
destructor Destroy; override;
// IOTAWizard
procedure Execute; // Called only for Menu, Form, and Project wizards
function GetIDString: string; // return a unique string, e.g., 'Tempest Software.My wizard name'
function GetName: string; // return a user-friendly string
function GetState: TWizardState; // state of the menu item for IOTAMenuWizard; other wizards don't use
// the state.
end;
NOTIFIERS
Another kind of interface that you must implement is a notifier. Kylix uses notifiers to inform your
wizard of an interesting event. For example, you can define an IDE notifier to learn when the user opens files,
starts compiling, and so on. You can attach a module notifier to a specific module to learn when the user edits
that module, saves the files, and so on.
Most of the notifier interfaces have names that end with Notifier. To use a notifier, you must
first write a class that implements the notifier, then you register an instance of your class with the Tools API.
When you register the notifier, the Tools API returns an integer that uniquely identifies that notifier. You can
unregister the notifier at any time by supplying the integer. You can register and unregister notifiers in any
order.
Remember that you are using interfaces, so Kylix manages the memory automatically. Once you create your notifier
object and register it, you can forget it. The object will be freed when it is no longer needed. If you try to
free it yourself, all kinds of problems will ensure, most likely ending up by crashing the IDE.
The easiest way to implement a notifier is to derive your class from TNotifierObject. It implements
the base notifier interface, IOTANotifier, with stub methods. Not all notifiers uses all four base
methods, so having the stubs is convenient. Note that the methods are not virtual. If you need to override one
of the methods, just write a method of the same name, and be sure to include IOTANotifier in the list
of interfaces that your class implements. The methods' "virtuality" are managed by the interfaces, so
it doesn't matter that the methods are not virtual in the class. The four common notifier methods are described
below.
- AfterSave procedure: Editor notifiers call this method after the user saves the associated file.
- BeforeSave procedure: Editor notifiers call this method when the user saves a file, but before the file is actually written.
- Destroyed procedure: This method is called when the associated interface is about to be destroyed, and you should unregister the
notifier. For example, if the user closes a file, the module interface will be destroyed, but first it calls the
Destroyed procedure for all module notifiers registered with that module.
- Modified procedure: Editor notifiers call this method when the user modifies the file. Note that source editor notifiers call this
method for every keystroke.
WHAT NEXT?
This brief article can only touch on the most superficial aspects of the Tools API. For a more in-depth look,
I encourage you to read ToolsAPI.pas and try some experiments on your own. Join me at the 2001
Borland Conference, where I will be speaking at more length on the Tools API. The next section discusses how
the Tools API works in Kylix.
Tools API in Kylix
Other articles on the Community web site bring you the good news about CLX, the Kylix IDE and all the wonderful
features you have available. In particular, you should read about Kylix packages. Shared objects in Linux are similar
to DLLs in Windows, but they have some differences, too. These differences might affect how you work with Tools
API packages.
The Tools API in Kylix is almost exactly the same as it is in Delphi 5. If you have a lot of existing Tools
API code, it might seem to be a blessing, but if you think about the ramifications, you might change your mind.
For example, the Tools API uses the VCL, not CLX.
"Wait a minute!" you cry. "Isn't Kylix all about cross-platform development? A brand-new component
hierarchy? What's the VCL doing on Linux?"
The Kylix IDE is largely a port of the Delphi 5 IDE from Windows to Linux. Borland did not have enough time
to rewrite the IDE from scratch in CLX, so they ported the existing VCL-based IDE. It's not running under a Windows
emulator, but there is a library that provides some of the Windows API and maps the Windows API calls to native
Linux functions and system calls. The VCL sits on top of this library. It's all part of the vcl package.
(Yes, that's vcl not vcl60. Linux has its own way to support version numbers for shared
objects, so the numbers don't have to be encoded in the file names. That means you don't need to rewrite all your
package source files for every new release of Kylix.)
Because the VCL and CLX maintain separate event queues, running in separate threads, you must not try to create
a CLX form from the VCL thread or vice versa. In other words, don't use CLX in your Tools API wizard.
You can use the VCL, but the IDE lets you edit only CLX forms. If you want to use the VCL, therefore, you can
design your forms in Delphi 5, then recompile the files in Kylix.
You will need to maintain separate package source files, though. The required packages are different for Kylix
than for Delphi for Windows, and Borland does not support conditional compilation directives in packages. (More
specifically, the IDEs don't understand conditional compilation directives, so you cannot use the package managers
on Windows or Linux.)
The designide package contains the ToolsAPI unit. You can write a wizard that uses
only CLX and the ToolsAPI unit, but you cannot create any visual forms. You might use baseclx,
for example. If you do any visual work, remember to use the VCL, not VisualCLX, in which case you need the vcl
package, too.
Register your wizard in the usual way: declare a procedure named Register (case is important!),
and have the Register procedure call RegisterPackageWizard for each wizard instance you
want to register.
EXAMPLE: SORTING TEXT
Take a look at a specific example. This wizard sorts text in the source editor. The user highlights a block
of text in the usual manner, then invokes the wizard with a keyboard shortcut or a menu item. The wizard reads
the selected text into a string list, sorts the text, and writes the sorted text back to the source editor.
The main wizard class is in the SortWiz unit. The wizard adds a menu item to the IDE's menu bar
(Edit > Sort). It also registers a key binding for Ctrl+K, S. The menu item brings up the sort parameters dialog
box, but the key binding sorts using the current parameters. I think this is a convenience for the user, but you
are free to change this if you disagree.
The SortReg unit registers the wizard. I always like to put the registration code in a separate
unit, which lets me work with the wizard and its code without stumbling over the registration code.
SortSel does the main work of sorting the selection. There are two possibilities when selecting
text: columnar selection or linear selection.
In columnar selection, the user identifies two corners of a text rectangle in the editor. Selected lines are
not contiguous. What make columnar selections tricky is the possibility of having tabs in the selection or before
the selection on one or more lines. After sorting, the wizard tries to ensure the text lines up nicely. It does
so by keeping track of the text position of the start of each line fragment. It inserts the sorted text at the
same position on each line.
In linear selection, the user picks two endpoints, and all characters in between are part of the selection.
The exact endpoints might include or exclude the character after the end of the selection. The distinction isn't
important because the wizard sorts entire lines of text, so the selection is extended to include the entire line.
After sorting, the editor buffer is modified by deleting the old text and inserting the newly sorted text. Finally,
the sorted text is selected and scrolled into view. The status bar message informs the user that the text has been
sorted. (Sometimes, the text starts out sorted, so the message tells the user that something happened, even if
the result is identical to the starting text.)
SortParms lets the user adjust some sorting parameters. If the user chooses the Edit>Sort menu
item, the sort parameters dialog box is shown. The user can sort in ascending or descending order, choose case
sensitivity, and decide whether to ignore Pascal keywords. The latter option lets the user sort methods in a class
declaration without putting all functions before all procedures. The sort parameters dialog box is shown below.

I originally wrote this wizard in Delphi for Windows, and it ported to Kylix with almost no changes. I had to
edit the package source file, but the rest of the code compiled with almost no changes.
You can download the code from CodeCentral by clicking here.
ABOUT THE AUTHOR
Ray Lischner is the author of Shakespeare
for Dummies, but don't let that fool you. His day job is teaching computer programming at Oregon
State University and writing about computers and programming. He is the author of Delphi
in a Nutshell, and other books about Delphi. His articles appear in a variety of magazines and journals,
and he speaks at user group meetings and conferences across the country.