Notify me of everything… – Part 1

By | November 7, 2016

Overview

I was going to blog about Unit Testing non-public members of classes using inheritance, class helpers and test interfaces but the more I refactored my piece of legacy code into smaller classes and interfaces the less valid my methods seemed to become. I may still write something about this but for now here's something different.

In this article I'm going to go though creating and handling notifiers in the IDE. Since there are a lot of notifiers in the IDE this is going to be the first part of a multi-part article covering all the notifiers. I've started with the easier notifiers to implement, i.e. those that can be created with calls to services implemented directly by the BorlandIDEServices global variable.

Interfaces implemented by BorlandIDEServices

If you didn't already know, interfaces in the ToolsAPI.pas file with the pattern IOTAXxxxxServices are implemented directly by the BorlandIDEServices global variable. However I always check while I'm creating the coding that this is indeed the case by using the following snippet of code:

CodeSite.Send(
  'BorlandIDEServices Supports IOTAToDoServices = ',
  Supports(BorlandIDEServices, IOTAToDoServices, S)
);

Where S is defined as an IInterface variable. Now I know the above uses CodeSite to output a message but you could use OutputDebugString to get the same information in the IDE's event log however I would strongly suggest you investigate CodeSite. I believe CodeSite Express is available in GetIt free. Anyway the above outputs a message with the given text followed by True or False depending upon whether the interface is supported.

This technique is sometimes required to get interfaces from other interface variables that don't directly expose the interface (for instance to get a project group interface you have to query a module interface like this AModule.QueryInterface(IOTAProjectGroup, AProjectGroup)).

Common Notifier Code

To save me some time outputting information from the notifiers I've created a common notifier object which is used (through inheritance) in all the other notifiers so that the ability to output information is present in each notifier.

Types

I've done this by firstly defining an enumerate for each type of notifier I'm coding as follows:

Type
  TDGHIDENotification = (
    dinWizard,
    dinMenuWizard,
    dinIDENotifier,
    dinVersionControlNotifier,
    dinCompileNotifier,
    dinMessageNotifier,
    dinIDEInsightNotifier,
    dinProjectFileStorageNotifier,
    dinEditorNotifier,
    dinDebuggerNotifier
  );

I've also added some persistence to the notifier output log so I also needed a set based on the above as follows:

Type
  TDGHIDENotifications = Set Of TDGHIDENotification;

The above then allows me to create a couple of constant arrays of information which will be used in the interface. The reasons I do this is that I think it makes the code cleaner but also when I add a new enumerate for a new notifier the compiler always errors on the constant arrays until I've provided the information for the new notifier. So below are a constant array for colours associated with each notifier (in buttons on a toolbar in the notifier log) and a constant array for strings to describe each notifier (again used on the toolbar as hints).

Constants

Const
  iNotificationColours: Array [Low(TDGHIDENotification) .. High(TDGHIDENotification)] Of
    TColor = (
    clTeal,
    clAqua,
    clMaroon,
    clRed,
    clNavy,
    clBlue,
    clOlive,
    clYellow,
    clGreen,
    //clLime // not used as its the BitMap mask colour
    clPurple
  );

  strBoolean: Array [Low(False) .. High(True)] Of String = ('False', 'True');

  strNotificationLabel: Array [Low(TDGHIDENotification) .. High(TDGHIDENotification)] Of
    String = (
    'Wizard Notifications',
    'Menu Wizard Notifications',
    'IDE Notifications',
    'Version Control Notifications',
    'Compile Notifications',
    'Message Notifications',
    'IDE Insight Notifications',
    'Project File Storage Notifications',
    'Editor Notifications',
    'Debugger Notifications'
  );

So now I can define the common notifier object as follows:

TDGHNotifierObject

Type
  TDGHNotifierObject = Class(TNotifierObject, IOTANotifier)
    Strict Private
      FNotification : TDGHIDENotification;
      FNotifier     : String;
    Strict Protected
      Procedure DoNotification(strMessage: String);
    Public
      Constructor Create(strNotifier : String; iNotification : TDGHIDENotification);
      // IOTANotifier
      Procedure AfterSave;
      Procedure BeforeSave;
      Procedure Destroyed;
      Procedure Modified;
      Procedure AfterConstruction; Override;
      Procedure BeforeDestruction; Override;
    End;

This class derives from TInterfacedObject which should be the root class all notfifiers should be derived from as this provides automatic reference counting for your notifier and I also implement the basic notifier IOTANotifier.

This class defines 2 fields: one for the enumerate associated with the type if notifier being implemented and which is used later on to filter messages of different types along with colouring an icon associated with the message in the listbox (see the image of the notifier log view below). The other field is a text string for the notifier type which is output in the notifier log.

