11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Delphi の IDE にメモを設置する (Tools API)

Last updated at Posted at 2023-09-14

はじめに

最近の Delphi はアンドックな IDE スタイルが利用できません。できなければできないで慣れはするのですが、一時的なメモを置いておく場所が欲しい事があります

image.png

  • テキストファイルをコードエディタで開く事はできるのですが、タブを切り替える必要があります。
  • フォームデザイナとコードエディタは排他なので、オブジェクトインスペクタの内容をコピペしたい時はかなりガチャガチャします。
  • 外部のテキストエディタを使うとタスク切り替えになります。
  • [新規編集ウィンドウ] は IDE の裏に行かないので使い勝手が悪いです。

image.png

IDE から離れず、ウィンドウをガチャガチャしなくていいようにするためには IDE のどこかにメモがあれば便利なのですが...。

ドッキング可能なメモフォーム

それなら IDE にドック可能なメモフォームを追加すればいいんじゃね? という結論に至り、重い腰を上げて IDE 拡張を作る事にしました。こういった拡張は既に誰かが作っている気がしますが、気にしない事にします。

Tools API

Delphi の IDE は Tools API を使って拡張します。

・Native Tools API (NTAPI)
パッケージ内から使える Tools API です。IDE の機能を利用する事ができます。

・Open Tools API (OTAPI)
パッケージを作らなくても使える Tools API です。IDE の一部の機能を利用する事ができます。

IDE 拡張の具体的な方法については DocWiki にドキュメントがあります。

Delphi 8 辺りでドキュメントから Tools API の記述が消えていて、IDE 拡張を作成する敷居が高かったのですが、現在ではドキュメントが復活しており、IDE 拡張の敷居も幾分低くなっています。

ドッキング可能なフォームのサンプル

ドッキング可能なフォームのサンプルが gexperts.org にありますので、これをベースにしたいと思います。

インストールすると、[ヘルプ] メニューに [Show Dockable Form] というメニューが追加され、これをクリックするとドッキング可能な空のフォームが表示されます。

これにメモコンポーネントを貼り付けるだけで目的は達成できそうですね。

改変

では、Delphi 2007 以降で使える事を念頭に改変していきたいと思います。

・リネーム

全体的にリネームして DockMemo という名称にします。

・メモ

ドッキングフォームにメモを最大化で貼ります。

image.png

プロパティ
Align alClient
Font.Name Tahoma
Name mmMemo
ScrollBars ssBoth
WantTabs True

・メニュー

IOTAMenuWizard として登録されている ([ヘルプ] メニューに表示される) ので、これをやめます。

DockFormExpert.pas
procedure Register;
begin
//RegisterPackageWizard(TDockMemoFormExpert.Create as IOTAMenuWizard);
  RegisterPackageWizard(TDockMemoFormExpert.Create);
  TDockableMemoForm.CreateDockMemoForm;
end;

クラスから IOTAMenuWizard インターフェイスも外して、GetMenuText() メソッドも削除しておきます。

これにより、メニューへは自前で登録しなければならなくなりました。メニューの位置は [表示][デスクトップの配置] とセパレーターの間がいいと思いました。

image.png

最近の Delphi はメニューがまとめられているので [ツールウィンドウ] の中がよさそうです。

image.png

メニューアイテムを追加するコードは次のようになりました。

DockFormExpert.pas
procedure RegisterMenu;
var
  NTA: INTAServices;
  i, j: Integer;
  Method: TMethod;
  NewMenu: Boolean;
