In 1983 [1], Borland invented the Integrated Development Environment (IDE) for software developers with the introduction of Turbo Pascal. Since then, it has come a long way. The latest offering of an IDE from Borland for the Microsoft Windows operating system and the Microsoft .NET Framework is Borland Delphi 2005. Borland Delphi 2005 includes development and support for the following languages: C#, Delphi and Visual Basic .NET.
With the introduction of Borland C#Builder, Borland has rewritten their IDE into a single framework known as Borland Developer Studio (BDS). BDS is the IDE framework underpinning C#Builder, and Delphi 8.
Delphi 2005 is the successor to both C#Builder and Delphi 8. BDS provides a framework for its users to customise its IDE, known as the Open Tools (OT) Application Programming Interface (API). The Open Tools API (OTA) allows its users to extend the functionalities of the IDE.
The Open Tools Interfaces The Open Tools interfaces in Borland Developer Studio provides much functionality, allowing users to customise the menu, access the debugger, and add keyboard keystrokes, among others [2]. The following table list the interfaces provided by the Open Tools Interface, which is loosely divided into different subsystems [3].
|
Interface |
Description |
|
INTAServices |
Provides access to native IDE objects: main menu, action list, image list and tool bars |
|
IOTAAboutBoxServices |
Allows an IDE plug-in to display plug-in specific information in the about box |
|
IOTAActionServices |
Performs basic file actions: open, close, save and reload a file |
|
IOTACodeInsightServices |
Provides access to code completion, allowing wizard to install a custom code completion manager |
|
IOTADebuggerServices |
Provides access to debugger |
|
IOTADesignerCommandServices |
Provides access to commands implemented by a designer |
|
IOTAEditorServices |
Provides read-and-write access to source code editor and its internal buffers |
|
IOTAFileFilterServices |
Provides access to file filters used by the IDE |
|
IOTAHighlightServices |
Provides access to the highlight manager |
|
IOTAHistoryServices |
Provides access to IDE’s history stack |
|
IOTAKeyBindingServices |
Permits wizard to register custom keyboard bindings |
|
IOTAKeyboardDiagnostics |
Toggle debugging of keystrokes |
|
IOTAKeyboardServices |
Provides access to keyboard macros and bindings |
|
IOTAMessageServices |
Provides access to message view, allowing users to add messages and group them |
|
IOTAModuleServices |
Provides access to files currently opened in the IDE |
|
IOTAPackageServices |
Queries names of all installed packages and their components |
|
IOTAPersonalityServices |
Provides access to personalities supporting different languages implemented in the IDE |
|
IOTAServices |
Miscellaneous services |
|
IOTASplashScreenServices |
Allows IDE plug-in to provide an image to be displayed on the splash screen when the application starts up |
|
IOTAToDoServices |
Provides access to To-Do list, allowing wizard to install a custom To-Do manager |
|
IOTAWizardServices |
Registers and unregisters wizards | Table 1: Open Tools Services interface
In the next section, we will see how to create a simple OTA plug-in that intercepts a sequence of keystrokes in the code editor, and then act on it. What our simple OTA plug-in does is, on intercepting a particular sequence of keystrokes, surround a block of text with the region directive first introduced in Delphi 8.
A Simple OTA Plug-in In order to write a simple OTA plug-in, one needs to perform the following steps:
• Create a Delphi for Win32 package • Add a new Delphi for Win32 unit to the package • Add a reference to the designide.dcp compiled package • Add a reference to ToolsAPI in the uses clause • Declare and implement a public Register procedure • Obtain the Open Tools Services interface for IOTAKeyboardServices by calling the GetService method of the global variable BorlandIDEServices (which is published in ToolsAPI). • Implement a class that contains a method to handle the desired sequence of keystrokes. • Install a keyboard binding, passing an instance of the class in Step 7.

Fig. 1: Creating a Delphi for Win32 Package
Step 1: Creating a Delphi for Win32 Package So, in order to proceed, let us create a Delphi for Win32 package, as seen in Fig. 1. From the IDE main menu, choose File, New, Other, then select Package, from Delphi Projects.

