Editor Views in RAD Studio

By | June 5, 2018

Overview

Well, this was the blog I tried to write before Xmas before I found it didn’t quite work in all circumstances. So here I’ll describe how to create a custom editor view in RAD Studio along with editor status bar panels. These editor views are full editor tabs not sub-tabs like the sub-views I described before, therefore they are not associated with a specific module and you have to provide the contents of the editor view via a frame. The example below is from the currently unreleased Browse and Doc It plug and it provides a treeview of metrics for all modules (units, forms, etc) that are in the active project and highlights those that are above the limits set.

I’m only going to describe the class you need to create for the editor view and how to tell the IDE about it. The frame that is used will only be referred to as we look at some of the methods.

Definition

Below is the definition of the class which implements a number of Open Tools API interfaces. The definition at first might appear a little more complicated than it needs to be and that is because I have embedded some other classes in this class which manage information. I will go through those where they apply.

Type
  TBADIModuleMetricsEditorView = Class(TInterfacedObject, INTACustomEditorView, INTACustomEditorView150, INTACustomEditorViewStatusPanel)
  Strict Private
    Type
      TBADIFileInfoManager = Class
        ...
      End;
      TBADIMetricStatusPanel = (mspModules, mspMethods, mspLinesOfCode, mspUnderLimit, mspAtLimit, mspOverLimit);
      TBADIFrameManager = Class
      Strict Private
        Type
          TBADIFrameManagerRecord  = Record
            FEditWindowName : String;
            FFrameReference : TframeBADIModuleMetricsEditorView;
          End;
      Strict Private
        FFrames : TList;
      Strict Protected
        Function  GetFrame(Const strEditWindowName : String) : TframeBADIModuleMetricsEditorView;
        Function  Find(Const strEditWindowName :String) : Integer;
      Public
        Constructor Create;
        Destructor Destroy; Override;
        Procedure Add(Const strEditWindowName : String; Const AFrame : TframeBADIModuleMetricsEditorView);
        Property Frame[Const strEditWindowName : String] : TframeBADIModuleMetricsEditorView Read GetFrame;
      End;
    Class Var
      FEditorViewRef : INTACustomEditorView;
  Strict Private
    FFrameManager     : TBADIFrameManager;
    FFileInfoMgr      : TBADIFileInfoManager;
    FImageIndex       : Integer;
    FViewIdent        : String;
    FModulePanels     : Array[Low(TBADIMetricStatusPanel)..High(TBADIMetricStatusPanel)] Of TStatusPanel;
    FCount            : Integer;
    FSourceStrings    : TStringList;
    FSource           : String;
    FFileName         : String;
    FModified         : Boolean;
    FFileDate         : TDateTime;
    FLastRenderedList : TBADIModuleMetrics;
  Strict Protected
    // INTACustomEditorView
    Function  CloneEditorView: INTACustomEditorView;
    Procedure CloseAllCalled(Var ShouldClose: Boolean);
    Procedure DeselectView;
    Function  EditAction(Action: TEditAction): Boolean;
    Procedure FrameCreated(AFrame: TCustomFrame);
    Function  GetCanCloneView: Boolean;
    Function  GetCaption: String;
    Function  GetEditState: TEditState;
    Function  GetEditorWindowCaption: String;
    Function  GetFrameClass: TCustomFrameClass;
    Function  GetViewIdentifier: String;
    Procedure SelectView;
    // INTACustomEditorView150
    Procedure Close(Var Allowed: Boolean);
    Function  GetImageIndex: Integer;
    Function  GetTabHintText: String;
    // INTACustomEditorViewStatusPanel
    Procedure ConfigurePanel(StatusBar: TStatusBar; Panel: TStatusPanel);
    Procedure DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; Const Rect: TRect);
    Function  GetStatusPanelCount: Integer;
    // General Methods
    Procedure ParseAndRender;
    Procedure UpdateStatusPanels;
    Procedure ExtractSourceFromModule(Const Module : IOTAModule);
    Procedure ExtractSourceFromFile;
    Procedure LastModifiedDateFromModule(Const Module: IOTAModule);
    Procedure LastModifiedDateFromFile(Const ModuleInfo: IOTAModuleInfo);
    Function  CurrentEditWindow : String;
    Procedure ProcesModule(Const ModuleInfo : IOTAModuleInfo);
    Function  RenderedList : TBADIModuleMetrics;
  Public
    Class Function CreateEditorView: INTACustomEditorView;
    Constructor Create(Const strViewIdentifier : String);
    Destructor Destroy; Override;
  End;

