Delphi 2007: save only the breakpoint options in the DSK file?

delphi, delphi-2007

Solution

I've never come across an IDE facility to save only the breakpoint-related settings in the .Dsk file.

For amusement, I thought I'd try and implement something via an IDE add-in using OTA notifications. The code below runs fine installed into a package installed in D7, and the IDE seems quite happy to re-open a project whose .Dsk file has been processed by it (and the breakpoints get set!).

As you can see, it catches an OTA notifier's FileNotification event when called with a NotifyCode of ofnProjectDesktopSave, which happens just after the IDE has saved the .Dsk file (initially with the extension '.$$$', which I faile to notice when first writing this). It then reads the saved file file, and and prepares an updated version from which all except a specified list of sections are removed. The user then has the option to save the thinned-out file back to disk. I've used a TMemIniFile to do most of the processing simply to minimize the amount of code needed.

I had zero experience of writing an OTA notifier when I read your q, but the GE Experts FAQ referenced below was immensely helpful, esp the example notifier code.

Normally, deleting a project's .Dsk file is harmless, but use this code with caution as it has not been stress-tested.

Update: I noticed that the filename received by TIdeNotifier.FileNotification event actually has an extension of '.$$$'. I'm not quite sure why that should be, but seemingly the event is called before the file is renamed to xxx.Dsk. I thought that would require a change to how to save the thinned-out version, but evidently not.

Update#2: Having used a folder-monitoring utility to see what actually happens, it turns out that the desktop-save notification the code receives is only the first of a number of operations related to the .Dsk file. These include renaming any existing version of the .Dsk file as a .~Dsk file and finally saving the .$$$ file as the new .Dsk file.

unit DskFilesu;

interface

{$define ForDPK}  // undefine to test in regular app

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons, StdCtrls, IniFiles, TypInfo
{$ifdef ForDPK}
  , ToolsApi
{$endif}
  ;

{$ifdef ForDPK}

{
  Code for OTA TIdeNotifier adapted from, and courtesy of, the link on http://www.gexperts.org/open-tools-api-faq/#idenotifier
}

type
  TIdeNotifier = class(TNotifierObject, IOTANotifier, IOTAIDENotifier)
  protected
    procedure AfterCompile(Succeeded: Boolean);
    procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
    procedure FileNotification(NotifyCode: TOTAFileNotification;
      const FileName: string; var Cancel: Boolean);
  end;
{$endif}

type
  TDskForm = class(TForm)
    edDskFileName: TEdit;
    SpeedButton1: TSpeedButton;
    OpenDialog1: TOpenDialog;
    lbSectionsToKeep: TListBox;
    lbDskSections: TListBox;
    moDskFile: TMemo;
    btnSave: TButton;
    procedure btnSaveClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    procedure GetSectionsToKeep;
    function GetDskFileName: String;
    procedure SetDskFileName(const Value: String);
    function GetDskFile: Boolean;
  protected
  public
    DskIni : TMemIniFile;
    property DskFileName : String read GetDskFileName write SetDskFileName;
  end;

var
  NotifierIndex: Integer;
  DskForm: TDskForm;

{$ifdef ForDPK}
procedure Register;
{$endif}

implementation

{$R *.DFM}

{$ifdef ForDPK}
procedure Register;
var
  Services: IOTAServices;
begin
  Services := BorlandIDEServices as IOTAServices;
  Assert(Assigned(Services), 'IOTAServices not available');
  NotifierIndex := Services.AddNotifier(TIdeNotifier.Create);
end;
{$endif}

procedure DskPopUp(FileName : String);
var
  F : TDskForm;
begin
  F := TDskForm.Create(Application);
  try
    F.DskFileName := FileName;
    F.ShowModal;
  finally
    F.Free;
  end;
end;

function TDskForm.GetDskFileName: String;
begin
  Result := edDskFileName.Text;
end;

procedure TDskForm.SetDskFileName(const Value: String);
begin
  edDskFileName.Text := Value;
  if Assigned(DskIni) then
    FreeAndNil(DskIni);
  btnSave.Enabled := False;

  DskIni  := TMemIniFile.Create(DskFileName);
  DskIni.ReadSections(lbDskSections.Items);
  GetSectionsToKeep;
end;

procedure TDskForm.btnSaveClick(Sender: TObject);
begin
  DskIni.UpdateFile;
end;

procedure TDskForm.FormCreate(Sender: TObject);
begin
  lbSectionsToKeep.Items.Add('watches');
  lbSectionsToKeep.Items.Add('breakpoints');
  lbSectionsToKeep.Items.Add('addressbreakpoints');

  if not IsLibrary then
    DskFileName := ChangeFileExt(Application.ExeName, '.Dsk');
end;

procedure TDskForm.GetSectionsToKeep;
var
  i,
  Index : Integer;
  SectionName : String;
begin
  moDskFile.Lines.Clear;
  for i := lbDskSections.Items.Count - 1 downto 0 do begin
    SectionName := lbDskSections.Items[i];
    Index := lbSectionsToKeep.Items.IndexOf(SectionName);
    if Index < 0 then
      DskIni.EraseSection(SectionName);
  end;
  DskIni.GetStrings(moDskFile.Lines);
  btnSave.Enabled := True;
end;

function TDskForm.GetDskFile: Boolean;
begin
  OpenDialog1.FileName := DskFileName;
  Result := OpenDialog1.Execute;
  if Result then
    DskFileName := OpenDialog1.FileName;
end;

procedure TDskForm.SpeedButton1Click(Sender: TObject);
begin
  GetDskFile;
end;

{$ifdef ForDPK}

procedure RemoveNotifier;
var
  Services: IOTAServices;
begin
  if NotifierIndex <> -1 then
  begin
    Services := BorlandIDEServices as IOTAServices;
    Assert(Assigned(Services), 'IOTAServices not available');
    Services.RemoveNotifier(NotifierIndex);
  end;
end;

function MsgServices: IOTAMessageServices;
begin
  Result := (BorlandIDEServices as IOTAMessageServices);
  Assert(Result <> nil, 'IOTAMessageServices not available');
end;

procedure TIdeNotifier.AfterCompile(Succeeded: Boolean);
begin
end;

procedure TIdeNotifier.BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
begin
  Cancel := False;
end;

procedure TIdeNotifier.FileNotification(NotifyCode: TOTAFileNotification;
  const FileName: string; var Cancel: Boolean);
begin
  Cancel := False;
  // Note: The FileName passed below has an extension of '.$$$'
  if NotifyCode = ofnProjectDesktopSave then
    DskPopup(FileName);
end;

initialization

finalization
  RemoveNotifier;
{$endif}

end.

Problem

Is it possible in Delphi to just save the breakpointss in the .DSK file for a project and no other Desktop settings? Most of the .DSK gets in the way, but not being able to save debug breakpoints is a real pain (especially when they are conditionally or actions are attached).

Original source