取り消し可能操作

アクションをワークベンチに提供するさまざまな方法について説明してきましたが、アクションの run() メソッドの実装についてはまだ取り上げていません。メソッドのメカニックは、問題となっている特定のアクションによって異なりますが、取り消し可能操作としてコードを構造化することによって、このアクションをプラットフォームの取り消しおよびやり直しサポートに関係させることができます。

プラットフォームは、取り消し可能操作フレームワークをパッケージ org.eclipse.core.commands.operations で提供しています。 コードを run() メソッド内部に実装して IUndoableOperation を作成することにより、操作の取り消しおよびやり直しが可能になります。取り消しおよびやり直しの振る舞い自体を実装することは別として、操作を使用するようアクションを変換することは容易です。

取り消し可能操作の作成

まず、非常に単純な例を調べてみましょう。README サンプル・プラグインに提供されている単純な ViewActionDelegate を再呼び出しします。呼び出されると、アクションは、実行されたことを知らせるダイアログを起動します。

public void run(org.eclipse.jface.action.IAction action) {
	MessageDialog.openInformation(view.getSite().getShell(),
		MessageUtil.getString("Readme_Editor"),
		MessageUtil.getString("View_Action_executed"));
}
操作を使用した場合、run メソッドは、以前に run メソッドで実行されていた作業を行う操作を作成したり、操作ヒストリーが操作を実行するように要求します。これにより、取り消しややり直しのために操作を記憶できるようになります。
public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
操作は、run メソッドの以前の振る舞いと、操作の取り消しおよびやり直しをカプセル化します。
class ReadmeOperation extends AbstractOperation {
	Shell shell;
	public ReadmeOperation(Shell shell) {
		super("Readme Operation");
		this.shell = shell;
	}
	public IStatus execute(IProgressMonitor monitor, IAdaptable info) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),
		MessageUtil.getString("View_Action_executed"));
		return Status.OK_STATUS;
	}
	public IStatus undo(IProgressMonitor monitor) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),
			"Undoing view action"));   
		return Status.OK_STATUS;
	}
	public IStatus redo(IProgressMonitor monitor) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),
			"Redoing view action"));   
		return Status.OK_STATUS;
	}
}

簡単なアクションの場合、すべての基本作業を操作クラスに移行することができます。 この場合、以前は複数存在したアクション・クラスを、単一アクション・クラスにまとめてパラメータ化することが適切な場合があります。 この単一アクションを実行すると、指定操作が単に実行されます。 この方法を使用するかどうかは、主にアプリケーション設計時に判断します。

アクションがウィザードを起動すると、通常、操作はウィザードの performFinish() メソッドまたはウィザード・ページの finish() メソッドの一部として作成されます。操作を使用するように finish メソッドを変換することは、run メソッドの変換と似ています。メソッドは、以前にインラインで実行されていた作業を行う操作の作成および実行を担当します。

操作ヒストリー

これまで、実際の説明なしに操作ヒストリーを使用してきました。 サンプル操作を作成するコードをもう一度見てみましょう。

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
操作ヒストリーとは、いったい何でしょうか? IOperationHistory は、すべての取り消し可能操作を追跡するオブジェクト用のインターフェースを定義します。操作ヒストリーは、操作を実行するときに、まず操作を実行し、次いでそれを取り消しヒストリーに追加します。 操作の取り消しおよびやり直しを実行するクライアントは、IOperationHistory プロトコルを使用して、それを実行します。

アプリケーションで使用される操作ヒストリーは、複数の方法で検索できます。最も単純な方法は、OperationHistoryFactory を使用する方法です。

IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();

ワークベンチを使用して、操作ヒストリーを検索することもできます。ワークベンチは、デフォルトの操作ヒストリーを構成し、それにアクセスするためのプロトコルも提供します。以下のコードの断片は、ワークベンチから操作ヒストリーを取得する方法を示しています。

IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench();
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
操作ヒストリーの取得後に、それを使用して、取り消しまたはやり直しヒストリーの照会、一連の取り消しまたはやり直しの次の操作の検出、または特定の操作の取り消しまたはやり直しを実行できます。クライアントは、ヒストリーに対する変更についての通知を受信するために IOperationHistoryListener を追加できます。 他のプロトコルを使用することにより、クライアントは、ヒストリーに対して制限を設定し、または特定の操作に対する変更をリスナーに通知できます。プロトコルの詳細を取り上げる前に、取り消しコンテキストについて理解する必要があります。

取り消しコンテキスト

操作が作成されると、元の操作が実行されたユーザー・コンテキストを記述する取り消しコンテキストが操作に割り当てられます。通常、取り消しコンテキストは、取り消し可能操作を発生させたビューまたはエディターによって異なります。例えば、エディター内の変更は、通常、エディターに対してローカルです。 この場合、エディターは独自の取り消しコンテキストを作成して、ヒストリーに追加する操作に割り当てる必要があります。 このようにして、エディター内で実行されるすべての操作は、ローカルおよび半専用と見なされます。共用モデルで機能するエディターまたはビューは、多くの場合、処理しているモデルに関連した取り消しコンテキストを使用します。より一般的な取り消しコンテキストを使用することにより、あるビューまたはエディターで実行された操作を同じモデルで機能する別のビューまたはエディターの取り消しに使用できる可能性があります。

取り消しコンテキストの振る舞いは比較的単純で、IUndoContext のプロトコルは最小限のものです。コンテキストの主な役割は、別の取り消しコンテキストで作成される操作と区別するために、この取り消しコンテキストに属していることを示すタグを特定の操作に付けることです。これで、操作ヒストリーは、これまでに実行されたすべての取り消し可能操作のグローバル・ヒストリーを追跡できるようになり、 ビューおよびエディターは、取り消しコンテキストを使用して、特定の観点でヒストリーをフィルターに掛けることができます。

取り消しコンテキストは、取り消し可能操作を作成しているプラグインによって作成することも、API を介してアクセスすることもできます。例えば、ワークベンチは、ワークベンチ全体にわたる操作に使用できる取り消しコンテキストへのアクセスを提供します。どのように取得した場合にも、操作の作成時に取り消しコンテキストを割り当てる必要があります。以下のコードの断片は、README プラグインの ViewActionDelegate がワークベンチ全体にわたるコンテキストを操作に割り当てる方法を示しています。

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench();
	IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
	IUndoContext undoContext = workbench.getOperationSupport().getUndoContext();
	operation.addContext(undoContext);
	operationHistory.execute(operation, null, null);
}

なぜ取り消しコンテキストを使用するのでしょうか? なぜ、ビューおよびエディターごとに別個の操作ヒストリーを使用しないのでしょうか? 別個の操作ヒストリーを使用する場合は、特定のビューまたはエディターが独自の専用取り消しヒストリーを保持していること、また取り消しはアプリケーション内でグローバルな意味を持たないことが想定されます。これは一部のアプリケーションに適しているかもしれません。その場合、各ビューまたはエディターは、独自の別個の取り消しコンテキストを作成する必要があります。アプリケーションによっては、操作が発生したビューまたはエディターに関係なく、すべてのユーザー操作に適用されるグローバル取り消しを実装したい場合があります。この場合、操作をヒストリーに追加するすべてのプラグインで、ワークベンチ・コンテキストを使用する必要があります。

より複雑なアプリケーションでは、取り消しは厳密にローカルでも厳密にグローバルでもありません。そうではなく、取り消しコンテキスト間で交差が見られます。これは、複数のコンテキストを 1 つの操作に割り当てることで実現されます。例えば、IDE ワークベンチ・ビューは、ワークスペース全体を操作し、ワークスペースを取り消しコンテキストと見なします。ワークスペース内の特定のリソース上で開いているエディターは、操作をほぼローカルと見なします。ただし、エディター内部で実行される操作は、実際には特定のリソースとワークスペース全体の両方に影響を与えます。(この良い例として、JDT リファクタリング・サポートがあります。このサポートにより、ソース・ファイルの編集中に、Java エレメントの構造を変更することができます)。このような場合、エディターと、ワークスペースを操作するビューから取り消しを実行できるよう、両方の取り消しコンテキストを操作に追加できると便利です。