Fig. 2: Adding a Delphi for Win32 unit Step 2: Adding a Delphi for Win32 Unit Then, add a new Delphi for Win32 unit (See Fig. 2) to the package to provide the implementation of the OTA plug-in. Save the project now, naming the project OTARegion, and the unit OTARegionImpl.pas.
Each unit in an OTA plug-in needs to provide a public Register procedure. The Register procedure is used to initialise one or more classes that customise the IDE. There can be one or more units in an OTA plug-in, each customising the IDE in their own particular way.
Step 3: Add a Reference to the designide.dcp Compiled Package To gain access to the internals of the BDS IDE, one needs to add a reference to the DesignIDE.dcp compiled package and then add ToolsAPI to the uses clause of the unit implementing the OTA plug-in. In order to do this, right click (See Fig. 3) in the Requires node of the Project Manager, choose Add Reference (See Fig. 4), browse to C:\Program Files\Borland\BDS\3.0\lib (assuming that you installed Delphi 2005 to C:\Program Files\Borland\BDS\3.0), and add DesignIDE.dcp (See Fig. 5).
Most of the API required to extend the IDE is declared in the ToolsAPI unit. However, there is no documentation in the Delphi 2005 product that documents the OTA, which is a serious omission. Fortunately, there is a HTML Help reference on Borland’s CodeCentral [4] website that provides some of the documentation for the Open Tools interface.

Fig. 3: Adding a reference
Steps 4 and 5: Add a Reference to ToolsAPI in the Uses Clause, and Declare and Implement a Public Register Procedure After adding the reference to the compiled package DesignIDE.dcp, add a reference to ToolsAPI in the uses clause, then declare and implement the public Register procedure. In the Register procedure, we shall obtain a reference to the IOTAKeyboardServices interface as seen in Listing 1.
Listing 1: Obtaining a Reference to the IOTAKeyboardServices Interface procedure Register; var KBS: IOTAKeyboardServices; begin BorlandIDEServices.GetService(IOTAKeyboardServices, KBS);
end;

Fig. 4: The Add Reference dialog
Step 6: Obtaining an IOTAKeyboardServices Reference The GetService method of BorlandIDEServices returns an implementation of an instance of a class that implements IOTAKeyboardServices into the variable KBS.
With IOTAKeyboardServices, we can call its method, AddKeyboardBinding. The AddKeyboardBinding method allows its users to add a partial or complete keyboard binding that completely defines what actions the code editor should take, upon encountering a user-defined sequence of keystrokes. It expects an instance of a class implementing IOTANotifier and IOTAKeyboardBinding [3].

Fig. 5: Adding a reference to a compiled package
Step 7: Implement the Class Instead of implementing IOTANotifier, we can implement a class that derives from TNotifierObject (which implements IOTANotifier), saving us the trouble of implementing IOTANotifier, and just implement IOTAKeyboardBinding (See Listing 2).
Listing 2: Declaring a Class that Implements IOTANotifier and IOTAKeyboardBinding type TRegionBinding = class(TNotifierObject, IOTAKeyboardBinding) public // Our method that declares a region procedure DeclareRegion(const Context: IOTAKeyContext; KeyCode: TShortCut; var BindingResult: TKeyBindingResult);
// IOTAKeyboardBinding methods function GetBindingType: TBindingType; function GetDisplayName: string; function GetName: string; procedure BindKeyboard(const BindingServices: IOTAKeyBindingServices); end;