I&#39ve then added a method called DoNotification which takes an enumerate and a message. This is the common code that allows all my notifiers to output a message to the notifier log.

Finally I implement a constructor to initialise the fields and methods which must be implemented for the IOTANotifier interface. I won't go into all their implementations as you will be able to look at the code but they simply output a notification to the log using the DoNotification method however I will describe the DoNotification method.

Procedure TDGHNotifierObject.DoNotification(strMessage: String);

Begin
  TfrmDockableIDENotifications.AddNotification(
    FNotification,
    FNotifier + strMessage
    );
End;

There isn't much to this other than it calls a class method of the notifier log dockable form and passes the notifier enumerate along with the notifier string (from the constructor) along with the message.

IOTANotifier

Before we move on to the notifiers I should just go through the implementation of the IOTANotifer interface which is defined as below:

Type
  IOTANotifier = interface(IUnknown)
    ['{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}']
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
  end;

The above interface has the following methods which need to be implemented.

Procedure AfterSave

If appropriate in the context of the notifier this method is called when the item is saved however this is not called for IOTAWizards.

Procedure BeforeSave

Again as above, if appropriate in the context of the notifier this method is called immediately before the item is saved however this is also not called for IOTAWizards.

Procedure Destroyed

This method is called to signify that this object is being destroyed and therefore any reference should be released. Exceptions are ignored.

Procedure Modified

This item is called to signify that the underlying object that notifier is associated with has been modified in some way. This is not called for IOTAWizards.

In the rest of this article you will only find the code for the declarations of the notifiers and the code to create them and remove them. Each notifier's methods simply call the DoNotification inherited method passing any parameters as a formatted string. All this can be found in the source code.

Dockable Notification Log

Below is an image of the notification log with some output from my IDE. To display the dockable window select the IDE Notification menu item from under the Help menu (or Help Wizards sub-menu in later IDEs)

IDE Notifications

I'm not going to go into the code behind it (it's not that complicated) as the dockable form information has already been written about in the The Delphi Open Tools API Book. The rest of the code is straight forward. The messages are stored in a string list with their enumerate type stored in the string list's Object property. These messages are then output to the list box if they match the current message filter (you can select specific message types to be displayed [each toolbar button in the window is a toggle] but they are ALL logged in the background). You can also switch on and off the logging of mesages with the Play button and clear the log with the adjacent button. Finally, on closing the IDE all the messages are saved to a log file in the same directly as the add-in DLL just in case you want to look at that information later on however the log is not reloaded on IDE start up.

IOTAServices.AddNotifier(IOTAIDENotifier)

This notifier provides methods that are fired before and after a compilation and a file notification method which tells you when files are opened, closed, desktops are loaded, etc. I use this notifier in my Integrated Testing Help IDE add-in to run command-line applications before and / or after the compilation of a project.

Notifier Definition

There are a number of IDE notifier interfaces which need to be implemented depending upon which version of RAD Studio you are using. Note: You must implement them all in later versions not just the new interfaces. Below is the definition of the notifier class:

Type
  TDGHNotificationsIDENotifier = Class(TDGHNotifierObject, IOTAIDENotifier,
    IOTAIDENotifier50, IOTAIDENotifier80)
  Strict Private
  Strict Protected
  Public
    // IOTAIDENotifier
    Procedure FileNotification(NotifyCode: TOTAFileNotification;
      Const FileName: String; Var Cancel: Boolean);
    Procedure BeforeCompile(Const Project: IOTAProject; Var Cancel: Boolean); Overload;
    Procedure AfterCompile(Succeeded: Boolean); Overload;
    // IOTAIDENotifier50
    Procedure BeforeCompile(Const Project: IOTAProject; IsCodeInsight: Boolean;
      Var Cancel: Boolean); Overload;
    Procedure AfterCompile(Succeeded: Boolean; IsCodeInsight: Boolean); Overload;
    // IOTAIDENotifier80
    Procedure AfterCompile(Const Project: IOTAProject; Succeeded:
      Boolean; IsCodeInsight: Boolean); Overload;
  End;

The implemented method are described further on.

Creating and freeing the notifier

To create the above notifier we need to use the following code which creates an instance of the notifier and then passes it to the IOTAServices.AddNotifier method which returns an integer reference which is needed to remove the notifier later on. This as you will see is a common pattern for the creation of notifiers in the IDE.

FIDENotifier := (BorlandIDEServices As IOTAServices).AddNotifier(
    TDGHNotificationsIDENotifier.Create('IOTAIDENotifier', dinIDENotifier));

To remove the notifier from the IDE we use the returned integer from the creation process above and pass this to the IOTAServices.RemoveNotifier method as below:

