Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

The Delphi Open Tools API


Dr. Dobb's Journal February 1998: The Delphi Open Tools API

Who needs another scripting language?

Ray is the author of Hidden Paths of Delphi 3 (Informant Press, 1997), a reference and programmer's guide to Delphi's Open Tools API. He can be contacted at [email protected].


Borland's Delphi is a Windows development tool with a GUI-centric IDE that lets you create and modify windows and dialog boxes (often known as "forms"), visually position and resize controls on forms, and set the attributes and event handlers of forms and their controls. In addition, Delphi includes the Open Tools API -- a set of classes for extending and customizing Delphi's IDE. What this means is that, in effect, you can use Delphi as a scripting language to extend itself. It also means that you can use your favorite components to write Delphi extensions without having to learn yet another scripting language.

In Delphi, you work visually with a form by selecting components from a toolbar and dropping them on the form. Once on the form, you can drag the components to reposition or resize them. The object inspector displays the attributes (called "properties") of the form and its components, so you can view and edit them. For example, you can attach a pop-up menu to a control by setting the control's PopupMenu property to the name of a TPopupMenu component. Delphi's IDE synchronizes changes between the form editor, object inspector, and source-code editor. For example, when you add a component to a form, the object inspector displays that component's properties, and Delphi updates the source code to add a declaration for the new component. Figure 1 shows Delphi's IDE and form editor, source-code editor, and object inspector.

The Open Tools API lets you write Delphi extensions, often called "experts," that interface with the form editor, object inspector, source-code editor, and Delphi's main menu. For example, the form interface class TIFormInterface, lets your expert access the form editor -- bring the window forward, add a component to the form, or search for a component by name. Each component has a component interface: an instance of TIComponentInterface. The component interface gives your expert access to the properties of the component: its size, position, caption, and so on. Using the component interface, you can also delete the component or select it in the object inspector.

Other interfaces in the Open Tools API let your expert read and write the sourcecode editor's buffers for the files that the user is editing. Your expert can read code, modify it, scroll a source window, and select text or move the editor's cursor position. Using these interfaces, you can extend Delphi's source editing capabilities.

Your expert can also add files to a project, create new projects, and add resources such as bitmaps and icons to a project's resource file. An expert can add menu items to Delphi's menu bar, open modal or modeless windows to display information about a project, the user's environment, or anything else that strikes your fancy. An expert can respond to changes to a project; for example, when users open/close a file, save a file, edit a form or source file, and so on.

The Open Tools API consists of about 25 classes in seven modules (called "units"). Table 1 summarizes most of the capabilities of Open Tools and lists the relevant interface classes. In this article, I'll concentrate on TIEditorInterface and related classes. (Class and component names in Delphi start with "T" for Type. Classes in the Open Tools API are interfaces between Delphi's IDE and your expert, so their names start with "TI.")

Sorting Text

One of the common reasons for using the Open Tools API is to supply a feature missing from Delphi. Say, for example, you have a growing list of variable declarations in a class or procedure. To help you manage the declarations, you decide to sort the names alphabetically. The only hitch is that Delphi's source-code editor lacks a sort function. Luckily, you can use the Open Tools API to implement a "sort expert."

The basic approach for the sort expert is simple:

  1. Determine the current file.
  2. Determine the start and end of the source editor's selected text.
  3. Obtain the actual text in that selection.
  4. Sort the text.
  5. Edit the source buffer to replace the current selection with the newly sorted text.
  6. Select the newly inserted text.

Listing One is the Sort unit, which implements the main logic of the sort expert. The complete source code and related files are available electronically; see "Resource Center," page 3.

File Information

The first step is easy. Open Tools declares a global object, ToolServices, which provides information about the current project, the environment, and similar information. Its GetCurrentFile method returns the full path to the file that the user is currently editing. GetModuleInterface returns an instance of TIModuleInterface for that file.

A module interface is how Open Tools represents a source or form file. You can use a module interface to save a modified file, close the file, show the file in its editor (source files are shown in the source editor, and form files are shown in the form editor). Most important, the module interface is the gateway to the advanced interfaces in the Open Tools API. Thus, the sort expert uses the module interface to obtain an editor interface (by calling GetEditorInterface).

If a file is not open in any editor, Delphi returns a nil value for the module interface. If the file is not a source file open in the source editor, Delphi returns a nil value for the editor interface. In either case, the sort expert has nothing to sort, so it returns immediately.

View to a Thrill

At this point, the sort expert encounters an obstacle. The user might have multiple views open on the current file. Delphi lets you open any number of source-editor windows, and you can open the same file in different windows. The editor interface can tell you whether the user has multiple views open on a file, but it has no interface for telling you which view is active. This is important because the sort expert will select the sorted text and scroll the view to ensure that the sorted text is visible. You don't want to scroll the wrong view. The "solution" (that is, work-around) is to limit the user to a single view. This is hardly an ideal user interface, but you must live within the constraints of the Open Tools API.