Fig. 6: After adding reference, and updating the uses clause
TRegionBinding is a class that implements IOTAKeyboardBinding. For the methods contractually required to be implemented by a class implementing the IOTAKeyboardBinding interface, refer to Table 2.
|
Method |
Description |
|
BindKeyboard |
Calls the AddKeyBinding method of a class implementing IOTAKeyBindingServices to intercept a sequence of keystrokes |
|
GetBindingType |
Returns btPartial or btComplete, indicating whether this keyboard binding defines a complete set of keyboard sequences |
|
GetDisplayName |
Returns a string displayed in the Tools Options dialog, Editor Key Mappings and Enhancement modules section (see Fig. 7) |
|
GetName |
Returns a string used internally by the IDE, uniquely identifying this specific IOTAKeyboardBinding implementation | Table 2: IOTAKeyboardBinding methods and their responsibilities
Step 8: Installing the Keyboard Binding In order to fulfill the contractual responsibilities required by IOTAKeyboardBinding, we will flesh out the implementation coding for the methods presented in Table 2 (See Listing 3). In our BindKeyboard method, we call the AddKeyBinding method, passing it a sequence of keystrokes or an array of shortcuts (which in this case, is Ctrl+D), and a method to call (which is DeclareRegion), when the sequence of keystrokes is detected in the code editor.

Fig. 7: Tools Options dialog, showing the enhancement modules registered
Listing 3: Implementing the Contractual Methods Required by the IOTAKeyboardBinding Interface procedure TRegionBinding.BindKeyboard( const BindingServices: IOTAKeyBindingServices); begin BindingServices.AddKeyBinding([Shortcut(Ord('D'), [ssCtrl])], DeclareRegion, nil); end;
function TRegionBinding.GetBindingType: TBindingType; begin Result := btPartial; end;
function TRegionBinding.GetName: string; begin Result := 'chuacw.SDARegionExpert'; end;
function TRegionBinding.GetDisplayName: string; begin Result := 'Chee Wee''s Region Expert for SDA'; end;
Finally, we will implement the DeclareRegion method. When DeclareRegion is called, it is passed the following parameters:
• an instance of a class implementing the IOTAKeyContext interface, • the shortcut which invoke the method, and • a variable parameter of the type TBindingResult.
IOTAKeyContext implements a property named EditBuffer, which provides the method access to the code editor’s (well) edit buffer. The edit buffer represents a view into the currently loaded file being viewed / edited in the IDE. In turn, EditBuffer provides two properties (out of many others) named EditBlock (which contains a reference to an instance of a class implementing IOTAEditBlock) and EditPosition (which in turn contains a reference to an instance of a class implementing IOTAEditBlock).
EditBlock provides us access to the currently selected block of text in the code editor, and various other methods to manipulate it, and EditPosition provides us access to the current position of the cursor in the code editor and also various other methods to manipulate it.
With the currently selected block of text in the editor, and the position of the cursor in the editor, we can surround the selected block of text with the region directive, hence achieving the goal of implementing our simple OTA plug-in.
Listing 4 shows the implementation of the DeclareRegion method. Each line of code is preceded by its line number, so that it is easier for everyone to follow how the code is implemented. When the DeclareRegion method is called by the IDE after recognising the specific sequence of keystrokes we have instructed it to recognise, the method begins execution by first asking the user to enter a region name. If the user chooses to enter a region name, all is well, and the code following the if-then block is executed, otherwise, the method exits.
Next, we save the position of the cursor (Line 14), and set up a try-finally block so that even if the code fails to execute completely, the original position of the cursor can be restored (Line 15, 31-33). We then cut the selected text from the code editor into the Windows clipboard. Subsequently, the cursor is moved to the starting position of the selected text block (Line 17-18). The selected text is retrieved from the Windows clipboard (Line 19), and it is surrounded by the region directive (Line 21-23). The result is placed into the variable Data, and copied into the Windows clipboard (Line 26). From the cursor position, the contents of the Windows clipboard is then pasted back into the code editor.
Changing the Project Options Before installing the package, invoke the Project Options dialog (See Fig. 8) from either the Project Manager, or the main menu, and set the Description field to “OTA plug-in for BDS IDE” and the usage options to “Designtime only”. This would be reflected in the Design packages of the Default Project Options dialog, which can be invoked from the main menu’s Component, Install Packages menu item after you have installed the package.

Fig. 8: Setting the options in Project options dialog

