Extending the Project Manager Context menu

By: Jeremy North

Abstract: Shows how you can add extra context menu items to the project manager context menu by registering a class that implements the INTAProjectMenuCreatorNotifier interface

Overview

This is the first article in hopefully a series about the Open Tools API (OTAPI). While the goal is to explain some of the more useful and interesting APIs in the Open Tools I believe it is also important to complete each article with a meaningful example (with full source code) that is useful.

While the first article only touches one particular area of functionality, others are likely to be more complex and use a number of features of the API.

About this article

This first article is about implementing the INTAProjectMenuCreatorNotifier interface. This interface allows you to add context menu items to the Project Manager that then enables you to perform specific actions depending on the content of the selected node in the Project Manager.

Getting started

When deciding to create an IDE expert using the OTAPI you need to determine what format your expert to be in. There are two possible IDE experts, one is to create a Package and another is to create a DLL. While the preference for deployed IDE experts is to be DLL ones, while still developing the preference is generally to use packages. For me this is because you can Install/Uninstall it within the IDE without needing to shutdown and restart the IDE.

Expert Installation

IDE experts must be installed into the IDE. The installation method depends on what kind of expert it is.

  1. Package

Package experts are installed to the HKCU\Borland\BDS\4.0\Known Packages key in the registry. Install a package expert by using the Install context menu item on the project node in the project manager, or use the Component | Install Packages menu item and select the compiled package file.

A Package experts registry entry needs the Name value be the full path to the experts filename and the Data value is the experts description.

For Package experts to install correctly include a public method called Register. Note this method is CASE sensitive.

  1. IDE Package

IDE Package experts install to the HKCU\Borland\BDS\4.0\Known IDE Packages registry key.

Like the Package expert, the registry entries Name value is the full path to the IDE Package expert and the Data value is the description for the expert.

For IDE Package experts to install correctly include a public method called IDERegister.

  1. DLL

DLL experts are installed to the HKCU\Borland\BDS\4.0\Experts registry key.

The DLL experts registry entry is different to the Package and IDE Package experts registry entries. The Name value is the experts description where the Data value is the full path to the actual experts file.

DLL experts must export a method with a particular signature to be installed by the IDE. I’ll touch on DLL experts a lot more in a future article.

Selecting a descendant class when implementing a notifier

Info: When working with the OTAPI you need to have a good understanding of working with interfaces as the OTAPI uses them extensively.

There is no issue descending the class from TInterfacedObject. However there is a useful class declared in the ToolsAPI unit that is more common to be used. This class is called TNotifierObject and already has stubbed implementations of the methods in the IOTANotifier interface. When descending from TNotifierObject there is no need to declare and implement these methods unless needed.

In the case of the INTAProjectMenuCreatorNotifier the methods of the IOTANotifier interface are not used so there is no need to declare IOTANotifier in the class declaration.

type
  TMyContextMenu = class(TNotifierObject, INTAProjectMenuCreatorNotifier)
    …
  end; 

Registering the notifier

Registration

All notifiers in the IDE require registration. Generally there is a specific interface that includes the specific methods for notifier registration.

The IOTAProjectManager interface has the method needed to register the notifier with the Project Manager. Query the BorlandIDEServices variable for the IOTAProjectManager interface. Once the IOTAProjectManager reference has been retrieved, call the AddMenuCreatorNotifier method, passing an instance of the class that implements the INTAProjectMenuCreatorNotifier interface.

Getting the IOTAProjectManager interface

There are a number of ways in which to get a reference to the IOTAProjectManager interface.

Typecast

Use an as typecast

    var
      lProjManager: IOTAProjectManager   
    begin
      lProjManager := BorlandIDEServices as IOTAProjectManager;
      …
    end;

Supports

Using the supports method from the SysUtils unit

    var
      lProjManager: IOTAProjectManager   
    begin
      if Supports(BorlandIDEServices, IOTAProjectManager, lProjManager) then
    …
    end;

Which is best

For a Package expert either is fine. For a DLL expert, I would generally use the Supports method. This is because if the BorlandIDEServices doesn’t support the interface and you perform an as type cast, then an access violation will occur. Within the IDE, you really do not want to be causing any access violations! DLL experts are also loaded before any package expert which means that the BorlandIDEServices variable may not support a particular interface until later in the IDE load process.

Unregistering

Unregister the notifier using the RemoveMenuCreatorNotifier method on the IOTAProjectManager interface. Pass in the notifier Index that was returned from the call to AddMenuCreatorNotifier.