begin
  NTA := BorlandIDEServices as INTAServices;
  for i := 0 to NTA.MainMenu.Items.Count-1 do
    begin
      if NTA.MainMenu.Items[i].Name <> 'ViewsMenu' then
        Continue;
      for j := 0 to NTA.MainMenu.Items[i].Count-1 do
        begin
          if NTA.MainMenu.Items[i][j].Name = 'ViewToolWindowsItem' then
            NewMenu := True
          else if NTA.MainMenu.Items[i][j].Name = 'ViewToggleSeparator' then
            NewMenu := False
          else
            Continue;
          MemoMenu := TMenuItem.Create(nil);
          MemoMenu.AutoHotkeys := maManual;
          {$IF CompilerVersion >= 21.0}
          if not SameText(PreferredUILanguages, 'JA') then
            MemoMenu.Caption := Title_EN
          else
          {$IFEND}
            MemoMenu.Caption := Title_JA;
          Method.Code := @MenuClick;
          Method.Data := MemoMenu;
          MemoMenu.OnClick := TNotifyEvent(Method);
          if NewMenu then
            NTA.MainMenu.Items[i][j].Add(MemoMenu)
          else
            NTA.MainMenu.Items[i].Insert(j, MemoMenu);
          Break;
        end;
      Break;
    end;
end; { RegisterMenu }

余談ですが、挿入するメニューアイテムの位置を決定するには、既存のメニューアイテムの名前を知る必要があります。メニューアイテムの名前を調べるには適当なパッケージを作成して次のようなユニットを含めます。

EnumMenusExpert.pas
unit EnumMenusExpert;

interface

uses
  Sysutils, Classes, ToolsAPI, Menus, Dialogs;

type
  TEnumMenusExpert = class(TNotifierObject, IOTANotifier, IOTAWizard, IOTAMenuWizard)
  public
    procedure Execute;
    function GetIDString: string;
    function GetMenuText: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Destroyed;
  end;

  procedure Register;

implementation

procedure Register;
begin
  RegisterPackageWizard(TEnumMenusExpert.Create as IOTAMenuWizard);
end;

{ TEnumMenusExpert }

procedure TEnumMenusExpert.Destroyed;
begin
  { Do Nothing }
end;

procedure TEnumMenusExpert.Execute;
var
  NTA: INTAServices;
  i, j: Integer;
  SL: TStringList;
begin
  SL := TStringList.Create;
  try
    NTA := BorlandIDEServices as INTAServices;
    for i := 0 to NTA.MainMenu.Items.Count-1 do
      begin
        SL.Add(Format('[%s]: %s', [NTA.MainMenu.Items[i].Caption, NTA.MainMenu.Items[i].Name]));
        for j := 0 to NTA.MainMenu.Items[i].Count-1 do
          SL.Add(Format('  %s: %s', [NTA.MainMenu.Items[i][j].Caption, NTA.MainMenu.Items[i][j].Name]));
        SL.Add('');
      end;
    ShowMessage(SL.Text);
  finally
    SL.Free;
  end;
end;

function TEnumMenusExpert.GetIDString: string;
begin
  Result := 'EnumMenusExpert';
end;

function TEnumMenusExpert.GetMenuText: string;
begin
  Result := 'Enum Menus...';
end;

function TEnumMenusExpert.GetName: string;
begin
  Result := 'Enum Menus Expert';
end;

function TEnumMenusExpert.GetState: TWizardState;
begin
  Result := [wsEnabled];
end;

initialization

finalization

end.

Requiesdesignide を追加してインストールすれば、[ヘルプ | Enum Menus...] でメニューアイテムのキャプションと名前を調べる事ができます。

image.png

〔Ctrl〕+〔C〕でクリップボードにテキストが入ります

・ショートカット

IDE のショートカットが優先されるため、メモのクリップボード関連のショートカットが使えません。これではあまりに不便なのでメモコンポーネントに入った時に IDE のショートカットを無効にし、メモコンポーネントから出た時に有効に戻すようにします。

DockMemoForm.pas
procedure EnableShortcuts(AValue: Boolean);
  procedure Apply(const Name, ShortCut: string);
  var
    action: TAction;
  begin
    action := TAction(Application.MainForm.FindComponent(Name));
    if action = nil then
      Exit;
    if AValue then
      action.ShortCut := TextToShortCut(ShortCut)
    else
      action.ShortCut := scNone;
  end;