Interfaces

The following interfaces are implemented in the above class as follows:

INTACustomEditorView

This interface is the main interface which you need to implement in order to provide a custom editor view. You should note that this is a native interface (the N in INTAXxxx) and as such it is accessing some of the internals of the specific version of RAD Studio. What this means is that in order to use this interface you need to create specific versions of your plug-in for each RAD Studio IDE as the interface is version specific and will likely crash other versions.

INTACustomEditorView150

This interface extends the above interface by added the ability to provide hints and icons on the editor tabs.

INTACustomEditorViewStatusPanel

This last interface is not required for implementing a custom editor view however I’ve implemented it to show you how to create editor status panels that are specific to your custom editor view.

Inner Classes

I’ve embedded a number of types in the class as they are not required outside of the custom editor view. These may end up being pulled out into separate units as they may be useful in other editor views (code checks for instance).

TBADIFileInfoManager

This first class is a wrapper around a simple generic record and is used to store module filenames against their last modified update. The reason I’ve done this is that when the custom editor view regains focus it checks which modules might be updated and refreshes only those metrics in the treeview. I’m not going to describe the implementation as I’m sure that this is straightforward for everyone.

TBADIMetricStatusPanel

This is an enumerate to define the panels in the statusbar. This just makes the code more readable in terms of what each panel contains.

TBADIFrameManager

This class requires a little more explanation. I found that there is no way in the IDE to track custom editor views in different editor windows. This was the issue that prevented me from writing this blog before. Hopefully you know that you can have more than one editor window open in the IDE. In these cases you need an editor view for each and its keeping track of these that requires this class. I’m not going to go through the implementation as its straight forward however the class keeps track of the frame reference against the editor window name.

Fields

Below is a brief explanation of the fields I’ve defined in the custom editor view class.

FFrameManager : TBADIFrameManager

This is a reference to an instance of the frame manager described above.

FFileInfoMgr : TBADIFileInfoManager

This is a reference to the file information manager described above.

FImageIndex : Integer

This is the image index of the image we need to add the IDE’s image list that will be displayed next to the editor tab.

FViewIdent : String

This is the name of the view which is passed in the class’s constructor.

FModulePanels : Array[Low(TBADIMetricStatusPanel)..High(TBADIMetricStatusPanel)] Of TStatusPanel

This field is an array which holds references to each of the statusbar panels we want to maintain with the view.

FCount : Integer

This is a counter which is used in the editor view caption. See GetCaption for more details on why this is required.

FSourceStrings : TStringList

This field is used to load the source text from a disk file. Its created once in the constructor and free in the destructor to try and improve performance.

FSource : String

This field is used to hold the module source code for the module that is about to be parsed.

FFileName : String

This field holds the current filename being parsed.

FModified : Boolean

This field holds a boolean denoting whether the current file being parsed was modified.

FFileDate : TDateTime

This field holds the date and time of the current file being parsed

FLastRenderedList : TBADIModuleMetrics

This field holds a list of the metric options being rendered so that if the options are changed the list can be re-rendered even if the code has not changed.

Constants

Below are a few constants that are used within the code.

Const
  strBADIMetricsEditorView = 'BADIMetricsEditorView';
  strBADIMetrics = 'BADI Metrics';
  strUnknown = 'Unknown';

Implementation

Next I’ll go through the implementation of the methods in the class.

Functions

This method returns an instance of the custom editor view and is passed in the registration of the view so that a view can be created when a desktop is loaded.

Function RecreateBADIStatisticEditorView: INTACustomEditorView;

Begin
  Result := TBADIModuleMetricsEditorView.CreateEditorView;
End;

Interfaces Methods

INTACustomEditorView

Function CloneEditorView: INTACustomEditorView

This method is called when the IDE wants to clone the view and if the GetCanClose method returns true.

You should return a cloned instance of your view if requested. I have not been able to get the IDE to ever call this method.

Function TBADIModuleMetricsEditorView.CloneEditorView: INTACustomEditorView;

Var
  EVS : IOTAEditorViewServices;

Begin
  If Supports(BorlandIDEServices, IOTAEditorViewServices, EVS) Then
    EVS.CloseActiveEditorView;
  Result := RecreateBADIStatisticEditorView;
End;
Procedure CloseAllCalled(Var ShouldClose: Boolean)

This method is called when all the views in the editor are being requested to close. Return true to allow it to close else return false for it to persist.

I return true here so it can be closed.

