EclipseMenu

メニューについて


   メニューには大きく分けて2種類あります。

一つはポップアップメニューと呼ばれ、特定のエレメントに紐付くメニューです。
例えば Package Explorer 上でプロジェクトを選択して右クリックをしたときに
表示されるものなどです。

もう一つは、Eclipseのメニューです。
これはウィンドウ上部に表示されるもので、常に表示されています。

ポップアップメニューの作り方

まずは plugin.xml の方から一気に説明します。

<extension
      point="org.eclipse.ui.popupMenus">
   <objectContribution
         id="sample.contribution"
         objectClass="org.eclipse.jdt.core.IJavaElement">
      <menu
            id="sample.menu"
            label="Sample"
            path="additions">
      </menu>
      <action
            class="sample.SamplePopupAction"
            id="sample.SamplePopupAction"
            label="Popup Action"
            menubarPath="sample.menu/additions">
      </action>
      <action
            class="sample.SamplePopupAction2"
            id="sample.SamplePopupAction2"
            label="Popup Action2"
            menubarPath="sample.menu/additions">
      </action>
   </objectContribution>
</extension>

このように popupMenus の extension を作成します。
その中に、objectContribution 要素を作成します。
これは「どのエレメントに対応するメニューか」を表したもので
ここでは IJavaElement を指定しています。

具体的にはこれでどうなるかというと、例えば Package Explorer 上で
Javaクラスを選択して右クリックしたときにメニューが表示されるようになります。
しかし、通常のファイルを選択したときにはメニューが表示されません。
これは、通常のファイルは IJavaElement インターフェイスを持っていないからです。

IJavaElement のインターフェイスを持つのは、
Javaファイル、Javaパッケージ、Javaソースフォルダ、
Javaライブラリ(jar)ファイル、Javaプロジェクト等です。
全てを知りたい場合は、IJavaElement を Hierarchy ビューで見ましょう。

続いて menu, action 要素についてです。
menu要素は作成しなくても構いません。
ただその場合、メニューの階層化ができません。
今回はメニューとサブメニューを作成するので、menu要素が必要になります。

まず、メインメニューをmenu要素で作成します。
menu#id が、このメニューのIDになります。
続いて、サブメニューをaction要素で作成します。
action#menubarPath に、メインメニューのID + additions を記述します。
こうすることで、サブメニューはメインメニューの子として作成されます。

表示結果は以下のようになります。

Popupメニュー

ポップアップメニュー用アクションクラスの作り方

まだアクションクラスを作っていませんでした。
ポップアップメニュー用アクションは、IActionDelegate を実装する必要があります。

public class SamplePopupAction implements IActionDelegate {

    private ISelection selection;
    
    public void run(IAction action) {
        IJavaElement javaElement = (IJavaElement)
                ((IStructuredSelection)selection).getFirstElement();
        System.out.println(javaElement);
    }

    public void selectionChanged(IAction action, ISelection selection) {
        this.selection = selection;
    }

}

実装メソッドは2つです。
selectionChanged メソッドで、選択された範囲を取得します。
これをフィールドに格納しておいて、run メソッドで範囲内のエレメントを取得します。
ここでは簡略化のために getFirstElement を使って単一のエレメントを使いましたが
複数のエレメントを扱うことも可能です。

通常メニューの作成

続いて、通常メニューの作成方法です。
まずは plugin.xml から。

<extension
      point="org.eclipse.ui.actionSets">
   <actionSet
         id="sample.actionSet"
         label="Sample Action Set"
         visible="true">
      <menu
            id="sample.menu"
            label="Sample"/>
      <action
            class="sample.SampleAction"
            id="sample.SampleAction"
            label="Sample Action"
            menubarPath="sample.menu/additions"/>
   </actionSet>
</extension>

このように、actionSets を使います。

メニュー

アクションクラスは、IWorkbenchWindowActionDelegate インターフェイスを実装します。

public class SampleAction implements IWorkbenchWindowActionDelegate {

    public void dispose() {
    }
  
    public void init(IWorkbenchWindow window) {
    }
  
    public void run(IAction action) {
    }
  
    public void selectionChanged(IAction action, ISelection selection) {
    }
}

ポップアップメニュー用のときとほとんと同じですが、
init / dispose メソッドが追加されています。
特に使わないのならば、空実装で構いません。

選択範囲に関係無い作業、例えば現在開いているファイルに対しての処理がしたい場合などは
init メソッドに渡される window インスタンスが必要になります。

アクティブエディタ内で選択中の範囲を取得する

通常メニューから呼び出されるアクションはいつでも呼び出せる為、
ポップアップメニューよりも高度な処理が可能になります。

ここでは、エディタ内にカーソルがあるときにアクションを呼び出したときの
処理を作成してみます。
Actionクラスは IWorkbenchWindowActionDelegate を実装します。

public class SampleAction implements IWorkbenchWindowActionDelegate {

    private IWorkbenchWindow window;
    private ISelection selection;

    public void init(IWorkbenchWindow window) {
        this.window = window;
    }
  
    public void selectionChanged(IAction action, ISelection selection) {
        this.selection = selection;
    }

    public void run(IAction action) {
        if (selection instanceof IStructuredSelection) {
            // エレメントが選択されている場合
            IStructuredSelection structuredSel = (IStructuredSelection)selection;
            // ポップアップメニュー用アクションと同様...
        }
        if (selection instanceof ITextSelection) {
            // エディタ内にカーソルがある場合
            runInEditor();
        }
    }

}

init, selectionChanged メソッドでは、渡された引数をフィールドに格納します。
run メソッド内で、選択範囲の種別を判定しています。
IStructuredSelection ならば、先程説明したのと同様の処理でOKです。
ITextSelection の場合、エディタ内に選択範囲(カーソル)があることを意味します。

では、runInEditor() の実装例を紹介します。

private void runInEditor() {

    // アクティブエディタを取得
    IEditorPart editor = window.getActivePage().getActiveEditor();
    
    // エディタ内の選択範囲を取得
    ITextSelection textSelection = (ITextSelection)
            ((ITextEditor)editor).getSelectionProvider().getSelection();
    
    // Javaエディタの場合、エディタから対象の IJavaElement を取得できる
    IJavaElement javaElement = JavaUI.getEditorInputJavaElement(editor.getEditorInput());
    
    if (javaElement instanceof ICompilationUnit) {
        // ICompilationUnit はソース情報を持つJavaクラスを表す
        ICompilationUnit cunit = (ICompilationUnit)javaElement;
        
        // エディタ内の選択範囲から、現在選択中の IJavaElement を取得
        IJavaElement selectedElement = cunit.getElementAt(textSelection.getOffset());
        
        if (selectedElement instanceof IField) {
            // フィールド定義を選択している場合...
        }
        if (selectedElement instanceof IMethod) {
            // メソッド定義を選択している場合...
        }
        if (selectedElement instanceof IType) {
            // クラス定義を選択している場合...
        }
    }
}

まずは、現在のアクティブエディタを取得します。
ここで先程格納しておいた window を使うわけです。
続いて、エディタ内での選択範囲を取得します。

ここで鋭い人は「あれ?わざわざここで取得しなくてもrunInEditorの引数に渡せばいいのに」
と思ったことでしょう。
しかしここが落とし穴です。
selectionフィールドに格納された ITextSelection には
選択範囲情報が格納されていないのです。
ですから、これは「エディタ内に選択範囲があるかどうか」という判別にしか使えません。

あとは、これらの情報を使って処理を続けましょう。
上の例ではJavaエディタ内で選択されたメンバに対して処理を行うやり方を載せました。