A view is an instance of TIEditView. With a view, you can examine or change the current cursor position, scroll the view, or determine the view size.

Playing with Blocks

As with any other text editor, Delphi's source editor lets you select a region of text. This region is called a "block." The editor interface has properties that return the starting/ending position of the block. Every file has its own block selection, but each file has one block, regardless of how many views are open on that file.

The block limits are of type TCharPos, which is a record that contains a line number and a character index. The character index is a 0-based index of the character position within the line. Line numbers are 1-based. The sort expert sorts entire lines, so it forces the character index of the block limits to be 0. If the end position is at the start of a line, the expert interprets that as the end of the previous line; otherwise, it increments the line number and forces the character index to 0. The net effect is selecting all of the lines that contain the start and end of the block selection, including the carriage-return/line-feed characters that end each line.

One of the difficulties in working with the editor interfaces is that they have three different ways to specify a character position: An editor position is relative to the user's view. Positions are column positions in the editor after expanding tab characters. A character position is relative to the source editor's internal structures. Character positions do not account for expanded tabs, so a tab character always counts for a single position. A buffer position is an integer offset into a file or stream. The Open Tools API provides several functions that convert between the various kinds of positions. The immediate task is to convert the character positions for the block limits to buffer positions.

Reading and Writing

Now that the sort expert knows the limits of the block selection as buffer positions, it can retrieve the text in the selection. An edit reader gets a buffer of text from any position in the source editor's buffer. Note that the edit reader is not reading the file on disk, but is reading Delphi's internal buffers, with all of the user's unsaved modifications. The sort expert creates a string to hold the text, then calls the edit reader's GetText method to retrieve the text.

The easiest way to sort lines of text is to use a TStringList. Assigning to the string list's Text property breaks the text into separate lines. The Sort method sorts those lines, and reading the Text property reads the lines as a single string.

The sort expert creates an edit writer to write the newly sorted text into the editor's buffer. An edit writer copies text from the old buffer to a new one. The expert can choose to copy text, delete text, or insert text. Once written to the new buffer, the edit writer cannot go backwards, so the expert must be prepared. In this case, the sort expert has an easy job: It copies text up to the start of the block, deletes the block, and inserts the sorted replacement. The edit writer automatically copies the remainder of the buffer.

Note that the sort expert calls CreateUndoableWriter. The other choice is to call CreateWriter, which is not undoable. In almost all cases, you will want to call CreateUndoableWriter. This preserves the editor's undo stack, and adds the expert's changes to that stack. This gives users the option to undo the sort operation.

Showing Off

The final step -- displaying the newly sorted text to users -- has three steps:

  1. Setting the cursor.
  2. Scrolling the view.
  3. Selecting the sorted text.

To set the cursor to the start of the sorted text, the sort expert converts the block-start position from a character position to an editor position. The expert sets the CursorPos property to move the text cursor (or insertion point).

The next step is to scroll the view so the sorted text is visible. To keep this example simple, the sort expert just checks whether the start of the sorted text is visible. If not, it scrolls the view to bring the start line to the top of the window. If you wanted to be fancier, you might also test the number of selected lines, and if that is smaller than the window size, you might scroll the view to center the selection in the window. Between the TopPos and ViewSize properties, you should be able to achieve any effect you think is best.

Finally, the sort expert selects the newly inserted text. The replacement text is the same size as the original text, and it takes up the same number of lines, so it reuses the BlockStart and BlockAfter positions. To set the block selection, start by setting BlockVisible to False. That hides the expert's machinations until the expert is finished. The BlockType is btNonInclusive, which is the default block type. btNonInclusive means the selection does not include the BlockAfter position, but ends on the character immediately before that position. Finally, the expert sets BlockVisible to True, so the user can see the selection.

Installing the Expert

At this point, the hard part has been completed. Dealing with Delphi's editor interfaces reveals a couple of tricky points, but most of the logic is straightforward. The only step remaining is to tell Delphi about all your hard work. The Sort unit in Listing One is all the code you need to write. Use a toolkit to take care of the remaining details. If you are curious about expert interfaces, read Delphi's Sources\Toolsapi\ExptIntf.pas file.

The full source code that accompanies this article (available electronically) includes the SortExpt.dpr project, which compiles to SortExpt.dll. To link the project, you will need the Expert Tool Kit (also available electronically).

To install the sort expert, create an entry in the Windows registry, under the key HKEY_CURRENT_USER\Software\Borland\ Delphi\3.0\Experts. The entry name is a unique name for your expert, and the entry value is a string, the full path to your expert's DLL. When Delphi starts, it loads all the expert DLLs listed in the registry, calls the initialization function for each DLL, and that function creates and registers all the experts in the DLL. The sort expert adds the Edit|Sort menu item to Delphi's menu bar. Figure 2 shows this new menu item and Delphi's source editor. Figure 3 shows the result after the sort expert has done its work.

