KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > operations > OperationHistoryActionHandler


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.ui.operations;
12
13 import java.lang.reflect.InvocationTargetException JavaDoc;
14
15 import org.eclipse.core.commands.ExecutionException;
16 import org.eclipse.core.commands.operations.IAdvancedUndoableOperation2;
17 import org.eclipse.core.commands.operations.IOperationHistory;
18 import org.eclipse.core.commands.operations.IOperationHistoryListener;
19 import org.eclipse.core.commands.operations.IUndoContext;
20 import org.eclipse.core.commands.operations.IUndoableOperation;
21 import org.eclipse.core.commands.operations.OperationHistoryEvent;
22 import org.eclipse.core.runtime.IAdaptable;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.OperationCanceledException;
26 import org.eclipse.jface.action.Action;
27 import org.eclipse.jface.operation.IRunnableWithProgress;
28 import org.eclipse.osgi.util.NLS;
29 import org.eclipse.swt.widgets.Display;
30 import org.eclipse.swt.widgets.Shell;
31 import org.eclipse.ui.IPartListener;
32 import org.eclipse.ui.IWorkbenchPart;
33 import org.eclipse.ui.IWorkbenchPartSite;
34 import org.eclipse.ui.IWorkbenchWindow;
35 import org.eclipse.ui.PlatformUI;
36 import org.eclipse.ui.actions.ActionFactory;
37 import org.eclipse.ui.internal.WorkbenchMessages;
38 import org.eclipse.ui.internal.WorkbenchPlugin;
39 import org.eclipse.ui.internal.misc.StatusUtil;
40 import org.eclipse.ui.internal.operations.TimeTriggeredProgressMonitorDialog;
41 import org.eclipse.ui.internal.util.Util;
42 import org.eclipse.ui.part.MultiPageEditorSite;
43 import org.eclipse.ui.statushandlers.StatusManager;
44
45 /**
46  * <p>
47  * OperationHistoryActionHandler implements common behavior for the undo and
48  * redo actions. It supports filtering of undo or redo on a particular undo
49  * context. If an undo context is not specified, or there has been no history
50  * available for the specified undo context, then the workbench undo context
51  * will be used.
52  * </p>
53  * <p>
54  * OperationHistoryActionHandler provides an adapter in the info parameter of
55  * the IOperationHistory undo and redo methods that is used to get UI info for
56  * prompting the user during operations or operation approval. Adapters are
57  * provided for org.eclipse.ui.IWorkbenchWindow, org.eclipse.swt.widgets.Shell,
58  * org.eclipse.ui.IWorkbenchPart, org.eclipse.core.commands.IUndoContext, and
59  * org.eclipse.runtime.IProgressMonitor.
60  * </p>
61  * <p>
62  * OperationHistoryActionHandler assumes a linear undo/redo model. When the
63  * handler is run, the operation history is asked to perform the most recent
64  * undo/redo for the handler's undo context. The handler can be configured
65  * (using #setPruneHistory(true)) to flush the operation undo or redo history
66  * for the handler's undo context when there is no valid operation on top of the
67  * history. This avoids keeping a stale history of invalid operations. By
68  * default, pruning does not occur and it is assumed that clients of the
69  * particular undo context are pruning the history when necessary.
70  * </p>
71  *
72  * @since 3.1
73  */