INTAProjectMenuCreator Interface

  INTAProjectMenuCreatorNotifier = interface(IOTANotifier)
  ['{8209348C-2114-439C-AD4E-BFB7049A636A}']
    function AddMenu(const Ident: string): TMenuItem;
    function CanHandle(const Ident: string): Boolean;
  end;

The INTAProjectMenuCreatorNotifier interface requires two methods being implemented in the notifier class.

AddMenu

This method is called when the project manager context menu is being created. Return a valid TMenuItem when this function is called.

Returning nil will cause the IDE to crash.

CanHandle

CanHandle determines whether the context menu item should appear for a certain Ident value. It would not make much sense if the context menu item only performed an action on HTML files and the selected item in the project manager was a Pascal file. Determine the type of node selected in the Project Manager via the Ident parameter.

In the ToolsAPI unit there are some known Ident values documented. Here is the list as well as an example of when the Ident is sent.

sBaseContainer

The selected node is the base node that can contain child nodes. This is only used for .NET applications and appears for when the project node is selected as well as the References node.

sFileContainer

The selected node contains a filename.

sProjectContainer

The selected node is the project file name.

sCategoryContainer

Cannot find a node that returns this Ident value.

sDirectoryContainer

A directory is selected.

sReferencesContainer

The References node of a .NET assembly is currently selected.

sContainsContainer

The Contains node of a package is selected.

sRequiresContainer

The Requires node of a package is selected.

As mentioned above, this is the list of valid Ident values that are documented in the ToolsAPI file. The issue is that there are many possible Ident values that are not documented.

Undocument Ident values list

Here is a list of the undocumented Ident values.

DelphiDotNetAssemblyCompiler

The selected Assembly reference is in a Delphi.NET (VCL or WinForms) project.

DotNetAssemblyCompiler

The selected Assembly reference is in a CSharp or VB project.

CSharpAssemblyCompiler

The selected file is a CSharp file.

VBTool

The selected file is a Visual Basic file.

HTMLTool

The selected file is a HTML file.

XsdCompiler

The selected file is an XML schema file.

WebRefCreator

The selected file is the MAP file node from a Web Reference that was added to a .NET project.

TogetherDiagram

The selected node is under the ModelSupport_XXX directory in an ECO application or any application that supports modeling.

AspMarkupContainer

The selected node is an ASP file. This includes files with extensions ASAX and ASPX.

AspWebConfigContainer

The selected node is the Web.config file from an ASP.NET project.

File

The selected node is the *.ecopkg file from a Delphi.NET ECO WinForms project. This might be a bug however since the *.ecopkg file from an ECO Package project does not return File as a valid Ident.

ResXCompiler

Selected node is a *.resx file in a .NET project.

RC

Selected node is an *.rc file.

Note: This may not be all possible Ident values.

About Ident values

There are a couple of extra things I would like to mention.

A selected node can return multiple Ident values. This means the CanHandle logic needs to check all possible Ident values and support the ones necessary. It is possible to end up with multiple copies of the menu item in the context menu if the CanHandle logic is flawed.

Just because the Ident value of the FileContainer is set, do not assume that the filename will always be fully qualified. One instance where this is not correct is when the selected node is a required package. When a required package is selected, only the package name is returned in the Ident.

Tip for keeping the CanHandle method short and sweet

As you can see there are a number of Ident values the can be tested for, especially if the menu item is not to be duplicated in the context menu. With this in mind, it is best to focus on when the menu item should display; not when you do not want the menu item to display.

To display the context menu item when the selected node is a filename, do not wait until the actual Ident value contains the filename, instead set CanHandle to True when the FileContainer Ident value is found. The actual filename from the Project Manager can be retrieved using the IOTAProjectManager interface.

function TMyContextMenu.CanHandle(const Ident: string): Boolean;
begin
  result := SameText(Ident, sFileContainer);
end;

When the selected node is the project node (ie. project1.exe) then it is important to point out that this selection does not include a FileContainer ident value. If the project node is to contain the menu item, check for the ProjectContainer Ident value in the CanHandle method.

function TMyContextMenu.CanHandle(const Ident: string): Boolean;
begin
  result := SameText(Ident, sFileContainer) or
    SameText(Ident, sProjectContainer);
end;

Note: It is possible to differentiate between the ProjectContainer and a FileContainer node being selected. This is important due to the fact that the Ident value returned for the ProjectContainer is not the full path. This is because output folders can be different to the actual folder the project is stored in.