begin
  Apply('EditUndoCommand'     , 'Ctrl+Z'      );
  Apply('EditRedoCommand'     , 'Shift+Ctrl+Z');
  Apply('EditCutCommand'      , 'Ctrl+X'      );
  Apply('EditCopyCommand'     , 'Ctrl+C'      );
  Apply('EditPasteCommand'    , 'Ctrl+V'      );
  Apply('EditSelectAllCommand', 'Ctrl+A'      );
end;

・自動保存 / 自動読込

メモはプロジェクト毎に関連付け、プロジェクトファイルのある場所に *.memo という名前で自動保存する事にします。

ノーティファイアを登録し、アクティブなプロジェクトが変更された時に保存/読込を行います。プロジェクトが閉じられた時にも保存を行います。

DockMemoForm.pas
procedure TIDENotifier.FileNotification(NotifyCode: TOTAFileNotification;
  const FileName: string; var Cancel: Boolean);
begin
  if not Assigned(FormInstance) then
    Exit;
  case NotifyCode of
    ofnFileClosing:
      begin
        if GetActiveProject = nil then
          begin
            SaveFile;
            ChangeFile;
            SetCaption;
            TDockableMemoForm(FormInstance).mmMemo.Lines.Clear;
          end;
      end;
    ofnActiveProjectChanged:
      begin
        SaveFile;
        ChangeFile;
        SetCaption;
        LoadFile;
      end;
  end;
end;

Unicode 版 Delphi では UTF-8 で保存し、ANSI 版ではデフォルトのコードページ (日本語版 Windows なら Shift_JIS) で保存します。

DockMemoForm.pas
procedure SaveFile;
begin
  with TDockableMemoForm(FormInstance) do
    if FileExists(ProjectFileName) then
      mmMemo.Lines.SaveToFile(MemoFileName{$IFDEF UNICODE}, TEncoding.UTF8{$ENDIF});
end; { SaveFile }

・リネーム対応

本来はプロジェクトがリネームされた時に、それに追従してメモファイルもリネームすべきですが、ノーティファイアがうまく働かなかった (ちゃんと動かせなかった) ので、リネーム対応にはなっていません。

image.png

ここでリネームする事はあまりないので、そこまで不便ではないとは思います (今後の課題としておきます)。

・デバッグ

IDE 拡張はデバッグしにくいです。とりあえず、次のような手続きを作っておくと Print デバッグが可能となります。

procedure WriteLog(TabName, s: string);
var
  IMessageServices: IOTAMessageServices;
  MessageGroup: IOTAMessageGroup;
begin
  IMessageServices := BorlandIDEServices as IOTAMessageServices;
  MessageGroup := IMessageServices.AddMessageGroup(TabName);
  IMessageServices.AddTitleMessage(s, MessageGroup);
end;

例えば

WriteLog('DockMemo', 'FileNotification: 7');

のように記述すると、

image.png

メッセージウィンドウに DockMemo タブが作られ、そこにログが表示されます。

・パッケージ

とりあえず 2007 / XE / 11 Alexandria 用のパッケージを作りました。

Delphi パッケージプロジェクト名
Delphi 2007 DockMemoPackageD2007.dproj
Delphi XE DockMemoPackageDXE.dproj
Delphi 11 Alexandria DockMemoPackageD11.dproj

Delphi 10 Seattle 以降、IDE は Large Address Aware に対応しているので、IDE 拡張も LAA 対応にする必要があったハズです。

image.png

Delphi 11 Alexandria 用のパッケージはプロジェクトオプションで LAA を有効にしておきました。

完成

出来上がった IDE 拡張のアーカイブを用意しました。

パッケージをインストールするとメモが使えるようになります。

image.png

お好きな所にドッキングさせて使って下さい。デスクトップの保存 をするとドック状態が維持されます。

アンインストール

アンインストールは [コンポーネント | パッケージのインストール...] から IDE Docking Memo Form を探して削除してください。

image.png

おわりに

不満な所があったらご自由に改変してお使いください。

バグ修正コードやリネーム対応コードをお待ちしております (w

See also:

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?