74 public abstract class OperationHistoryActionHandler extends Action implements
75         ActionFactory.IWorkbenchAction, IAdaptable {
76
77     private static final int MAX_LABEL_LENGTH = 32;
78
79     private class PartListener implements IPartListener {
80         /**
81          * @see IPartListener#partActivated(IWorkbenchPart)
82          */

83         public void partActivated(IWorkbenchPart part) {
84         }
85
86         /**
87          * @see IPartListener#partBroughtToTop(IWorkbenchPart)
88          */

89         public void partBroughtToTop(IWorkbenchPart part) {
90         }
91
92         /**
93          * @see IPartListener#partClosed(IWorkbenchPart)
94          */

95         public void partClosed(IWorkbenchPart part) {
96             if (part.equals(site.getPart())) {
97                 dispose();
98                 // Special case for MultiPageEditorSite
99
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=103379
100
} else if ((site instanceof MultiPageEditorSite)
101                     && (part.equals(((MultiPageEditorSite) site)
102                             .getMultiPageEditor()))) {
103                 dispose();
104             }
105         }
106
107         /**
108          * @see IPartListener#partDeactivated(IWorkbenchPart)
109          */

110         public void partDeactivated(IWorkbenchPart part) {
111         }
112
113         /**
114          * @see IPartListener#partOpened(IWorkbenchPart)
115          */

116         public void partOpened(IWorkbenchPart part) {
117         }
118
119     }
120
121     private class HistoryListener implements IOperationHistoryListener {
122         public void historyNotification(final OperationHistoryEvent event) {
123             Display display = getWorkbenchWindow().getWorkbench().getDisplay();
124             switch (event.getEventType()) {
125             case OperationHistoryEvent.OPERATION_ADDED:
126             case OperationHistoryEvent.OPERATION_REMOVED:
127             case OperationHistoryEvent.UNDONE:
128             case OperationHistoryEvent.REDONE:
129                 if (display != null
130                         && event.getOperation().hasContext(undoContext)) {
131                     contextActive = true;
132                     display.asyncExec(new Runnable JavaDoc() {
133                         public void run() {
134                             update();
135                         }
136                     });
137                 }
138                 break;
139             case OperationHistoryEvent.OPERATION_NOT_OK:
140                 if (display != null
141                         && event.getOperation().hasContext(undoContext)) {
142                     contextActive = true;
143                     display.asyncExec(new Runnable JavaDoc() {
144                         public void run() {
145                             if (pruning) {
146                                 IStatus status = event.getStatus();
147                                 /*
148                                  * Prune the history unless we can determine
149                                  * that this was a cancelled attempt. See
150                                  * https://bugs.eclipse.org/bugs/show_bug.cgi?id=101215
151                                  */

152                                 if (status == null
153                                         || status.getSeverity() != IStatus.CANCEL) {
154                                     flush();
155                                 }
156                                 // not all flushes will trigger an update so
157
// force it here
158
update();
159                             } else {
160                                 update();
161                             }
162                         }
163                     });
164                 }
165                 break;
166             case OperationHistoryEvent.OPERATION_CHANGED:
167                 if (event.getOperation().hasContext(undoContext)) {
168                     contextActive = true;
169                 }
170                 if (display != null && event.getOperation() == getOperation()) {
171                     display.asyncExec(new Runnable JavaDoc() {
172                         public void run() {
173                             update();
174                         }
175                     });
176                 }
177                 break;
178             default:
179                 if (event.getOperation().hasContext(undoContext)) {
180                     contextActive = true;
181                 }
182                 break;
183             }
184         }
185     }
186
187     private boolean contextActive = false;
188
189     private boolean pruning = false;
190
191     private IPartListener partListener = new PartListener();
192
193     private IOperationHistoryListener historyListener = new HistoryListener();
194
195     private TimeTriggeredProgressMonitorDialog progressDialog;
196
197     private IUndoContext undoContext = null;
198
199     IWorkbenchPartSite site;
200
201     /**
202      * Construct an operation history action for the specified workbench window
203      * with the specified undo context.
204      *
205      * @param site -
206      * the workbench part site for the action.
207      * @param context -
208      * the undo context to be used
209      */

210     OperationHistoryActionHandler(IWorkbenchPartSite site, IUndoContext context) {
211         // string will be reset inside action
212
super(""); //$NON-NLS-1$
213
this.site = site;
214         undoContext = context;
215         checkUndoContext();
216         site.getPage().addPartListener(partListener);
217         getHistory().addOperationHistoryListener(historyListener);
218         // An update must be forced in case the undo limit is 0.
219
// see bug #89707
220
update();
221     }
222
223     /*
224      * (non-Javadoc)
225      *
226      * @see org.eclipse.ui.actions.ActionFactory.IWorkbenchAction#dispose()
227      */

228     public void dispose() {
229
230         IOperationHistory history = getHistory();
231         if (history != null) {
232             history.removeOperationHistoryListener(historyListener);
233         }
234
235         if (isInvalid()) {
236             return;
237         }
238
239         site.getPage().removePartListener(partListener);
240         site = null;
241         progressDialog = null;
242         // We do not flush the history for our undo context because it may be
243
// used elsewhere. It is up to clients to clean up the history
244
// appropriately.
245
// We do null out the context to signify that this handler is no longer
246
// accessing the history.
247
undoContext = null;
248     }
249
250     /*
251      * Flush the history associated with this action.
252      */

253     abstract void flush();
254
255     /*
256      * Return the string describing the command, including the binding for the
257      * operation label.
258      */

259     abstract String JavaDoc getCommandString();
260
261     /*
262      * Return the string describing the command for a tooltip, including the
263      * binding for the operation label.
264      */

265     abstract String JavaDoc getTooltipString();
266
267     /*
268      * Return the simple string describing the command, with no binding to any
269      * operation.
270      */

271     abstract String JavaDoc getSimpleCommandString();
272
273     /*
274      * Return the simple string describing the tooltip, with no binding to any
275      * operation.
276      */

277     abstract String JavaDoc getSimpleTooltipString();
278
279     /*
280      * Return the operation history we are using.
281      */

282     IOperationHistory getHistory() {
283         if (PlatformUI.getWorkbench() == null) {
284             return null;
285         }
286
287         return PlatformUI.getWorkbench().getOperationSupport()
288                 .getOperationHistory();
289     }
290
291     /*
292      * Return the current operation.
293      */

294     abstract IUndoableOperation getOperation();
295
296     /*
297      * (non-Javadoc)
298      *
299      * @see org.eclipse.ui.actions.ActionFactory.IWorkbenchAction#run()
300      */

301     public final void run() {
302         if (isInvalid()) {
303             return;
304         }
305
306         Shell parent = getWorkbenchWindow().getShell();
307         progressDialog = new TimeTriggeredProgressMonitorDialog(parent,
308                 getWorkbenchWindow().getWorkbench().getProgressService()
309                         .getLongOperationTime());
310         IRunnableWithProgress runnable = new IRunnableWithProgress() {
311             public void run(IProgressMonitor pm)
312                     throws InvocationTargetException JavaDoc {
313                 try {
314                     runCommand(pm);
315                 } catch (ExecutionException e) {
316                     if (pruning) {
317                         flush();
318                     }
319                     throw new InvocationTargetException JavaDoc(e);
320                 }
321             }
322         };
323         try {
324             boolean runInBackground = false;
325             if (getOperation() instanceof IAdvancedUndoableOperation2) {
326                 runInBackground = ((IAdvancedUndoableOperation2) getOperation())
327                         .runInBackground();
328             }
329             progressDialog.run(runInBackground, true, runnable);
330         } catch (InvocationTargetException JavaDoc e) {
331             Throwable JavaDoc t = e.getTargetException();
332             if (t == null) {
333                 reportException(e);
334             } else {
335                 reportException(t);
336             }
337         } catch (InterruptedException JavaDoc e) {
338             // Operation was cancelled and acknowledged by runnable with this
339
// exception.
340
// Do nothing.
341
} catch (OperationCanceledException e) {
342             // the operation was cancelled. Do nothing.
343
} finally {
344             progressDialog = null;
345         }
346     }
347
348     abstract IStatus runCommand(IProgressMonitor pm) throws ExecutionException;
349
350     /*
351      * (non-Javadoc)
352      *
353      * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
354      */

355     public Object JavaDoc getAdapter(Class JavaDoc adapter) {
356         if (adapter.equals(IUndoContext.class)) {
357             return undoContext;
358         }
359         if (adapter.equals(IProgressMonitor.class)) {
360             if (progressDialog != null) {
361                 return progressDialog.getProgressMonitor();
362             }
363         }
364         if (site != null) {
365             if (adapter.equals(Shell.class)) {
366                 return getWorkbenchWindow().getShell();
367             }
368             if (adapter.equals(IWorkbenchWindow.class)) {
369                 return getWorkbenchWindow();
370             }
371             if (adapter.equals(IWorkbenchPart.class)) {
372                 return site.getPart();
373             }
374             // Refer all other requests to the part itself.
375
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=108144
376
IWorkbenchPart part = site.getPart();
377             if (part != null) {
378                 return Util.getAdapter(part, adapter);
379             }
380         }
381         return null;
382     }
383
384     /*
385      * Return the workbench window for this action handler
386      */

387     private IWorkbenchWindow getWorkbenchWindow() {
388         if (site != null) {
389             return site.getWorkbenchWindow();
390         }
391         return null;
392     }
393
394     /**
395      * The undo and redo subclasses should implement this.
396      *
397      * @return - a boolean indicating enablement state
398      */

399     abstract boolean shouldBeEnabled();
400
401     /**
402      * Set the context shown by the handler. Normally the context is set up when
403      * the action handler is created, but the context can also be changed
404      * dynamically.
405      *
406      * @param context
407      * the context to be used for the undo history
408      */

409     public void setContext(IUndoContext context) {
410         // optimization - do nothing if there was no real change
411
if (context == undoContext) {
412             return;
413         }
414         undoContext = context;
415         checkUndoContext();
416         update();
417     }
418
419     /**
420      * Specify whether the action handler should actively prune the operation
421      * history when invalid operations are encountered. The default value is
422      * <code>false</code>.
423      *
424      * @param prune
425      * <code>true</code> if the history should be pruned by the
426      * handler, and <code>false</code> if it should not.
427      *
428      */

429     public void setPruneHistory(boolean prune) {
430         pruning = prune;
431     }
432
433     /**
434      * Update enabling and labels according to the current status of the
435      * operation history.
436      */

437     public void update() {
438         if (isInvalid()) {
439             return;
440         }
441
442         boolean enabled = shouldBeEnabled();
443         String JavaDoc text, tooltipText;
444         if (enabled) {
445             tooltipText = NLS.bind(getTooltipString(), getOperation()
446                     .getLabel());
447             text = NLS.bind(getCommandString(), shortenText(getOperation()
448                     .getLabel()));
449         } else {
450             tooltipText = NLS.bind(
451                     WorkbenchMessages.Operations_undoRedoCommandDisabled,
452                     getSimpleTooltipString());
453             text = getSimpleCommandString();
454             /*
455              * if there is nothing to do and we are pruning the history, flush
456              * the history of this context.
457              */

458             if (undoContext != null && pruning) {
459                 flush();
460             }
461         }
462         setText(text);
463         setToolTipText(tooltipText);
464         setEnabled(enabled);
465     }
466
467     /*
468      * Shorten the specified command label if it is too long
469      */

470     private String JavaDoc shortenText(String JavaDoc message) {
471         int length = message.length();
472         if (length > MAX_LABEL_LENGTH) {
473             StringBuffer JavaDoc result = new StringBuffer JavaDoc();
474             int mid = MAX_LABEL_LENGTH / 2;
475             result.append(message.substring(0, mid));
476             result.append("..."); //$NON-NLS-1$
477
result.append(message.substring(length - mid));
478             return result.toString();
479         }
480         return message;
481     }
482
483     /*
484      * Report the specified exception to the log and to the user.
485      */

486     final void reportException(Throwable JavaDoc t) {
487         // get any nested exceptions
488
Throwable JavaDoc nestedException = StatusUtil.getCause(t);
489         Throwable JavaDoc exception = (nestedException == null) ? t : nestedException;
490
491         // Messages
492
String JavaDoc exceptionMessage = exception.getMessage();
493         if (exceptionMessage == null) {
494             exceptionMessage = WorkbenchMessages.WorkbenchWindow_exceptionMessage;
495         }
496         IStatus status = StatusUtil.newStatus(WorkbenchPlugin.PI_WORKBENCH,
497                 exceptionMessage, exception);
498
499         // Log and show the problem
500
WorkbenchPlugin.log(exceptionMessage, status);
501         StatusUtil.handleStatus(status, StatusManager.SHOW,
502                 getWorkbenchWindow().getShell());
503     }
504
505     /*
506      * Answer true if the receiver is not valid for running commands, accessing
507      * the history, etc.
508      */

509     final boolean isInvalid() {
510         return undoContext == null || site == null;
511     }
512
513     /*
514      * Get the undo context that should be used.
515      */

516     final IUndoContext getUndoContext() {
517         // If no context was specified, or the specified one is not
518
// in use, use the workbench context.
519
if (undoContext == null || !contextActive) {
520             return PlatformUI.getWorkbench().getOperationSupport()
521                     .getUndoContext();
522         }
523         return undoContext;
524     }
525
526     /*
527      * The undo context has been set. Check whether there is undo or redo
528      * history available and set the contextActive flag accordingly. We check
529      * both undo and redo here because we don't ever want the undo/redo action
530      * handlers to have different values for contextActive.
531      */

532     private void checkUndoContext() {
533         if (undoContext == null) {
534             return;
535         }
536         contextActive = getHistory().getUndoOperation(undoContext) != null
537                 || getHistory().getRedoOperation(undoContext) != null;
538     }
539 }
540
Popular Tags