取り消しコンテキストについて理解できたので、IOperationHistory 用のプロトコルをもう一度見てみましょう。 以下のコードの断片は、あるコンテキストで取り消しを実行するために使用されます。

IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
  try {
	IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo);
} catch (ExecutionException e) {
	// handle the exception 
}
このヒストリーは、特定のコンテキストを持つ実行された最新の操作を取得し、その操作の取り消しを求めます。他のプロトコルを使用して、コンテキストの取り消しまたはやり直しヒストリー全体を取得したり、特定のコンテキストで取り消しまたはやり直しが実行される操作を検索できます。以下のコードの断片は、特定のコンテキストで取り消される操作のラベルを取得します。
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
String label = history.getUndoOperation(myContext).getLabel();

グローバルな取り消しコンテキスト IOperationHistory.GLOBAL_UNDO_CONTEXT は、グローバルな取り消しヒストリーを参照するために、使用できます。 つまり、特定のコンテキストに関係なくヒストリー内のすべての操作を参照するために、使用できます。以下の断片は、グローバルな取り消しヒストリーを取得します。

IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT);

操作ヒストリー・プロトコルを使用して操作の実行、取り消し、またはやり直しが行われるたびに、クライアントは進行モニターと、操作の実行に必要な場合がある追加の UI 情報を提供できます。 この情報は、操作自体に渡されます。このオリジナルのサンプルでは、README アクションが、ダイアログを開くために使用できるシェル・パラメーターを使用して操作を構成しました。シェルを操作に保管する代わりに、操作を実行するために必要な UI 情報を提供する execute、undo、および redo メソッドにパラメーターを渡すと良いでしょう。これらのパラメーターは、操作自体に渡されます。

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation();
	...
	operationHistory.execute(operation, null, infoAdapter);
}
infoAdapter は、ダイアログの起動時に使用できるシェルを最低限提供できる IAdaptable です。このサンプル操作は、このパラメーターを以下のように使用します。
	public IStatus execute(IProgressMonitor monitor, IAdaptable info) {
		if (info != null) {
			Shell shell = (Shell)info.getAdapter(Shell.class);
			if (shell != null) {
				MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),
		MessageUtil.getString("View_Action_executed"));
		return Status.OK_STATUS;
		}
	}
		// do something else...
}

取り消しおよびやり直しアクション・ハンドラー

プラットフォームは、ビューおよびエディターで構成可能な、標準の取り消しおよびやり直し再ターゲット可能なアクション・ハンドラーを提供して、特定のコンテキスト用の取り消しおよびやり直しサポートを提供します。アクション・ハンドラーが作成されると、それにコンテキストが割り当てられ、特定のビューに適した方法で操作ヒストリーをフィルターに掛けられるようになります。アクション・ハンドラーは、適切な進行モニターおよび UI 情報を操作ヒストリーに提供し、オプションで現在の操作が無効な場合にヒストリーをプルーニングすることによって、取り消しおよびやり直しラベルを更新し、問題となっている現在の操作を示します。アクション・ハンドラーを作成し、それをグローバル取り消しおよびやり直しアクションに割り当てるアクション・グループが便宜上提供されています。

new UndoRedoActionGroup(this.getSite(), undoContext, true);
最後のパラメーターは、現在取り消しまたはやり直し使用可能な操作が有効でない場合に、指定されたコンテキストの取り消しおよびやり直しヒストリーを廃棄するべきかどうかを示すブールです。このパラメーターの設定は、提供されている取り消しコンテキストおよびそのコンテキストの操作で使用される妥当性検査ストラテジーと関係しています。

アプリケーション取り消しモデル