Procedure TBADIModuleMetricsEditorView.CloseAllCalled(Var ShouldClose: Boolean);

Begin
  ShouldClose := True;
End;
Procedure DeselectView

This method is called when the editor view loses focus.

I don’t do anything here but you may want to do some processing here to store any state information for your view.

Procedure TBADIModuleMetricsEditorView.DeselectView;

Begin
  // Does nothing
End;
Function EditAction(Action: TEditAction): Boolean

This method is called for the given editor action that you have said is supported by the editor view (the return of GetEditState).

I have only implemented copy, so the treeview text is copied to the clipboard if that action is invoked.

Function TBADIModuleMetricsEditorView.EditAction(Action: TEditAction): Boolean;

Var
  AFrame: TframeBADIModuleMetricsEditorView;

Begin
  Result := False;
  Case Action Of
    eaCopy:
      Begin
        AFrame := FFrameManager.Frame[CurrentEditWindow];
        If Assigned(AFrame) Then
          AFrame.CopyToClipboard;
        Result := True;
      End;
  End;
End;
Procedure FrameCreated(AFrame: TCustomFrame)

This method is called when the frame is first created.

The method stores a reference to the frame so that a module metrics frame can be rendered

Procedure TBADIModuleMetricsEditorView.FrameCreated(AFrame: TCustomFrame);

Const
  strTEditWindow = 'TEditWindow';

Var
  ES : INTAEditorServices;
  C : TWinControl;
  strEditWindowName : String;

Begin
  FFileInfoMgr.Clear;
  If Supports(BorlandIDEServices, INTAEditorServices, ES) Then
    Begin
      strEditWindowName := strUnknown;
      C := AFrame;
      While Assigned(C) Do
        Begin
          If C.ClassName = strTEditWindow Then
            Begin
              strEditWindowName := C.Name;
              Break;
            End;
          C := C.Parent;
        End;
      FFrameManager.Add(strEditWindowName, AFrame As TframeBADIModuleMetricsEditorView);
    End;
End;
Function GetCanCloneView: Boolean

This is a getter method for the CanCloseView property.

Returns false as this editor view should not be cloned (think singleton view).

Function TBADIModuleMetricsEditorView.GetCanCloneView: Boolean;

Begin
  Result := False;
End;
Function GetCaption: String

This is a getter method for the Caption property.

The method returns the caption for the editor view. It is also used as the editor sub view tab description. I found that this occurred on separate calls so by looking at the even or odd calls you can name the editor sub-view tab differently than the editor tab.

Function TBADIModuleMetricsEditorView.GetCaption: String;

ResourceString
  strMetrics = 'Metrics';

Const
  iDivisor = 2;

Begin
  Inc(FCount);
  If FCount Mod iDivisor = 0 Then
    Result := strMetrics
  Else
    Result := strBADIMetrics;
End;
Function GetEditState: TEditState

This is a getter method for the EditState property.

This method is called to tell the IDE what editor states can be invoked on the data in the view (cut, copy, paste, etc). I only want to be able to copy the treeview text.

Function TBADIModuleMetricsEditorView.GetEditState: TEditState;

Begin
  Result := [esCanCopy];
End;
Function GetEditorWindowCaption: String

This is a getter method for the EditorWindowCaption property.

Returns the text to be displayed in the Editor Window (you can only see this when the editor is floating).

Function TBADIModuleMetricsEditorView.GetEditorWindowCaption: String;

Begin
  Result := strBADIMetrics;
End;
Function GetFrameClass: TCustomFrameClass

This is a getter method for the FrameClass property.

The method returns the frame class that the IDE should create when creating the editor view (you don’t create this yourself).

Function TBADIModuleMetricsEditorView.GetFrameClass: TCustomFrameClass;

Begin
  Result := TframeBADIModuleMetricsEditorView;
End;
Function GetViewIdentifier: String

This is a getter method for the ViewIdentifer property.

This returns a unique identifier for this view (must be unique within the IDE – think singleton instance).

Function TBADIModuleMetricsEditorView.GetViewIdentifier: String;

Begin
  Result := Format('%s.%s', [strBADIMetricsEditorView, FViewIdent]);
End;
Procedure SelectView;

This method is called when the editor view is selected, either when it’s created or when it regains focus.

This method renders the module metrics in the frame.

Procedure TBADIModuleMetricsEditorView.SelectView;

ResourceString
  strParsingProjectModules = 'Parsing project modules';
  strPleaseWait = 'Please wait...';
  strParsing = 'Parsing: %s...';