If FIDENotifier > -1 Then
    (BorlandIDEServices As IOTAServices).RemoveNotifier(FIDENotifier);

IOTAIDENotifier (predates RAD Studio Delphi 5)

procedure FileNotification(NotifyCode: TOTAFileNotification; const FileName: string; var Cancel: Boolean)

This method is called for various file operations within the IDE. When the method is called you receive the filename and you can optionally change the Cancel parameter to true to stop the operation from succeeding. The file operations which you get notifications for are as follows:

Operation Description
ofnFileOpening A file is about to open.
ofnFileOpened A file has opened.
ofnFileClosing A file is about to close.
ofnDefaultDesktopLoad The IDE's default desktop is about to load.
ofnDefaultDesktopSave The IDE's default desktop is about to be saved.
ofnProjectDesktopLoad The project's desktop is about to load.
ofnProjectDesktopSave The project's default desktop is about to be saved.
ofnPackageInstalled A package has just been installed into the IDE.
ofnPackageUninstalled A package has just been uninstalled from the IDE.
ofnActiveProjectChanged The active project has changed.
ofnProjectOpenedFromTemplate The project has been opened from a template.

procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean); overload

This method is called immediately before the compiler is invoked. You can set Cancel to True to cancel the compile or leaves as False to allow the compilation to go ahead. So for instance I will run command line applications at this point in my Integrated Testing Help and I will change Cancel to True if any of the command line applications return error levels above zero.

procedure AfterCompile(Succeeded: Boolean); overload

This method is called immediately following a compile. The parameter Succeeded will be True if the compile was successful. You will note that there is no var Cancel parameter here so for me if a post compilation process fails (zipping files or otherwise) I will call Abort.

IOTAIDENotifier50 (RAD Studio Delphi 5)

procedure BeforeCompile(const Project: IOTAProject; IsCodeInsight: Boolean;
var Cancel: Boolean); overload

This method is the same as BeforeCompile in IOTAIDENotifier except that it indicates if the compiler was invoked due to a CodeInsight compile.

procedure AfterCompile(Succeeded: Boolean; IsCodeInsight: Boolean); overload

This method is the same as AfterCompile in IOTAIDENotifier except indicates if the compiler was invoked due to a CodeInsight compile.

IOTAIDENotifier80 (RAD Studio Delphi 8)

procedure AfterCompile(const Project: IOTAProject; Succeeded: Boolean; IsCodeInsight: Boolean); overload

This method is the same as AfterCompile in IOTAIDENotifier except that it provide a reference to the project being compiled. The comment in the ToolsAPI.pas indicates that this was an oversight and it should have done this from the start.

IOTAVersionControlServices.AddNotifier(IOTAVersionControlNotifier)

This notifier provide you with a way to integrate your own version control system into the IDE by providing you with methods that are called at different points in time so that you can load and save them, etc.

Notifier Definition

There are a couple of notifier interfaces that need to be implemented depending upon your version of RAD Studio as below:

Type
  TDGHIDENotificationsVersionControlNotifier = Class(TDGHNotifierObject,
    IOTAVersionControlNotifier {$IFDEF DXE00}, IOTAVersionControlNotifier150 {$ENDIF})
  Strict Private
  Strict Protected
  Public
    // IOTAVersionControlNotifier
    Function AddNewProject(Const Project: IOTAProject): Boolean;
    Function GetDisplayName: String;
    Function IsFileManaged(Const Project: IOTAProject; Const IdentList: TStrings)
      : Boolean;
    Procedure ProjectManagerMenu(Const Project: IOTAProject; Const IdentList: TStrings;
      Const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean);
    {$IFDEF DXE00}
    // IOTAVersionControlNotifier150
    Function CheckoutProject(Var ProjectName: String): Boolean;
    Function CheckoutProjectWithConnection(Var ProjectName: String;
      Const Connection: String): Boolean;
    Function GetName: String;
    {$ENDIF}
  End;

Creating and freeing the notifier

Below we create the notifier and then pass that object to the IOTAVersionControlServices.AddNotifier method and store the returned integer for later removal of the notifier.

FVersionControlNotifier := (BorlandIDEServices As IOTAVersionControlServices).AddNotifier(
    TDGHIDENotificationsVersionControlNotifier.Create('IOTAVersionControlNotifier',
    dinVersionControlNotifier));

To remove the notifier we call IOTAVersionControlServices.RemoveNotifier passing the integer we got from the AddNotifier method earlier.

If FVersionControlNotifier > -1 Then
    (BorlandIDEServices As IOTAVersionControlServices).RemoveNotifier(FVersionControlNotifier);

IOTAVersionControlNotifier (RAD Studio 2009)

function GetDisplayName: string