以前に、取り消しコンテキストを使用して、各種アプリケーション取り消しモデルを実装する方法について調べました。 1 つ以上のコンテキストを操作に割り当てることができるため、アプリケーションは、各ビューまたはエディターに対して厳密にローカル、すべてのプラグインにわたって厳密にグローバル、または中間的なモデルである、取り消しストラテジーを実装できます。 取り消しおよびやり直しが関係する別の設計判断は、任意の操作をいつでも取り消しまたはやり直し可能であるかどうか、またはモデルが厳密に線形で、最新の操作のみを取り消しまたはやり直しの対象として考慮するかどうかということです。

IOperationHistory は、何を許可するかを個々の実装に委ねる柔軟な取り消しモデルを許容するプロトコルを定義します。 これまで説明してきた取り消しおよびやり直しプロトコルは、特定のやり直しコンテキストでは、取り消しまたはやり直し可能な暗黙操作は 1 つのみであると想定しています。クライアントが、ヒストリー内の操作の位置に関係なく、特定の操作を実行できるようにする追加のプロトコルが提供されています。アプリケーションに適したモデルを実装できるよう、操作ヒストリーを構成することができます。これは、操作の取り消しまたはやり直しが実行される前に、任意の取り消しまたはやり直し要求を事前承認するためのインターフェースを使用して行います。

操作アプルーバー

IOperationApprover は、特定の操作の取り消しおよびやり直しを承認するプロトコルを定義します。操作アプルーバーは、操作ヒストリー上にインストールされます。次に、特定の操作アプルーバーはすべての操作の妥当性をチェックしたり、特定のコンテキストの操作のみをチェックしたり、または予期せぬ状況が操作時に検出された場合にユーザーにプロンプトを表示することができます。 以下のコードの断片は、アプリケーションが、操作ヒストリーを構成して、すべての操作に対して線形取り消しモデルを強制する方法を示しています。
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// set an approver on the history that will disallow any undo that is not the most recent operation
history.addOperationApprover(new LinearUndoEnforcer());

この場合、フレームワークで提供される操作アプルーバー LinearUndoEnforcer がヒストリーにインストールされ、最近実行されていないすべての操作の取り消しややり直し、またはすべての取り消しコンテキストの取り消し操作が禁止されます。

別の操作アプルーバー LinearUndoViolationUserApprover は、同じ条件を検出し、操作を継続できるようにするかどうかに関するプロンプトをユーザーに表示します。 この操作アプルーバーは、特定のワークベンチ・パーツに組み込むことができます。

IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// set an approver on this part that will prompt the user when the operation is not the most recent.
IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart);
history.addOperationApprover(approver);

プラグイン開発者は、アプリケーション固有の取り消しモデルおよび承認ストラテジーを実装するために、独自の操作アプルーバーを自由に開発し、インストールすることができます。

取り消しおよび IDE ワークベンチ

前に、ワークベンチ・プロトコルを使用して、操作ヒストリーおよびワークベンチ取り消しコンテキストにアクセスするコードの断片を取り上げました。これは、ワークベンチから取得可能な IWorkbenchOperationSupport を使用することで実現されます。ワークベンチ全体にわたる取り消しコンテキストの概念はかなり概括的です。ワークベンチ取り消しコンテキストで暗黙指定される特定のスコープを判別したり、取り消しサポートを提供する場合にどのビューまたはエディターがワークベンチ・コンテキストを使用するかを判別したりするのは、ワークベンチ・アプリケーションです。

Eclipse IDE ワークベンチの場合、IDE ワークスペース全体に影響を与える任意の操作にワークベンチ取り消しコンテキストを割り当てる必要があります。 このコンテキストは、リソース・ナビゲーターなどのワークスペースを操作するビューによって使用されます。IDE ワークベンチは、ワークベンチ取り消しコンテキストを戻す IUndoContext 用のワークスペース上にアダプターをインストールします。このモデル・ベースの登録により、ワークスペースを操作するプラグインは、ヘッドレスでどのワークベンチ・クラスも参照しない場合でも、適切な取り消しコンテキストを取得できるようになります。

// get the operation history
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// obtain the appropriate undo context for my model
IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class);
if (workspaceContext != null) {
	// create an operation and assign it the context
}

他のプラグインは、この同じ技法を使用して、モデル・ベースの取り消しコンテキストを登録することが推奨されています。