Const
  setModuleTypesToParse = [omtForm, omtDataModule, omtProjUnit, omtUnit];

Var
  P: IOTAProject;
  iModule: Integer;
  frmProgress : TfrmProgress;
  ModuleInfo: IOTAModuleInfo;
  AFrame: TframeBADIModuleMetricsEditorView;

Begin
  P := ActiveProject;
  If Assigned(P) Then
    Begin
      If FLastRenderedList <> RenderedList Then
        FFileInfoMgr.Clear;
      FLastRenderedList := RenderedList;
      frmProgress := TfrmProgress.Create(Application.MainForm);
      Try
        frmProgress.Init(P.GetModuleCount, strParsingProjectModules, strPleaseWait);
        For iModule := 0 To P.GetModuleCount - 1 Do
          Begin
            ModuleInfo := P.GetModule(iModule);
            If ModuleInfo.ModuleType In setModuleTypesToParse Then
              Begin
                ProcesModule(ModuleInfo);
                frmProgress.UpdateProgress(Succ(iModule), Format(strParsing, [ExtractFileName(FFileName)]));
              End
          End;
        AFrame := FFrameManager.Frame[CurrentEditWindow];
        If Assigned(AFrame) Then
          AFrame.FocusResults;
      Finally
        frmProgress.Free;
      End;
      UpdateStatusPanels;
    End;
End;

INTACustomEditorView150

Procedure Close(Var Allowed: Boolean)

This method is called when this view tab in the editor is being requested to close. Return true to allow it to close else return false for it to persist.

I return true here so it can be closed.

Procedure TBADIModuleMetricsEditorView.Close(Var Allowed: Boolean);

Begin
  Allowed := True;
End;
Function GetImageIndex: Integer

This is a getter method for the ImageIndex property.

Returns the image index of the image in the editor image list for this editor view.

Function TBADIModuleMetricsEditorView.GetImageIndex: Integer;

Begin
  Result := FImageIndex;
End;
Function GetTabHintText: String

This is a getter method for the TabHintText property.

Returns the text to be displayed when the mouse is hovered over the editor tab.

Function TBADIModuleMetricsEditorView.GetTabHintText: String;

Begin
  Result := strBADIMetrics;
End;

INTACustomEditorViewStatusPanel

Procedure ConfigurePanel(StatusBar: TStatusBar; Panel: TStatusPanel)

This method is called when each editor status panel is created.

References to the panels are stored for later use and each panel is configured. Note: I found a bug here regarding the style of the panel.

Procedure TBADIModuleMetricsEditorView.ConfigurePanel(StatusBar: TStatusBar; Panel: TStatusPanel);

Const
  iPanelWidth = 80;

Begin
  FModulePanels[TBADIMetricStatusPanel(Panel.Index)] := Panel;
  FModulePanels[TBADIMetricStatusPanel(Panel.Index)].Alignment := taCenter;
  FModulePanels[TBADIMetricStatusPanel(Panel.Index)].Width := iPanelWidth;
  // Problems with first panel if you do not explicitly set this
  FModulePanels[TBADIMetricStatusPanel(Panel.Index)].Style := psOwnerDraw; // psText;
End;
Procedure DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; Const Rect: TRect)

This method is called for each status panel if it is set to owner draw.

Each panel is drawn with a blue number and black bold text (more to demonstrate what you can do then actually needing this).