Fig. 9: Design Packages of Default Projects
Testing the Implementation In order to test the implementation of the code presented, go to the Project Manager, and right click on the project folder (See Fig. 12). Choose Install, and once the IDE finishes installing the package, you will be presented with a dialog, informing you that the IDE has installed the package (See Fig. 13).
Listing 4: Implementation of the DeclareRegion Method 01 procedure TRegionBinding.DeclareRegion(const Context: IOTAKeyContext; 02 KeyCode: TShortCut; var BindingResult: TKeyBindingResult); 03 var 04 EditPosition: IOTAEditPosition; 05 EditBlock: IOTAEditBlock; 06 Column, Row: Integer; 07 TextBlock, Data, RegionName: string; 08 begin 09 if InputQuery(GetDisplayName, 'Please declare the region name', 10 RegionName) then 11 begin 12 EditPosition := Context.EditBuffer.EditPosition; 13 EditBlock := Context.EditBuffer.EditBlock; 14 EditPosition.Save; 15 try 16 EditBlock.Cut(False); 17 EditPosition.Move(EditBlock.StartingRow, 18 EditBlock.StartingColumn); 19 TextBlock := Clipboard.AsText; 20 Data := Format( 21 '{ ''%s''}'#13#10+ // Region Name 22 '%s'+ // block of text selected by user 23 '{}'#13#10, [RegionName, TextBlock]); 24 25 // Copy the new text into the clipboard 26 Clipboard.AsText := Data; 27 28 // Insert the clipboard text into the current editor position 29 EditPosition.Paste; 30 BindingResult := krHandled; 31 finally 32 EditPosition.Restore; 33 end; 34 end; 35 end;
After all this is implemented and installed in the IDE, open any Delphi file, and in the code editor, select a block of text, then press Ctrl+D to activate the OTA plug-in. The specific keystroke sequence is recognised by the IDE, and we’re presented with the screen as seen in Fig. 10. Once the user enters a region name, we obtain references to the currently selected text block, and cursor position in the code editor.
What you see in Fig. 11 is the end result of all that work.

Fig. 10: Activating the OTA plug-in by pressing Ctrl+D
Conclusion In this article, we have presented a simple OTA plug-in that integrates into the Delphi 2005 IDE, and when a specific sequence of keystrokes is detected, our plug-in is activated. The plug-in demonstrates how to obtain the cursor position and selected block of text in the code editor, accessing, reading and modifying the clipboard contents, and manipulating the text in the code editor.

Fig. 11: OTA plug-in has performed its work

Fig. 12: Installing the OTA plug-in

Fig. 13: The IDE information dialog after completing installing the package
Chua Chee Wee, CCNA, MCSE, SCJP, SCSA is an independent technical consultant specialising in software development, systems administration and providing technical support. He can be contacted at his blog: http://chuacw.ath.cx/chuacw/.
Links & Literature [1] Delphi history: from Pascal to Diamondback, URL: http://delphi.about.com/cs/azindex/a/dhistory.htm [2] Opening Doors: Notes on the Delphi ToolsAPI by its creator, URL: Http://community.borland.com/article/0,1410,20360,00.html [3] Gentlemen, start your engines, Opening Doors: Notes on the Delphi ToolsAPI by its creator – Part 2, URL: http://community.borland.com/article/0,1410,20419,00.html [4] C#Builder Open Tools API html help reference, URL: http://cc.borland.com/codecentral/ccWeb.exe/listing?id=20594 • Berry Erik, Erik’s Open Tools API FAQ and Resources, URL: http://www.gexperts.org/opentools/ • Berry Erik, The C#Builder Open Tools API, URL: http://bdn.borland.com/article/0,1410,30194,00.html • Kaster John, C#Builder Open Tools API references, URL: http://bdn.borland.com/article/0,1410,30363,00.html • Berry Erik, C#Builder Open Tools API html help reference, URL: http://cc.borland.com/codecentral/ccWeb.exe/listing?id=20594 • Bauer Allen, Opening Doors: Notes On the Delphi ToolsAPI by its Creator, URL: http://community.borland.com/article/0,1410,20360,00.html • Bauer Allen, Opening Doors: Notes On the Delphi ToolsAPI by its Creator - Part 2, URL: http://community.borland.com/article/0,1410,20419,00.html
|