. Updated Daily. Editions SDA India   SDA Indonesia
BUSINESS ENTERPRISE SOLUTIONS ARCHITECTURE INFORMATION SECURITY WIRELESS & MOBILITY DATA & STORAGE DEVELOPMENT HARDWARE













Online Articles

 

Introduction to Borland Developer Studio


By Chua Chee Wee

 

In this article, Chee Wee presents a sample on how to hook into Borland Delphi's 2005 Open Tools API and modify the code editor's contents to introduce a region.

 

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

 

 
print save email comment

print

save

email

comment

 
 

Search SDA Asia

Free eNewsletter

Copyright @ 2008 SDA Asia Magazine - All Right Reserved Privacy Policy | Terms of Use