Procedure TBADIModuleMetricsEditorView.DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; Const Rect: TRect);

  Procedure DrawBackground(Const strNum : String; Const StyleServices : TCustomStyleServices);

  Var
    iColour : TColor;

  Begin
    If TBADIMetricStatusPanel(Panel.Index) In [mspModules..mspLinesOfCode] Then
      Begin
        iColour := clBtnFace;
        If Assigned(StyleServices) Then
          iColour := StyleServices.GetSystemColor(clBtnFace);
      End Else
        iColour := iLightGreen;
    If strNum <> '' Then
      Case TBADIMetricStatusPanel(Panel.Index) Of
        mspAtLimit:
          If StrToInt(strNum) > 0 Then
            iColour := iLightAmber;
        mspOverLimit:
          If StrToInt(strNum) > 0 Then
            iColour := iLightRed;
      End;
    StatusBar.Canvas.Brush.Color := iColour;
    StatusBar.Canvas.FillRect(Rect);
  End;

  Function CalcWidth(Const strNum, strSpace, strText : String) : Integer;

  Begin
    StatusBar.Canvas.Font.Style := [];
    Result := StatusBar.Canvas.TextWidth(strNum);
    Inc(Result, StatusBar.Canvas.TextWidth(strSpace));
    StatusBar.Canvas.Font.Style := [fsBold];
    Inc(Result, StatusBar.Canvas.TextWidth(strText));
  End;

  Procedure DrawText(Var strNum, strSpace, strText : String; Const iWidth : Integer;
    Const StyleServices : TCustomStyleServices);

  Const
    iDivisor = 2;

  Var
    R : TRect;

  Begin
    R := Rect;
    Inc(R.Left, (R.Right - R.Left - iWidth) Div iDivisor);
    Inc(R.Top);
    StatusBar.Canvas.Font.Color := clBlue; //: @todo Fix when the IDE is fixed.
    StatusBar.Canvas.Font.Style := [];
    StatusBar.Canvas.TextRect(R, strNum, [tfLeft, tfVerticalCenter]);
    Inc(R.Left, StatusBar.Canvas.TextWidth(strNum));
    StatusBar.Canvas.TextRect(R, strSpace, [tfLeft, tfVerticalCenter]);
    StatusBar.Canvas.Font.Color := clWindowText;
    If Assigned(StyleServices) Then
      StatusBar.Canvas.Font.Color := StyleServices.GetSystemColor(clWindowText);
    StatusBar.Canvas.Font.Style := [fsBold];
    Inc(R.Left, StatusBar.Canvas.TextWidth(strSpace));
    StatusBar.Canvas.TextRect(R, strText, [tfLeft, tfVerticalCenter]);
  End;

Var
  strNum, strSpace, strText : String;
  iPos : Integer;
  StyleServices : TCustomStyleServices;
  {$IFDEF DXE102}
  ITS : IOTAIDEThemingServices;
  {$ENDIF}