This method should return the name of the version control system you are implemented so that when there are more than one control system loaded the IDE can identify them separately.

function IsFileManaged(const Project: IOTAProject; const IdentList: TStrings): Boolean

This method should return True if the selected file(s) are managed by your version control system. The IdentList string list contains one or more fully qualified filename for the selected file(s) in the Project Manager. If you select a node like Contains rather than a file then the string list will contain the node name. Hopefully its obvious that the Project parameter is an interface reference to the IOTAProject the file(s) belong to.

procedure ProjectManagerMenu(const Project: IOTAProject; const IdentList: TStrings; const ProjectManagerMenuList: IInterfaceList; IsMultiSelect: Boolean)

This method is called when the Project Manager is creating its local menu. The version control system you implement may want to add menu items at this point to the ProjectManagerMenuList for actions you want the user to be able to do. Project as above is the IOTAProject the files belong to and IsMultiSelect is True if more than one file is selected. I assume from the previous method that IdentList behaves then same as before.

function AddNewProject(const Project: IOTAProject): Boolean;

This method is called when the user selects a menu item to add a new project to the version control system. Project is the project in the IDE wanting to be added. I assume that you should return True if you've added the project to your version control system.

IOTAVersionControlNotifier150 (RAD Studio XE)

function CheckoutProject(var ProjectName: string): Boolean

This method is called whe the user wants to retrieve a project from the version control repository. You should return True to get the IDE to attempt to open the project with the name ProjectName.

function CheckoutProjectWithConnection(var ProjectName: string; const Connection: string): Boolean

This is similar to CheckoutProject but is called when the user wants to checkout a project using a specific connection string, for instance when called from a web page.

function GetName: string

This method should return a unique name for the version control system which will be used by the IDE to identify the version control system.

IOTACompileServices.AddNotifier(IOTACompileNotifier)

I think this acts as a supplementary notifier to the IOTAIDENofifier by providing a little more information on the start of a compile operation (which could be on more than one project) and at the end along with information on compiling the project group.

Notifier Definition

Below is the definition of the notifier. In this instance there is only one interface to implement.

Type
  TDGHIDENotificationsCompileNotifier = Class(TDGHNotifierObject, IOTACompileNotifier)
  Strict Private
  Strict Protected
  Public
    Procedure ProjectCompileFinished(Const Project: IOTAProject;
      Result: TOTACompileResult);
    Procedure ProjectCompileStarted(Const Project: IOTAProject; Mode: TOTACompileMode);
    Procedure ProjectGroupCompileFinished(Result: TOTACompileResult);
    Procedure ProjectGroupCompileStarted(Mode: TOTACompileMode);
  End;

Creating and freeing the notifier

This notifier is added to the IDE using the IOTACompileServices.AddNotifier method where we pass it an instance of our notifier implementation and store the returned integer for removal later on.

FCompileNotifier := (BorlandIDEServices As IOTACompileServices).AddNotifier(
    TDGHIDENotificationsCompileNotifier.Create('IOTACompileNotifier', dinCompileNotifier));

To remove the notifier from the IDE we call the IOTACompileServices.RemoveNotifier method passing the integer we stored from the notifiers creation.

If FCompileNotifier > -1 Then
    (BorlandIDEServices As IOTACompileServices).RemoveNotifier(FCompileNotifier);

IOTACompileNotifier (RAD Studio 2010)

Procedure ProjectCompileFinished(Const Project: IOTAProject; Result: TOTACompileResult)

This method is called once the compilation of a project is completed for each project. Project is the project that has been compiled and Result is an enumerate that tells you if the compilation was successful (crOTASucceeded), whether it failed (crOTAFailed), or if the compilation was in the background (crOTABackground).

Procedure ProjectCompileStarted(Const Project: IOTAProject; Mode: TOTACompileMode)

This method is called at the start of the compilation of each project. Project is the project to be compiled and Mode is the type of compilation being undertaken as per the table below:

Mode Description
cmOTAMake This is a normal F9 compilation (Normal make).
cmOTABuild This builds all modules that have source.
cmOTACheck This is a normal make without final link.
cmOTAMakeUnit This is only valid on an IOTAModule and in C++Builder

Procedure ProjectGroupCompileFinished(Result: TOTACompileResult)

This method is called at the completion of the compilation for a group of projects (main project and dependencies not the RAD Studio project group). Result contains an enumerate (as above) which tells you whether the compilation succeeded, failed or was in the background.

Procedure ProjectGroupCompileStarted(Mode: TOTACompileMode)

This method is called before any projects are compiled at the start of the compilation process and Mode is an enumerate (as above) which tells you the type of compilation.

It should be noted that this notifier is ONLY called for F9 style compilations, i.e. it is not called for Code Insight compilations.

