| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML> |
| <HEAD> |
| |
| <meta name="copyright" content="Copyright (c) IBM Corporation and others 2000, 2005. This page is made available under license. For full details see the LEGAL in the documentation book that contains this page." > |
| |
| <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1"> |
| <META HTTP-EQUIV="Content-Style-Type" CONTENT="text/css"> |
| |
| <LINK REL="STYLESHEET" HREF="../book.css" CHARSET="ISO-8859-1" TYPE="text/css"> |
| <TITLE> |
| Undoable operations |
| </TITLE> |
| |
| <link rel="stylesheet" type="text/css" HREF="../book.css"> |
| </HEAD> |
| <BODY BGCOLOR="#ffffff"> |
| <h2>Undoable operations</h2> |
| <p>We've looked at many different ways to contribute actions to the workbench, but we |
| haven't focused on the implementation of an action's <tt>run()</tt> method. The mechanics |
| of the method depend on the specific action in question, but structuring the code as an |
| <b>undoable operation</b> allows the action to participate in the platform undo and redo |
| support.</p> |
| <p> |
| The platform provides an <b>undoable operations framework</b> in the package |
| <b><a href="../reference/api/org/eclipse/core/commands/operations/package-summary.html">org.eclipse.core.commands.operations</a></b>. |
| By implementing the code inside a <tt>run()</tt> method to create an |
| <a href="../reference/api/org/eclipse/core/commands/operations/IUndoableOperation.html"><b>IUndoableOperation</b></a>, |
| the operation can be made available for undo and redo. Converting an action to use operations is straightforward, |
| apart from implementing the undo and redo behavior itself.</p> |
| <h3>Writing an undoable operation</h3> |
| <p> |
| We'll start by looking at a very simple example. Recall the simple |
| <b>ViewActionDelegate</b> provided in the readme example plug-in. When |
| invoked, the action simply launches a dialog that announces it was executed.</p> |
| <pre> |
| public void run(org.eclipse.jface.action.IAction action) { |
| MessageDialog.openInformation(view.getSite().getShell(), |
| MessageUtil.getString("Readme_Editor"), |
| MessageUtil.getString("View_Action_executed")); |
| }</pre> |
| |
| Using operations, the run method is responsible for creating an operation that does |
| the work formerly done in the run method, and requesting that an <b>operations history</b> |
| execute the operation, so that it can be remembered for undo and redo. |
| <pre> |
| public void run(org.eclipse.jface.action.IAction action) { |
| IUndoableOperation operation = new ReadmeOperation( |
| view.getSite().getShell()); |
| ... |
| operationHistory.execute(operation, null, null); |
| } |
| </pre> |
| The operation encapsulates the old behavior from the run method, as well as the undo |
| and redo for the operation. |
| <pre> |
| 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, IAdaptable info) { |
| MessageDialog.openInformation(shell, |
| MessageUtil.getString("Readme_Editor"), |
| "Undoing view action"); |
| return Status.OK_STATUS; |
| } |
| public IStatus redo(IProgressMonitor monitor, IAdaptable info) { |
| MessageDialog.openInformation(shell, |
| MessageUtil.getString("Readme_Editor"), |
| "Redoing view action"); |
| return Status.OK_STATUS; |
| } |
| } |
| </pre> |
| |
| <p> |
| For simple actions, it may be possible to move all of the nuts and bolt work into the operation class. |
| In this case, it may be appropriate to collapse the former action classes into a single action class |
| that is parameterized. The action would simply execute the supplied operation when it is time to run. |
| This is largely an application design decision. |
| </p> |
| <p> |
| When an action launches a wizard, then the operation is typically created as part of the wizard's |
| <tt>performFinish()</tt> method or a wizard page's <tt>finish()</tt> method. Converting the <tt>finish</tt> method |
| to use operations is similar to converting a <tt>run</tt> method. The method is responsible for creating |
| and executing an operation that does the work previously done inline. |
| </p> |
| <h3>Operation history</h3> |
| <p> |
| So far we've used an <b>operations history</b> without really explaining it. |
| Let's look again at the code that creates our example operation.</p> |
| <pre> |
| public void run(org.eclipse.jface.action.IAction action) { |
| IUndoableOperation operation = new ReadmeOperation( |
| view.getSite().getShell()); |
| ... |
| <b>operationHistory.execute(operation, null, null);</b> |
| } |
| </pre> |
| What is the <b>operation history</b> all about? |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationHistory.html"><b>IOperationHistory</b></a> |
| defines the interface for the object that keeps track of all of the undoable operations. When an operation history |
| executes an operation, it first executes the operation, and then adds it to the undo history. |
| Clients that wish to undo and redo operations do so by using |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationHistory.html"><b>IOperationHistory</b></a> |
| protocol. |
| |
| <p> |
| The operation history used by an application can be retrieved in several ways. The simplest way is to use the |
| <a href="../reference/api/org/eclipse/core/commands/operations/OperationHistoryFactory.html"><b>OperationHistoryFactory</b></a>.</p> |
| <pre>IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory(); |
| </pre> |
| |
| <p> |
| The workbench can also be used to retrieve the operations history. The workbench configures the default |
| operation history and also provides protocol to access it. The following snippet demonstrates how to |
| obtain the operation history from the workbench. </p> |
| <pre> |
| IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench(); |
| IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); |
| </pre> |
| Once an operation history is obtained, it can be used to query the undo or redo history, find out which operation |
| is the next in line for undo or redo, or to undo or redo particular operations. Clients can add an |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationHistoryListener.html"><b>IOperationHistoryListener</b></a> |
| in order to receive notifications about changes to the history. Other protocol allows clients |
| to set limits on the history or notify listeners about changes to a particular operation. Before |
| we look at the protocol in detail, we need to understand the <b>undo context</b>. |
| |
| <h4>Undo contexts</h4> |
| <p> |
| When an operation is created, it is assigned an <b>undo context</b> that describes the user context in which the original |
| operation was performed. The undo context typically depends on the view or editor that originated the undoable operation. For |
| example, changes made inside an editor are often local to that editor. In this case, the editor should create its own |
| own undo context and assign that context to operations it adds to the history. In this way, all of the operations performed in the |
| editor are considered local and semi-private. Editors or views that operate on a shared model often use an undo context that is related |
| to the model that they are manipulating. By using a more general undo context, operations performed by one view or editor |
| may be available for undo in another view or editor that operates on the same model.</p> |
| <p>Undo contexts are relatively simple in behavior; the protocol for |
| <a href="../reference/api/org/eclipse/core/commands/operations/IUndoContext.html"><b>IUndoContext</b></a> is fairly |
| minimal. The main role of a context is to "tag" a particular operation as belonging in that undo context, in order to distinguish |
| it from operations created in different undo contexts. This allows the operation history to keep track of the global history of all undoable |
| operations that have been executed, while views and editors can filter the history for a specific point of view using the undo context. |
| </p> |
| <p> |
| Undo contexts can be created by the plug-in that is creating the undoable operations, or accessed through API. For |
| example, the workbench provides access to an undo context that can be used for workbench-wide operations. However they |
| are obtained, undo contexts should be assigned when an operation is created. The following snippet shows how the |
| readme plug-in's <b>ViewActionDelegate</b> could assign a workbench-wide context to its operations. |
| </p> |
| <pre> |
| 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); |
| } |
| </pre> |
| <p> |
| Why use undo contexts at all? Why not use separate operation histories for separate views and editors? Using separate operation |
| histories assumes that any particular view or editor maintains its own private undo history, and that undo has no global meaning |
| in the application. This may be appropriate for some applications, and in these cases each view or editor should create its own |
| separate undo context. Other applications may wish to implement a global undo that applies to all user operations, regardless of |
| the view or editor where they originated. In this case, the workbench context should be used by all plug-ins that add operations |
| to the history. </p> |
| <p>In more complicated applications, the undo is neither strictly local or strictly global. Instead, there is some |
| cross-over between undo contexts. This can be achieved by assigning multiple contexts to an operation. For example, an IDE workbench view may |
| manipulate the entire workspace and consider the workspace its undo context. An editor that is open on a particular |
| resource in the workspace may consider its operations mostly local. However, operations performed inside the editor may |
| in fact affect both the particular resource and the workspace at large. (A good example of this case is the JDT refactoring support, |
| which allows structural changes to a Java element to occur while editing the source file). In these cases, it is useful to be able to add both |
| undo contexts to the operation so that the undo can be performed from the editor itself, as well as those views that manipulate the |
| workspace. |
| </p> |
| <p> |
| Now that we understand what an undo context does, we can look again at the protocol for |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationHistory.html"><b>IOperationHistory</b></a>. |
| The following snippet is used to perform an undo on the some context:</p> |
| <pre>IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); |
| try { |
| IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo); |
| } catch (ExecutionException e) { |
| // handle the exception |
| } |
| </pre> |
| The history will obtain the most recently performed operation that has the given context and ask it to undo |
| itself. Other protocol can be used to get the entire undo or redo history for a context, or to find the operation |
| that will be undone or redone in a partcular context. The following snippet obtains the label for the operation |
| that will be undone in a particular context. |
| <pre> |
| IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); |
| String label = history.getUndoOperation(myContext).getLabel(); |
| </pre> |
| |
| <p> |
| The global undo context, <b>IOperationHistory.GLOBAL_UNDO_CONTEXT</b>, may be used to refer to the global |
| undo history. That is, to all of the operations in the history regardless of their specific context. The |
| following snippet obtains the global undo history. |
| </p> |
| <pre> |
| IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); |
| IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT); |
| </pre> |
| <p> |
| Whenever an operation is executed, undone, or redone using operation history protocol, clients can |
| provide a progress monitor and any additional UI info that may be needed for performing the operation. |
| This information is passed to the operation itself. In our original |
| example, the readme action constructed an operation with a shell parameter that could be used to open |
| the dialog. Instead of storing the shell in the operation, a better approach is to pass parameters |
| to the execute, undo, and redo methods that provide any UI information needed to run the operation. These |
| parameters will be passed on to the operation itself.</p> |
| <pre>public void run(org.eclipse.jface.action.IAction action) { |
| IUndoableOperation operation = new ReadmeOperation(); |
| ... |
| <b>operationHistory.execute(operation, null, infoAdapter);</b> |
| } |
| </pre> |
| The <b>infoAdapter</b> is an <a href="../reference/api/org/eclipse/core/runtime/IAdaptable.html"><b>IAdaptable</b></a> |
| that minimally can provide the <a href="../reference/api/org/eclipse/swt/widgets/Shell.html"><b>Shell</b></a> that can |
| be used when launching dialogs. Our example operation would use this parameter as follows: |
| <pre> |
| 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... |
| } |
| </pre> |
| |
| <h3>Undo and redo action handlers</h3> |
| <p>The platform provides standard undo and redo <a href="wrkAdv_retarget.htm"><b>retargetable action handlers</b></a> |
| that can be configured by views and editors to provide undo and redo support for their particular context. When the |
| action handler is created, a context is assigned to it so that the operations history is filtered in a way appropriate for |
| that particular view. The action handlers take care of updating the undo and redo labels to show the current |
| operation in question, providing the appropriate progress monitor and UI info to the operation history, and |
| optionally pruning the history when the current operation is invalid. An action group that creates the action handlers |
| and assigns them to the global undo and redo actions is provided for convenience.</p> |
| <pre>new UndoRedoActionGroup(this.getSite(), undoContext, true); |
| </pre> |
| The last parameter is a boolean indicating whether the undo and redo histories for the specified context should |
| be disposed when the operation currently available for undo or redo is not valid. The setting for this parameter |
| is related to the undo context provided and the validation strategy used by operations with that context. |
| |
| <h3>Application undo models</h3> |
| <p> |
| Earlier we looked at how undo contexts can be used to implement different kinds of application undo models. |
| The ability to assign one or more contexts to operations allows applications to implement undo strategies that |
| are strictly local to each view or editor, strictly global across all plug-ins, or some model in between. |
| Another design decision involving undo and redo is whether any operation can be undone or redone at any time, |
| or whether the model is strictly linear, with only the most recent operation being considered for undo or |
| redo. |
| </p> |
| <p> |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationHistory.html"><b>IOperationHistory</b></a> |
| defines protocol that allows flexible undo models, leaving it up to individual implementations to determine what is allowed. |
| The undo and redo protocol we've seen so far assumes that there is only one implied operation available for undo or redo in a particular |
| undo context. Additional protocol is provided to allow clients to execute a specific operation, regardless |
| of its position in the history. The operation history can be configured so that the model appropriate for an |
| application can be implemented. This is done with an interface that is used to pre-approve any undo or redo request |
| before the operation is undone or redone.</p> |
| <h4>Operation approvers</h4> |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationApprover.html"><b>IOperationApprover</b></a> |
| defines the protocol for approving undo and redo of a particular operation. An operation approver is installed on an operation history. Specific |
| operation approvers may in turn check all operations for their validity, check operations of only certain contexts, |
| or prompt the user when unexpected conditions are found in an operation. |
| The following snippet shows how an application could configure the operation history to enforce a linear undo model for all |
| operations. |
| <pre> |
| 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()); |
| |
| </pre> |
| <p> |
| In this case, an operation approver provided by the framework, |
| <a href="../reference/api/org/eclipse/core/commands/operations/LinearUndoEnforcer.html"><b>LinearUndoEnforcer</b></a>, |
| is installed on the history to prevent the undo or redo of any operation that is not the most recently done or undone |
| operation in all of its undo contexts.</p> |
| <p> |
| Another operation approver, |
| <a href="../reference/api/org/eclipse/ui/operations/LinearUndoViolationUserApprover.html"><b>LinearUndoViolationUserApprover</b></a>, |
| detects the same condition and prompts the user as to whether the operation should be allowed to continue. This operation |
| approver can be installed on a particular workbench part.</p> |
| <pre>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); |
| </pre> |
| |
| <p> |
| Plug-in developers are free to develop and install their own operation approvers for implementing |
| application-specific undo models and approval strategies. In your plug-in, it may be appropriate to seek |
| approval for the original execution of an operation, in addition to the undo and redo of the operation. |
| If this is the case, your operation approver should also implement |
| <a href="../reference/api/org/eclipse/core/commands/operations/IOperationApprover2.html"><b>IOperationApprover2</b></a>, |
| which approves the execution of the operation. When asked to execute an operation, the platform operation history |
| will seek approval from any operation approver that implements this interface. </p> |
| </BODY> |
| </HTML> |