Begin
  StyleServices := Nil;
  {$IFDEF DXE102}
  If Supports(BorlandIDEServices, IOTAIDEThemingServices, ITS) Then
    If ITS.IDEThemingEnabled Then
      StyleServices := ITS.StyleServices;
  {$ENDIF}
  // Split text by first space
  iPos := Pos(#32, Panel.Text);
  strNum := Copy(Panel.Text, 1, Pred(iPos));
  strSpace := #32;
  strText := Copy(Panel.Text, Succ(iPos), Length(Panel.Text) - iPos);
  DrawBackground(strNum, StyleServices);
  DrawText(strNum, strSpace, strText, CalcWidth(strNum, strSpace, strText), StyleServices);
End;
Function GetStatusPanelCount: Integer;

This is a getter method for the StatusPanelCount property.

Returns the number of status panels to create for the editor view.

Function TBADIModuleMetricsEditorView.GetStatusPanelCount: Integer;

Begin
  Result := Ord(High(TBADIMetricStatusPanel)) - Ord(Low(TBADIMetricStatusPanel)) + 1;
End;

General Methods

Constructor

This is the constructor for the TBADIModuleMetrics class.

This create a number of the classes for managing information and adds an image to the editor image list to be displayed against this editor view.

Constructor TBADIModuleMetricsEditorView.Create(Const strViewIdentifier : String);

Const
  strBADIMetricsImage = 'BADIMetricsImage';

Var
  EVS : INTAEditorViewServices;
  ImageList : TImageList;
  BM: TBitmap;

Begin
  Inherited Create;
  FFrameManager := TBADIFrameManager.Create;
  FFileInfoMgr := TBADIFileInfoManager.Create;
  FSourceStrings := TStringList.Create;
  FViewIdent := strViewIdentifier;
  FCount := 0;
  If Supports(BorlandIDEServices, INTAEditorViewServices, EVS) Then
    Begin
      ImageList := TImageList.Create(Nil);
      Try
        BM := TBitMap.Create;
        Try
          BM.LoadFromResourceName(HInstance, strBADIMetricsImage);
          ImageList.AddMasked(BM, clLime);
          FImageIndex := EVS.AddImages(ImageList, strBADIMetricsEditorView);
        Finally
          BM.Free;
        End;
      Finally
        ImageList.Free;
      End;
    End;
End;

Destructor

This is the destructor for the TBADIModuleMetrics class.

It frees the memory used by the module’s management classes.

Destructor TBADIModuleMetricsEditorView.Destroy;

Begin
  FSourceStrings.Free;
  FFileInfoMgr.Free;
  FFrameManager.Free;
  Inherited Destroy;
End;

Class Constructor

This is a class method to create a singleton instance of this editor view.

It create the editor view if it does not already exist else it returned the existing instance reference.

Class Function TBADIModuleMetricsEditorView.CreateEditorView : INTACustomEditorView;

Var
  EVS : IOTAEditorViewServices;

Begin
  Result := Nil;
  If Supports(BorlandIDEServices, IOTAEditorViewServices, EVS) Then
    Begin
      If Not Assigned(FEditorViewRef) Then
        FEditorViewRef := TBADIModuleMetricsEditorView.Create('');
      Result := FEditorViewRef;
      EVS.ShowEditorView(Result);
    End;
End;

CurrentEditWindow

This method returns the name of the current top level editor window.

Function TBADIModuleMetricsEditorView.CurrentEditWindow: String;

Var
  ES : INTAEditorServices;

Begin
  Result := strUnknown;
  If Supports(BorlandIDEServices, INTAEditorServices, ES) Then
    Result := ES.TopEditWindow.Form.Name;
End;

UpdateStatusPanels

This method updates the status panels with the information from the frame, i.e. statistics on the metrics.

Procedure TBADIModuleMetricsEditorView.UpdateStatusPanels;

ResourceString
  strModules = '%d Modules';
  strMethods = '%d Methods';
  strLinesOfCode = '%d Lines';
  strUnderLimit = '%d < Limit';
  strAtLimit = '%d @ Limit';
  strOverLimit = '%d > Limit';

Var
  AFrame: TframeBADIModuleMetricsEditorView;

Begin
  AFrame := FFrameManager.Frame[CurrentEditWindow];
  If Assigned(AFrame) Then
    Begin
      FModulePanels[mspModules].Text := Format(strModules, [AFrame.ModuleCount]);
      FModulePanels[mspMethods].Text := Format(strMethods, [AFrame.MethodCount]);
      FModulePanels[mspLinesOfCode].Text := Format(strLinesOfCode, [AFrame.LinesOfCode]);
      FModulePanels[mspUnderLimit].Text := Format(strUnderLimit, [AFrame.UnderLimit]);
      FModulePanels[mspAtLimit].Text := Format(strAtLimit, [AFrame.AtLimit]);
      FModulePanels[mspOverLimit].Text := Format(strOverLimit, [AFrame.OverLimit]);
    End;
End;

ProcessModule

This last general method has been added as it presents an interesting issue I, to date, have not had to tackle, and that is getting the source code (for parsing) for all the modules in a project. You might think that’s easy I’ll just open each module with OpenModule() from the IOTAModuleInfo interface (which you can get from the IOTAModule interface) however if you do this you will get the IDE to open every module in the project into memory (not necessarily as an editor tab) and this wouldn’t be a good idea for large projects.

So what I’ve done here is see if the IDE has a module open with IOTAModuleServices.FindModule() and if so get the source code from ther editor else I get the code from the disk file.

Procedure TBADIModuleMetricsEditorView.ProcesModule(Const ModuleInfo : IOTAModuleInfo);

Var
  Module: IOTAModule;

Begin
  FModified := False;
  Module := (BorlandIDEServices As IOTAModuleServices).FindModule(ModuleInfo.FileName);
  If Assigned(Module) Then
    LastModifiedDateFromModule(Module)
  Else
    LastModifiedDateFromFile(ModuleInfo);
  If FFileInfoMgr.ShouldUpdate(FFileName, FFileDate) Then
    Begin
      If Assigned(Module) Then
        ExtractSourceFromModule(Module)
      Else
        ExtractSourceFromFile;
        ParseAndRender;
    End;
End;

IDE Registration

This method is called from the main wizard’s constructor to register this custom editor view.

Registering the View

Procedure RegisterMetricsEditorView;

Var
  EVS : IOTAEditorViewServices;

Begin
  If Supports(BorlandIDEServices, IOTAEditorViewServices, EVS) Then
    EVS.RegisterEditorView(strBADIMetricsEditorView, RecreateBADIStatisticEditorView);
End;

Unregistering the View

This method is called from the main wizard’s destructor to unregister this custom editor view.

Procedure UnregisterMetricsEditorView;

Var
  EVS : IOTAEditorViewServices;

Begin
  If Supports(BorlandIDEServices, IOTAEditorViewServices, EVS) Then
    EVS.UnregisterEditorView(strBADIMetricsEditorView);
End;

Final Thoughts

Although I’m not in a position to provide you with a working example I’ve include the code for this module below. On the Browse and Doc It web page you can download a beta test which includes this functionality (for XE3 to Tokyo only).

BADI.Module.Metrics.pas