IOTAIDEInsightService.AddNotifier(IOTAIDEInsightNotifier)

This notifier is called then IDE Insight is invoked.

Notifier Definition

There is only one interface to implement for this notifier as shown below:

Type
  TDGHIDENotificationsIDEInsightNotifier = Class(TDGHNotifierObject,
    IOTAIDEInsightNotifier {$IFDEF DXE00}, IOTAIDEInsightNotifier150 {$ENDIF})
  Strict Private
  Strict Protected
  Public
    // IOTAIDEInsightNotifier
    Procedure RequestingItems(IDEInsightService: IOTAIDEInsightService;
      Context: IInterface);
    {$IFDEF DXE00}
    // IOTAIDEInsightNotifier150
    Procedure ReleaseItems(Context: IInterface);
    {$ENDIF}
  End;

Creating and freeing the notifier

Below we create the notifier and then pass that object to the IOTAIDEInsightServices.AddNotifier method and store the returned integer for later removal of the notifier.

FIDEInsightNotifier := (BorlandIDEServices As IOTAIDEInsightService).AddNotifier(
    TDGHIDENotificationsIDEInsightNotifier.Create('IOTAIDEInsightNotifier',
    dinIDEInsightNotifier));

To remove the notifier we call IOTAIDEInsightServices.RemoveNotifier passing the integer we got from the AddNotifier method earlier.

If FIDEInsightNotifier > -1 Then
    (BorlandIDEServices As IOTAIDEInsightService).RemoveNotifier(FIDEInsightNotifier);

IOTAIDEInsightNotifier (RAD Studio 2010)

procedure RequestingItems(IDEInsightService: IOTAIDEInsightService; Context: IInterface)

This method is called when the IDE Insight dialogue is being invoked and is requesting items for the list. You can use the provided IDEInsightService reference at this point to add items with categories to the list of items to be displayed. Note that the Context parameter is currently always null and reserved for furture use.

IOTAIDEInsightNotifier150 (RAD Studio XE)

procedure ReleaseItems(Context: IInterface)