Getting the selected filename from the project manager

To get the filename from the selected node in the project manager use the GetCurrentSelection method that is declared on the IOTAProjectManager interface.

The GetCurrentSelection method returns an interface reference to the project that the selected file belongs to, as well as returning the Ident value.

Checking for ProjectContainer node selection

The method I use to get the ProjectContainer node when I am also handling FileContainer nodes is as follows.

Use GetCurrentSelection and keep a reference to the IOTAProject interface that is returned. Then test the projects TargetFileName against the Ident value from the GetCurrentSelection call.

The code below is a helper function from the second example application. Full source is included in the download file associated with this article.

// return the path to the project reference in the aIdent parameter
function GetProjectPath(const aProj: IOTAProject; const aIdent: string): string;
begin
  // need to check the targetname against the aIdent parameter value
  if SameText(ExtractFileName(aProj.ProjectOptions.TargetName), aIdent) then
    result := ExtractFilePath(aProj.FileName)
  else
    result := '';
end;

Example 1: Implementation for adding a FileContainer context menu item

Here is an example class for implementing a FileContainer specific context menu item in the project manager.

unit MCMAddIn;

interface

uses
    Menus
  , ToolsAPI
  ;

type
  TMyContextMenu = class(TNotifierObject, INTAProjectMenuCreatorNotifier)
  private
    procedure MyContextMenuClickHandler(Sender: TObject);
  public
    function AddMenu(const Ident: string): TMenuItem;
    function CanHandle(const Ident: string): Boolean;
  end;

procedure Register;

implementation

uses
    Dialogs
  , SysUtils
  ;

resourcestring
  StrMyContextMenu = 'My Context Menu';
  StrSelectedFileIs = 'Selected File is:'#13#10'%s';

{ TMyContextMenu }

function TMyContextMenu.AddMenu(const Ident: string): TMenuItem;
begin
  result := TMenuItem.Create(nil);
  result.Caption := StrMyContextMenu;
  result.OnClick := MyContextMenuClickHandler;
end;

function TMyContextMenu.CanHandle(const Ident: string): Boolean;
begin
  result := Ident = sFileContainer;
end;

procedure TMyContextMenu.MyContextMenuClickHandler(Sender: TObject);
var
  lIdent: string;
begin
  lIdent := '';
  (BorlandIDEServices as IOTAProjectManager).GetCurrentSelection(lIdent);
  ShowMessageFmt(StrSelectedFileIs, [lIdent]);
end;

var
  FNotifierIndex: Integer;

procedure Register;
begin
  FNotifierIndex := (BorlandIDEServices as IOTAProjectManager).AddMenuCreatorNotifier(TMyContextMenu.Create);
end;

initialization
  FNotifierIndex := -1;

finalization
  if FNotifierIndex > -1 then
    (BorlandIDEServices as IOTAProjectManager).RemoveMenuCreatorNotifier(FNotifierIndex);

end.

The project manager context menu when the file MCMAddIn.pas is selected.

Hide image

After selecting the context menu item the following dialog is displayed.

Hide image

Example 2 – Explorer shortcuts

Installing this expert into your IDE adds a new menu item to all FileContainer, ProjectContainer and DirectoryContainer Idents.

The menu item added also has four sub menu items on them. The screen capture below shows the menu items that are available.

Hide image

Copy Filename to clipboard

Executing this command copies the selected filename to the clipboard.

Copy File Path to clipboard

Executing this command copies the selected file path to the clipboard.

Command Line Here

Executing this command opens a command prompt at the selected file path.

Explorer Here

Executing this command opens a Windows Explorer instance at the selected file path.

Fully commented source code is available in the download form CodeCentral that is associated with this article.

Source Code

The source code and compiled binaries of each IDE expert is available to download from CodeCentral.

http://cc.codegear.com/Item/24522

Included in the download also in source code and binary form is the Project Manager – What Ident expert. Installing this expert will create a new menu item for each Ident that the selected file in the project manager will support.

Conclusion

Hopefully this article was helpful to those wishing to explore the internals of the Developer Studio product using the published Open Tools API. I am hoping to do some more articles on other Open Tools functions in the future so if you think they are worthwhile, please let me know.

About the author

You can contact Jeremy via the contact page on his website at http://www.jed-software.com.

JED Software provides the Delphi community with developer tools, specialized components and other IDE enhancements. Some are free, while others incur a small fee.



Server Response from: ETNASC02