001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.operations;
011:
012: import java.lang.reflect.InvocationTargetException;
013:
014: import org.eclipse.core.commands.ExecutionException;
015: import org.eclipse.core.commands.operations.IAdvancedUndoableOperation;
016: import org.eclipse.core.commands.operations.IAdvancedUndoableOperation2;
017: import org.eclipse.core.commands.operations.IOperationApprover;
018: import org.eclipse.core.commands.operations.IOperationApprover2;
019: import org.eclipse.core.commands.operations.IOperationHistory;
020: import org.eclipse.core.commands.operations.IUndoContext;
021: import org.eclipse.core.commands.operations.IUndoableOperation;
022: import org.eclipse.core.runtime.IAdaptable;
023: import org.eclipse.core.runtime.IProgressMonitor;
024: import org.eclipse.core.runtime.IStatus;
025: import org.eclipse.core.runtime.OperationCanceledException;
026: import org.eclipse.core.runtime.Status;
027: import org.eclipse.jface.dialogs.ErrorDialog;
028: import org.eclipse.jface.dialogs.IDialogConstants;
029: import org.eclipse.jface.dialogs.MessageDialog;
030: import org.eclipse.jface.operation.IRunnableWithProgress;
031: import org.eclipse.jface.window.Window;
032: import org.eclipse.osgi.util.NLS;
033: import org.eclipse.swt.widgets.Shell;
034: import org.eclipse.ui.PlatformUI;
035: import org.eclipse.ui.internal.Workbench;
036: import org.eclipse.ui.internal.WorkbenchMessages;
037: import org.eclipse.ui.internal.WorkbenchPlugin;
038: import org.eclipse.ui.internal.misc.StatusUtil;
039: import org.eclipse.ui.internal.util.Util;
040:
041: /**
042: * <p>
043: * An operation approver that rechecks the validity of a proposed undo or redo
044: * operation using
045: * {@link IAdvancedUndoableOperation#computeUndoableStatus(IProgressMonitor)} or
046: * {@link IAdvancedUndoableOperation#computeRedoableStatus(IProgressMonitor)}.
047: * Some complex operations do not compute their validity in canUndo() or
048: * canRedo() because it is too time-consuming. To save time on complex
049: * validations, the true validity is not determined until it is time to perform
050: * the operation.
051: * </p>
052: * <p>
053: * Since 3.3, this operation approver also checks the validity of a proposed
054: * execute by determining whether the redo is viable.
055: *
056: * @since 3.1
057: */
058: public class AdvancedValidationUserApprover implements
059: IOperationApprover, IOperationApprover2 {
060:
061: /**
062: * Static to prevent opening of error dialogs for automated testing.
063: *
064: * @since 3.3
065: */
066: public static boolean AUTOMATED_MODE = false;
067:
068: private IUndoContext context;
069:
070: private static final int EXECUTING = 1;
071:
072: private static final int UNDOING = 2;
073:
074: private static final int REDOING = 3;
075:
076: private class StatusReportingRunnable implements
077: IRunnableWithProgress {
078: IStatus status;
079:
080: int doing;
081:
082: IUndoableOperation operation;
083:
084: IOperationHistory history;
085:
086: IAdaptable uiInfo;
087:
088: StatusReportingRunnable(IUndoableOperation operation,
089: IOperationHistory history, IAdaptable uiInfo, int doing) {
090: super ();
091: this .operation = operation;
092: this .history = history;
093: this .doing = doing;
094: this .uiInfo = uiInfo;
095: }
096:
097: // The casts to IAdvancedUndoableOperation and
098: // IAdvancedUndoableOperation2 are safe because these types were checked
099: // in the call chain.
100: public void run(IProgressMonitor pm) {
101: try {
102: switch (doing) {
103: case UNDOING:
104: status = ((IAdvancedUndoableOperation) operation)
105: .computeUndoableStatus(pm);
106: break;
107: case REDOING:
108: status = ((IAdvancedUndoableOperation) operation)
109: .computeRedoableStatus(pm);
110: break;
111: case EXECUTING:
112: status = ((IAdvancedUndoableOperation2) operation)
113: .computeExecutionStatus(pm);
114: break;
115: }
116:
117: } catch (ExecutionException e) {
118: reportException(e, uiInfo);
119: status = IOperationHistory.OPERATION_INVALID_STATUS;
120: }
121: }
122:
123: IStatus getStatus() {
124: return status;
125: }
126: }
127:
128: /**
129: * Create an AdvancedValidationUserApprover that performs advanced
130: * validations on proposed undo and redo operations for a given undo
131: * context.
132: *
133: * @param context -
134: * the undo context of operations in question.
135: */
136: public AdvancedValidationUserApprover(IUndoContext context) {
137: super ();
138: this .context = context;
139: }
140:
141: /*
142: * (non-Javadoc)
143: *
144: * @see org.eclipse.core.commands.operations.IOperationApprover#proceedRedoing(org.eclipse.core.commands.operations.IUndoableOperation,
145: * org.eclipse.core.commands.operations.IOperationHistory,
146: * org.eclipse.core.runtime.IAdaptable)
147: */
148: public IStatus proceedRedoing(IUndoableOperation operation,
149: IOperationHistory history, IAdaptable uiInfo) {
150: return proceedWithOperation(operation, history, uiInfo, REDOING);
151: }
152:
153: /*
154: * (non-Javadoc)
155: *
156: * @see org.eclipse.core.commands.operations.IOperationApprover#proceedUndoing(org.eclipse.core.commands.operations.IUndoableOperation,
157: * org.eclipse.core.commands.operations.IOperationHistory,
158: * org.eclipse.core.runtime.IAdaptable)
159: */
160: public IStatus proceedUndoing(IUndoableOperation operation,
161: IOperationHistory history, IAdaptable uiInfo) {
162:
163: return proceedWithOperation(operation, history, uiInfo, UNDOING);
164: }
165:
166: /*
167: * (non-Javadoc)
168: *
169: * @see org.eclipse.core.commands.operations.IOperationApprover2#proceedExecuting(org.eclipse.core.commands.operations.IUndoableOperation,
170: * org.eclipse.core.commands.operations.IOperationHistory,
171: * org.eclipse.core.runtime.IAdaptable)
172: */
173: public IStatus proceedExecuting(IUndoableOperation operation,
174: IOperationHistory history, IAdaptable uiInfo) {
175: return proceedWithOperation(operation, history, uiInfo,
176: EXECUTING);
177: }
178:
179: /*
180: * Determine whether the operation in question is still valid.
181: */
182: private IStatus proceedWithOperation(
183: final IUndoableOperation operation,
184: final IOperationHistory history, final IAdaptable uiInfo,
185: final int doing) {
186:
187: // return immediately if the operation is not relevant
188: if (!operation.hasContext(context)) {
189: return Status.OK_STATUS;
190: }
191:
192: // if the operation does not support advanced validation,
193: // then we assume it is valid.
194: if (doing == EXECUTING) {
195: if (!(operation instanceof IAdvancedUndoableOperation2)) {
196: return Status.OK_STATUS;
197: }
198: } else {
199: if (!(operation instanceof IAdvancedUndoableOperation)) {
200: return Status.OK_STATUS;
201: }
202: }
203:
204: // The next two methods make a number of UI calls, so we wrap the
205: // whole thing up in a syncExec.
206: final IStatus[] status = new IStatus[1];
207: Workbench.getInstance().getDisplay().syncExec(new Runnable() {
208: public void run() {
209: // Compute the undoable or redoable status
210: status[0] = computeOperationStatus(operation, history,
211: uiInfo, doing);
212:
213: // Report non-OK statuses to the user. In some cases, the user
214: // may choose to proceed, and the returned status will be
215: // different than what is reported.
216: if (!status[0].isOK()) {
217: status[0] = reportAndInterpretStatus(status[0],
218: uiInfo, operation, doing);
219: }
220:
221: }
222: });
223:
224: // If the operation is still not OK, inform the history that the
225: // operation has changed, since it was previously believed to be valid.
226: // We rely here on the ability of an IAdvancedUndoableOperation to
227: // correctly report canUndo() and canRedo() once the undoable and
228: // redoable status have been computed.
229: if (!status[0].isOK()) {
230: history.operationChanged(operation);
231: }
232: return status[0];
233: }
234:
235: private IStatus computeOperationStatus(
236: IUndoableOperation operation, IOperationHistory history,
237: IAdaptable uiInfo, int doing) {
238: try {
239: StatusReportingRunnable runnable = new StatusReportingRunnable(
240: operation, history, uiInfo, doing);
241: TimeTriggeredProgressMonitorDialog progressDialog = new TimeTriggeredProgressMonitorDialog(
242: getShell(uiInfo), PlatformUI.getWorkbench()
243: .getProgressService()
244: .getLongOperationTime());
245:
246: progressDialog.run(false, true, runnable);
247: return runnable.getStatus();
248: } catch (OperationCanceledException e) {
249: return Status.CANCEL_STATUS;
250: } catch (InvocationTargetException e) {
251: reportException(e, uiInfo);
252: return IOperationHistory.OPERATION_INVALID_STATUS;
253: } catch (InterruptedException e) {
254: // Operation was cancelled and acknowledged by runnable with this
255: // exception. Do nothing.
256: return Status.CANCEL_STATUS;
257: }
258: }
259:
260: /*
261: * Report the specified execution exception to the log and to the user.
262: */
263: private void reportException(Exception e, IAdaptable uiInfo) {
264: Throwable nestedException = StatusUtil.getCause(e);
265: Throwable exception = (nestedException == null) ? e
266: : nestedException;
267: String title = WorkbenchMessages.Error;
268: String message = WorkbenchMessages.WorkbenchWindow_exceptionMessage;
269: String exceptionMessage = exception.getMessage();
270: if (exceptionMessage == null) {
271: exceptionMessage = message;
272: }
273: IStatus status = new Status(IStatus.ERROR,
274: WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage,
275: exception);
276: WorkbenchPlugin.log(message, status);
277:
278: boolean createdShell = false;
279: Shell shell = getShell(uiInfo);
280: if (shell == null) {
281: createdShell = true;
282: shell = new Shell();
283: }
284: ErrorDialog.openError(shell, title, message, status);
285: if (createdShell) {
286: shell.dispose();
287: }
288: }
289:
290: /*
291: * Report a non-OK status to the user
292: */
293: private IStatus reportAndInterpretStatus(IStatus status,
294: IAdaptable uiInfo, IUndoableOperation operation, int doing) {
295: // Nothing to report if we are running automated tests. We will treat
296: // warnings as if they were authorized by the user.
297: if (AUTOMATED_MODE) {
298: if (status.getSeverity() == IStatus.WARNING) {
299: return Status.OK_STATUS;
300: }
301: return status;
302: }
303:
304: // CANCEL status is assumed to be initiated by the user, so there
305: // is nothing to report.
306: if (status.getSeverity() == IStatus.CANCEL) {
307: return status;
308: }
309:
310: // Other status severities are reported with a message dialog.
311: // First obtain a shell and set up the dialog title.
312: boolean createdShell = false;
313: IStatus reportedStatus = status;
314:
315: Shell shell = getShell(uiInfo);
316: if (shell == null) {
317: createdShell = true;
318: shell = new Shell();
319: }
320:
321: // Set up the dialog. For non-error statuses, we use a warning dialog
322: // that allows the user to proceed or to cancel out of the operation.
323:
324: if (!(status.getSeverity() == IStatus.ERROR)) {
325: String warning, title;
326: switch (doing) {
327: case UNDOING:
328: warning = WorkbenchMessages.Operations_proceedWithNonOKUndoStatus;
329: if (status.getSeverity() == IStatus.INFO) {
330: title = WorkbenchMessages.Operations_undoInfo;
331: } else {
332: title = WorkbenchMessages.Operations_undoWarning;
333: }
334: break;
335: case REDOING:
336: warning = WorkbenchMessages.Operations_proceedWithNonOKRedoStatus;
337: if (status.getSeverity() == IStatus.INFO) {
338: title = WorkbenchMessages.Operations_redoInfo;
339: } else {
340: title = WorkbenchMessages.Operations_redoWarning;
341: }
342: break;
343: default: // EXECUTING
344: warning = WorkbenchMessages.Operations_proceedWithNonOKExecuteStatus;
345: if (status.getSeverity() == IStatus.INFO) {
346: title = WorkbenchMessages.Operations_executeInfo;
347: } else {
348: title = WorkbenchMessages.Operations_executeWarning;
349: }
350: break;
351: }
352:
353: String message = NLS.bind(warning, new Object[] {
354: status.getMessage(), operation.getLabel() });
355: String[] buttons = new String[] {
356: IDialogConstants.YES_LABEL,
357: IDialogConstants.NO_LABEL };
358: MessageDialog dialog = new MessageDialog(shell, title,
359: null, message, MessageDialog.WARNING, buttons, 0);
360: int dialogAnswer = dialog.open();
361: // The user has been given the specific status and has chosen
362: // to proceed or to cancel. The user choice determines what
363: // the status should be at this point, OK or CANCEL.
364: if (dialogAnswer == Window.OK) {
365: reportedStatus = Status.OK_STATUS;
366: } else {
367: reportedStatus = Status.CANCEL_STATUS;
368: }
369: } else {
370: String title, stopped;
371: switch (doing) {
372: case UNDOING:
373: title = WorkbenchMessages.Operations_undoProblem;
374: stopped = WorkbenchMessages.Operations_stoppedOnUndoErrorStatus;
375: break;
376: case REDOING:
377: title = WorkbenchMessages.Operations_redoProblem;
378: stopped = WorkbenchMessages.Operations_stoppedOnRedoErrorStatus;
379: break;
380: default: // EXECUTING
381: title = WorkbenchMessages.Operations_executeProblem;
382: stopped = WorkbenchMessages.Operations_stoppedOnExecuteErrorStatus;
383:
384: break;
385: }
386:
387: // It is an error condition. The user has no choice to proceed, so
388: // we only report what has gone on. We use a warning icon instead of
389: // an error icon since there has not yet been a failure.
390:
391: String message = NLS.bind(stopped, status.getMessage(),
392: operation.getLabel());
393:
394: MessageDialog dialog = new MessageDialog(shell, title,
395: null, message, MessageDialog.WARNING,
396: new String[] { IDialogConstants.OK_LABEL }, 0); // ok
397: dialog.open();
398: }
399:
400: if (createdShell) {
401: shell.dispose();
402: }
403:
404: return reportedStatus;
405:
406: }
407:
408: /*
409: * Return the shell described by the supplied uiInfo, or null if no shell is
410: * described.
411: */
412: Shell getShell(IAdaptable uiInfo) {
413: if (uiInfo != null) {
414: Shell shell = (Shell) Util.getAdapter(uiInfo, Shell.class);
415: if (shell != null) {
416: return shell;
417: }
418: }
419: return null;
420: }
421: }
|