This method is called once IDE Insight is finished (I'm assuming that this means the dialogue / dropdown windows has closed. This should be used to to clean up any code setup in the RequestingItems method. I would also assume as per the above method that Context is null and reserved for future use.

IOTAMessageServices60.AddNotifier(IOTAMessageNotifier)

This notifier provides information on IDE messages.

Notifier Definition

This notifier implements 2 interfaces: the first provides a client with notifications when message groups are created and deleted; and the second provides the ability to add menu items to the message view context menu.

Type
  TDGHIDENotificationsMessageNotifier = Class(TDGHNotifierObject, IOTAMessageNotifier, INTAMessagNotifier)
  Strict Private
  Strict Protected
  Public
    // IOTAMessageNotifier
    Procedure MessageGroupAdded(Const Group: IOTAMessageGroup);
    Procedure MessageGroupDeleted(Const Group: IOTAMessageGroup);
    // INTAMessageNotifier
    Procedure MessageViewMenuShown(Menu: TPopupMenu; const MessageGroup: IOTAMessageGroup;
      LineRef: Pointer);
  End;

Creating and freeing the notifier

This notifier is added to the IDE using the IOTAMessageServices.AddNotifier method where we pass it an instance of our notifier implementation and store the returned integer for removal later on.

FMessageNotfier := (BorlandIDEServices As IOTAMessageServices).AddNotifier(
    TDGHIDENotificationsMessageNotifier.Create('IOTAMessageNotifier', dinMessageNotifier));

To remove the notifier from the IDE we call the IOTAMessageServices.RemoveNotifier method passing the integer we stored from the notifiers creation.

If FMessageNotfier > -1 Then
    (BorlandIDEServices As IOTAMessageServices).RemoveNotifier(FMessageNotfier);

IOTAMessageNotifier (RAD Studio Delphi 7)

This notifier should be implemented if you want to receive notifications about message groups. I'm not too sure how useful this it but I'm sure someone out there will have an idea. The comments in the ToolsAPI.pas file state that this interface current does not call BeforeSave, AfterSave, Destroy and Modified however they may be called in future releases.

procedure MessageGroupAdded(const Group: IOTAMessageGroup)

This method is called when a new message group is added to the message view.

procedure MessageGroupDeleted(const Group: IOTAMessageGroup)

This method is called when a message group is deleted from the message view.

INTAMessageNotifier (RAD Studio 2006)

procedure MessageViewMenuShown(Menu: TPopupMenu; const MessageGroup: IOTAMessageGroup; LineRef: Pointer)

This method is called when the message view context menu is displayed on the specified message group. Menu is the PopupMenu, MessageGroup is the message group and LineRef is an opaque pointer indicating the focused message. This pointer can be queried for an IOTACustomMessage interface if you have added a custom message in your message group.

IOTAProjectFileStorage.AddNotifier(IOTAProjectFileStorageNotifier)

This interface provides notifications when a project or project group is loaded, being created, saving and closing so that you can add your own custom information to the project's XML file.

Notifier Definition

Type
  TDGHNotificationsProjectFileStorageNotifier = Class(TDGHNotifierObject,
    IOTANotifier, IOTAProjectFileStorageNotifier)
  Strict Private
  Strict Protected
  Public
    Procedure CreatingProject(Const ProjectOrGroup: IOTAModule);
    Function GetName: String;
    Procedure ProjectClosing(Const ProjectOrGroup: IOTAModule);
    Procedure ProjectLoaded(Const ProjectOrGroup: IOTAModule; Const Node: IXMLNode);
    Procedure ProjectSaving(Const ProjectOrGroup: IOTAModule; Const Node: IXMLNode);
  End;

Creating and freeing the notifier

This notifier is added to the IDE using the IOTAProjectFileStorage.AddNotifier method where we pass it an instance of our notifier implementation and store the returned integer for removal later on.

FProjectFileStorageNotifier := (BorlandIDEServices As IOTAProjectFileStorage).AddNotifier(
    TDGHNotificationsProjectFileStorageNotifier.Create('IOTAProjectFileStorageNotifier',
    dinProjectFileStorageNotifier));

To remove the notifier from the IDE we call the IOTAProjectFileStorage.RemoveNotifier method passing the integer we stored from the notifiers creation.

If FProjectFileStorageNotifier > -1 Then
    (BorlandIDEServices As IOTAProjectFileStorage).RemoveNotifier(FProjectFileStorageNotifier);

IOTAProjectFileStorageNotifier (RAD Studio 2006)

function GetName: string

This method should returns the name of the node in the project file under which you want to load and saving custom information.

procedure ProjectLoaded(const ProjectOrGroup: IOTAModule; const Node: IXMLNode)

This method is called when a project is being loaded and there is a node in the projects XML that matches the result of GetName method you've implemented above. The comments in the interface code state that you may keep a reference to Node and edit the contents of the XML but you must free the reference when ProjectClosing is called (see below). ProjectOrGroup is the IOTAModule interface to the project or group begin loaded and Node is a reference to the XML code in the project / group file.

procedure CreatingProject(const ProjectOrGroup: IOTAModule)

This method is called when a new project or group is being created. ProjectOrGroup is an IOTAModule reference to the new project or group.

procedure ProjectSaving(const ProjectOrGroup: IOTAModule; const Node: IXMLNode)

This method is called when the project is being saved so you can save your custom XML information. ProjectOrGroup is the IOTAModule interface to the project or group being loaded and Node is a reference to the XML code in the project / group file.

procedure ProjectClosing(const ProjectOrGroup: IOTAModule)

This method is called when the project or group is closing. You should free any references to the Node you may have saved. ProjectOrGroup is an IOTAModule reference to the new project or group.

IOTAEditorServices.AddNotifier(IOTAEditorNotifier, INTAEditServicesNotifier)

This interface allows you to get notifications about events that happen within the editors. I've blogged about this interface before in Chapter 8: Editor Notifiers however I'll include the same information here for completeness.

Notifier Definition

There are 2 interface to implement in this notifier: IOTAEditorNotifier, a standard OTA interface and INTAEditServicesNotifier a native interface which provides direct access to parts of the IDE.

Type
  TDGHNotificationsEditorNotifier = Class(TDGHNotifierObject, IOTANotifier,
    IOTAEditorNotifier,
    INTAEditServicesNotifier)
  Strict Private
  Strict Protected
  Public
    // IOTAEditorNotifier
    Procedure ViewActivated(Const View: IOTAEditView);
    Procedure ViewNotification(Const View: IOTAEditView; Operation: TOperation);
    // INTAEditorServicesNotifier
    Procedure DockFormRefresh(Const EditWindow: INTAEditWindow; DockForm: TDockableForm);
    Procedure DockFormUpdated(Const EditWindow: INTAEditWindow; DockForm: TDockableForm);
    Procedure DockFormVisibleChanged(Const EditWindow: INTAEditWindow;
      DockForm: TDockableForm);
    Procedure EditorViewActivated(Const EditWindow: INTAEditWindow;
      Const EditView: IOTAEditView);
    Procedure EditorViewModified(Const EditWindow: INTAEditWindow;
      Const EditView: IOTAEditView);
    Procedure WindowActivated(Const EditWindow: INTAEditWindow);
    Procedure WindowCommand(Const EditWindow: INTAEditWindow; Command: Integer;
      Param: Integer;
      Var Handled: Boolean);
    Procedure WindowNotification(Const EditWindow: INTAEditWindow; Operation: TOperation);
    Procedure WindowShow(Const EditWindow: INTAEditWindow; Show: Boolean;
      LoadedFromDesktop: Boolean);
  End;

Creating and freeing the notifier

This notifier is added to the IDE using the IOTAEditorServices.AddNotifier method where we pass it an instance of our notifier implementation and store the returned integer for removal later on.

FEditorNotifier := (BorlandIDEServices As IOTAEditorServices).AddNotifier(
    TDGHNotificationsEditorNotifier.Create('INTAEditorServicesNotifier', dinEditorNotifier)
    );

To remove the notifier from the IDE we call the IOTAEditorServices.RemoveNotifier method passing the integer we stored from the notifiers creation.

If FEditorNotifier > -1 Then
    (BorlandIDEServices As IOTAEditorServices).RemoveNotifier(FEditorNotifier);

IOTAEditorNotifier (RAD Studio Delphi 5)

ViewNotification(const View: IOTAEditView; Operation: TOperation)

This method is called when ever an editor view is created or when destroyed. The View parameter contains a reference to the IOTAEditView interface so you can process some part of that editor view. Operation contains opInsert if the view has been created or opRemove is it has been destroyed.

procedure ViewActivated(const View: IOTAEditView)

This method is called when a view is activated, i.e. when the editor displays the page.

INTAEditServicesNotifier (RAD Studio 2006)

procedure WindowShow(const EditWindow: INTAEditWindow; Show, LoadedFromDesktop: Boolean)

This method is fired each time an editor window appears or disappear. The EditWindow parameter references the editor changing appearance with the Show parameter defining whether it is appearing (True) or disappearing (False). The LoadFromDesktop parameter defines whether the operation is being caused by a desktop layout being loaded.

procedure WindowNotification(const EditWindow: INTAEditWindow; Operation: TOperation)

This method is fired for each editor window that is opened or closed. The EditWindow parameter is a reference to the specific editor window opening or closing and the Operation parameter depicts whether the editor is opening (opInsert) or closing (opRemove).

procedure WindowActivated(const EditWindow: INTAEditWindow)

I've been unable to get this to fire in both a docked layout and a classic undocked layout, so if someone else knows what fires this, please let me know. EditWindow is a reference to an Native editor interface.

procedure WindowCommand(const EditWindow: INTAEditWindow; Command, Param: Integer; var Handled: Boolean)

This method is fired for editor keyboard commands. The Command parameter is the command number and in all my tests the Param parameter was 0. I’ve check against keyboard binding and have found that this event is not fired for OTA keyboard binding.

procedure EditorViewActivated(const EditWindow: INTAEditWindow; const EditView: IOTAEditView)

This method is fired each time a tab is changed in the editor whether that's through opening and closing files or simply changing tabs to view a different file. The EditWindow parameter provides access to the editor window. This is usually the first docked editor window unless you’ve opened a new editor window to have a second one visible. The EditView parameter provides you with access to the view of the file where you can get information about the cursor positions, the selected block, etc. By drilling down through the Buffer property you can get the text associated with the file.

procedure EditorViewModified(const EditWindow: INTAEditWindow; const EditView: IOTAEditView)

This method is fired each time the text of the file is changed whether that is an insertion, change or a deletion of text. The values returned by the parameters are the same as those for the above EditorViewActivated method.

procedure DockFormVisibleChanged(const EditWindow: INTAEditWindow; DockForm: TDockableForm)

This method seems to be fired when desktops are loaded and not as I thought when dockable forms change their visibility. The EditWindow is the edit window that the docking operation is be docked to (its a dock site) and DockForm is the form that is being docked.

procedure DockFormUpdated(const EditWindow: INTAEditWindow; DockForm: TDockableForm)

This event seems to be fired when a dockable form is docked with an Edit Window dock site. The parameters are the same as those for the above DockFormVisibleChanged.

procedure DockFormRefresh(const EditWindow: INTAEditWindow; DockForm: TDockableForm)

This method seems to be fired when the IDE is closing down and the desktop is being save. I’ve not been able to get the event to fire for any other situations. The parameters are the same as those for the above DockFormVisibleChanged

IOTADebuggerServices60.AddNotifier(IOTADebuggerNotifier, IOTADebuggerNotifier90, IOTADebuggerNotifier100, IOTADebuggerNotifier110)

This interface provides you with information about break points both at design time and during debugging.

Notifier Definition

There are quite a few interfaces to be implemented here that have been introduced into RAD Studio over the years that provide you with notifications for when break points are added, changed or removed but also when debugging processes are created and destroyed.

Type
  TDGHNotificationsDebuggerNotifier = Class(TDGHNotifierObject, IOTANotifier,
    IOTADebuggerNotifier, IOTADebuggerNotifier90, IOTADebuggerNotifier100,
    IOTADebuggerNotifier110)
  Strict Private
  Strict Protected
  Public
    // IOTADebuggerNotifier
    Procedure ProcessCreated(Const Process: IOTAProcess);
    Procedure ProcessDestroyed(Const Process: IOTAProcess);
    Procedure BreakpointAdded(Const Breakpoint: IOTABreakpoint);
    Procedure BreakpointDeleted(Const Breakpoint: IOTABreakpoint);
    // IOTADebuggerNotifier90
    Procedure BreakpointChanged(Const Breakpoint: IOTABreakpoint);
    Procedure CurrentProcessChanged(Const Process: IOTAProcess);
    Procedure ProcessStateChanged(Const Process: IOTAProcess);
    Function  BeforeProgramLaunch(Const Project: IOTAProject): Boolean;
    Procedure ProcessMemoryChanged; Overload;
    // IOTADebuggerNotifier100
    Procedure DebuggerOptionsChanged;
    // IOTADebuggerNotifier110
    Procedure ProcessMemoryChanged(EIPChanged: Boolean); Overload;
  End;

Creating and freeing the notifier

This notifier is added to the IDE using the IOTADebuggerServices.AddNotifier method where we pass it an instance of our notifier implementation and store the returned integer for removal later on.

FDebuggerNotifier := (BorlandIDEServices As IOTADebuggerServices).AddNotifier(
    TDGHNotificationsDebuggerNotifier.Create('IOTADebufferNotifier', dinDebuggerNotifier));

To remove the notifier from the IDE we call the IOTADebuggerServices.RemoveNotifier method passing the integer we stored from the notifiers creation.

If FDebuggerNotifier > -1 Then
    (BorlandIDEServices As IOTADebuggerServices).RemoveNotifier(FDebuggerNotifier);

IOTADebuggerNotifier (before RAD Studio Delphi 5)

procedure ProcessCreated(const Process: IOTAProcess)

This method is called when a process is created during debugging, i.e. your application is launched. The parameter Process provides you with access to the process that has been created.

procedure ProcessDestroyed(const Process: IOTAProcess)

This method is called when a process is Destroyed. The parameter Process provides you with access to the process that has been destroyed.

procedure BreakpointAdded(const Breakpoint: IOTABreakpoint)

This method is called when a break point is added. The parameter Breakpoint provides you with access to the newly created break point's properties.

procedure BreakpointDeleted(const Breakpoint: IOTABreakpoint)

This method is called when a break point is deleted. The parameter Breakpoint provides you with access to the deleted break point's properties.

IOTADebuggerNotifier90 (RAD Studio 2005)

procedure BreakpointChanged(const Breakpoint: IOTABreakpoint)

This method is called after an existing break point has changed. The parameter Breakpoint provides you access to the changed break point's properties.

procedure CurrentProcessChanged(const Process: IOTAProcess)

This method is called after the current process has changed. The parameter Process provides you access to the process that has changed.

procedure ProcessStateChanged(const Process: IOTAProcess)

This method is called after a process state has changed. The parameter Process provides you access to the process that has changed.

function BeforeProgramLaunch(const Project: IOTAProject): Boolean

This method is called before a debugger launches a projects process. This method is called regardless as to whether or not the Integrated Debugging is enabled. The Result is whether the program should actually be launched. In all normal circumstances, you should return True.

procedure ProcessMemoryChanged

This method is called when memory within a process has changed in response to a user action. The events that can trigger this notifier include:

  1. The user changing a variable value in the Evaluator / Inspector / Etc.;
  2. The user changes raw data in the CPU views dump pane, register pane or flags pane.

IOTADebuggerNotifier100 (RAD Studio 2006)

procedure DebuggerOptionsChanged

This method is called when the global (global in this sense means those debugger options which are not specific to the process, think main IDE Options) debugger-specific options have been changed.

IOTADebuggerNotifier110 (RAD Studio 2007)

procedure ProcessMemoryChanged(EIPChanged: Boolean)

This method is called when a process's memory has changed. The parameter EIPChange refers to whether Execution Instruction Point has changed.

DLLs and Source Code

I've created a page for this add-in so you can download the DLLs I've built and the source code from the IDE Notifications page.

What's Next…

Well there's plenty more notifiers to incorporate into this add-in but for now I think I'll do something different next time and revisit my IDE Help Helper add-in and provide the options to allow you to view the browser help in a full page editor window (like a unit's code is displayed in).