Conclusion

Delphi is unusual among Windows GUI tools because it is its own extension language. The sort expert is an example of an expert that you can write to fill a need. It is just one example, though. When you use any tool long enough, you will inevitably run into situations where the tool lacks a feature you need. In Delphi, you can usually implement the feature yourself. Chances are that others also miss that feature, and you might even find yourself with a new product to sell.

DDJ

Listing One

unit Sort;{ Sort selected lines of text in Delphi's source code editor.
  Copyright (c) 1997 Tempest Software, Inc.
}
interface
uses Windows, SysUtils, Classes, Graphics, Controls, Dialogs, Forms,
  ExptIntf, ToolIntf, EditIntf, Etk;
type
  TSortExpert = class(TEtkModule)
    EditSort: TEtkMenuItem;
    procedure EditSortClick(Sender: TObject);
  end;
var
  SortExpert: TSortExpert;
procedure Register;
implementation
{$R *.DFM}
// Sort the selection in the current file.
procedure SortSelection;
var
  Module: TIModuleInterface;
  Editor: TIEditorInterface;
  View: TIEditView;
  Reader: TIEditReader;
  Writer: TIEditWriter;
  BlockStart: TCharPos;
  BlockAfter: TCharPos;
  StartPos, EndPos: LongInt;
  TopPos, CursorPos: TEditPos;
  Text: string;
  Strings: TStringList;
begin
  // Get the module interface for the current file.
  with ToolServices do
    Module := GetModuleInterface(GetCurrentFile);
  // If no file is open, Module is nil.
  if Module = nil then
    Exit;
  try
    // Get the interface to the source editor.
    Editor := Module.GetEditorInterface;
    // If the file is not a source file, Editor is nil.
    if Editor = nil then
      Exit;
    // The expert cannot tell which view is active, so force
    // the user to have only one view at a time.
    if Editor.GetViewCount > 1 then
      raise Exception.Create('Close all views but one');
    try
      // Get the limits of the selected text.
      BlockStart := Editor.BlockStart;
      BlockAfter := Editor.BlockAfter;
      // Sort entire lines, so modify the positions accordingly.
      BlockStart.CharIndex := 0;   // start of line
      if BlockAfter.CharIndex > 0 then
      begin
        // Select the entire line by setting the After position
        // to the start of the next line.
        BlockAfter.CharIndex := 0;
        Inc(BlockAfter.Line);
      end;
      View := Editor.GetView(0);
      Assert(View <> nil);
      try
        // Convert the character positions to buffer positions.
        StartPos := View.CharPosToPos(BlockStart);
        EndPos   := View.CharPosToPos(BlockAfter);
        // Get the selected text.
        Reader := Editor.CreateReader;
        try
          Assert(Reader <> nil);
          SetLength(Text, EndPos - StartPos - 1);
          Reader.GetText(StartPos, PChar(Text), Length(Text));
        finally
          Reader.Free;
        end;
        // Sort the text. Use a TStringList because it's easy.
        Strings := TStringList.Create;
        try
          Strings.Text := Text;
          Strings.Sort;
          Text := Strings.Text;
        finally
          Strings.Free;
        end;
        // Replace the selection with the sorted text.
        Writer := Editor.CreateUndoableWriter;
        try
          Writer.CopyTo(StartPos);
          Writer.DeleteTo(EndPos);
          Writer.Insert(PChar(Text));
        finally
          Writer.Free;
        end;
        // Set the cursor to the start of the sorted text.
        View.ConvertPos(False, CursorPos, BlockStart);
        View.CursorPos := CursorPos;
        // Make sure the top of the sorted text is visible.
        // Scroll the edit window if ncessary.
        if (BlockStart.Line < View.TopPos.Line) or
        (BlockAfter.Line >= View.TopPos.Line + View.ViewSize.CY) then
        begin
          View.ConvertPos(False, TopPos, BlockStart);
          View.TopPos := TopPos;
        end;
      finally
        View.Free;
      end;
        // Select the newly inserted, sorted text.
        Editor.BlockVisible := False;
        Editor.BlockType    := btNonInclusive;
        Editor.BlockStart   := BlockStart;
        Editor.BlockAfter   := BlockAfter;
        Editor.BlockVisible := True;
    finally
      Editor.Free;
    end;
    // Bring the focus back to the source editor window.
    Module.ShowSource;
  finally
    Module.Free;
  end;
end;
// OnClick handler for the Edit|Sort menu item.
procedure TSortExpert.EditSortClick(Sender: TObject);
begin
  Screen.Cursor := crHourglass;
  try
    SortSelection;
  finally
    Screen.Cursor := crDefault;
  end;
end;
// You can also install this unit in a package.
// The Register procedure creates and register the expert.
procedure Register;
begin
  TSortExpert.CreateAndRegister(SortExpert);
end;
end.

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.