blob: 78ac5b61d8964eafd93e3200109eccb7b62c2e4b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.operations;
import java.util.ArrayList;
import org.eclipse.core.commands.operations.IAdvancedUndoableOperation;
import org.eclipse.core.commands.operations.IOperationApprover;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.util.Util;
/**
* <p>
* An operation approver that prompts the user to see if a non-local undo should
* proceed inside an editor. A non-local undo is detected when an operation
* being undone or redone affects elements other than those described by the
* editor itself. Clients can optionally specify a class, the preferred
* comparison class, that should be used when comparing objects affected by the
* editor with objects affected by an undo or redo operation. Comparisons
* between the affected objects inside the editor and those described by the
* operation will first be done by simply performing an equality check, using
* {@link java.lang.Object#equals(java.lang.Object)}. If an object described by
* an operation is not equal to one of the objects affected by the editor, and
* if it is not an instance of the preferred comparison class, but is an
* instance of {@link org.eclipse.core.runtime.IAdaptable}, then the operation
* approver will also attempt to retrieve an adapter on that object for the
* preferred comparison class and perform a second equality check using the
* adapter.
* </p>
* <p>
* This class may be instantiated by clients.
* </p>
*
*
* @since 3.1
*/
public final class NonLocalUndoUserApprover implements IOperationApprover {
private IUndoContext context;
private IEditorPart part;
private Object[] elements;
private Class affectedObjectsClass;
private ArrayList elementsAndAdapters;
/**
* Create a NonLocalUndoUserApprover associated with the specified editor
* and undo context
*
* @param context
* the undo context of operations in question.
* @param part
* the editor part that is displaying the element
* @param affectedObjects
* the objects that are affected by the editor and considered to
* be objects local to the editor. The objects are typically
* instances of the preferredComparisonClass or else provide
* adapters for the preferredComparisonClass, although this is
* not required.
* @param preferredComparisonClass
* the preferred class to be used when comparing the editor's
* affectedObjects with those provided by the undoable operation
* using
* {@link org.eclipse.core.commands.operations.IAdvancedUndoableOperation#getAffectedObjects()}.
* If the operation's affected objects are not instances of the
* specified class, but are instances of
* {@link org.eclipse.core.runtime.IAdaptable}, then an adapter
* for this class will be requested. The preferredComparisonClass
* may be <code>null</code>, which indicates that there is no
* expected class or adapter necessary for the comparison.
*/
public NonLocalUndoUserApprover(IUndoContext context, IEditorPart part,
Object[] affectedObjects, Class preferredComparisonClass) {
super();
this.context = context;
this.part = part;
this.affectedObjectsClass = preferredComparisonClass;
this.elements = affectedObjects;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IOperationApprover#proceedRedoing(org.eclipse.core.commands.operations.IUndoableOperation,
* org.eclipse.core.commands.operations.IOperationHistory,
* org.eclipse.core.runtime.IAdaptable)
*/
public IStatus proceedRedoing(IUndoableOperation operation,
IOperationHistory history, IAdaptable uiInfo) {
// return immediately if the operation is not relevant
if (!requiresApproval(operation, uiInfo)) {
return Status.OK_STATUS;
}
String message = NLS.bind(
WorkbenchMessages.Operations_nonLocalRedoWarning, operation
.getLabel(), part.getEditorInput().getName());
return proceedWithOperation(operation, message, WorkbenchMessages.Operations_discardRedo, WorkbenchMessages.Workbench_redoToolTip);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.commands.operations.IOperationApprover#proceedUndoing(org.eclipse.core.commands.operations.IUndoableOperation,
* org.eclipse.core.commands.operations.IOperationHistory,
* org.eclipse.core.runtime.IAdaptable)
*/
public IStatus proceedUndoing(IUndoableOperation operation,
IOperationHistory history, IAdaptable uiInfo) {
// return immediately if the operation is not relevant
if (!requiresApproval(operation, uiInfo)) {
return Status.OK_STATUS;
}
String message = NLS.bind(
WorkbenchMessages.Operations_nonLocalUndoWarning, operation
.getLabel(), part.getEditorInput().getName());
return proceedWithOperation(operation, message, WorkbenchMessages.Operations_discardUndo, WorkbenchMessages.Workbench_undoToolTip);
}
/*
* Determine whether the operation in question affects elements outside of
* the editor. If this can be determined and it does affect other elements,
* prompt the user as to whether the operation should proceed.
*/
private IStatus proceedWithOperation(IUndoableOperation operation,
final String message, final String discardButton, final String title) {
// if the operation cannot tell us about its modified elements, there's
// nothing we can do.
if (!(operation instanceof IAdvancedUndoableOperation)) {
return Status.OK_STATUS;
}
// Obtain the operation's affected objects.
Object[] modifiedElements = ((IAdvancedUndoableOperation) operation)
.getAffectedObjects();
// Since the operation participates in describing its affected objects,
// we assume for the rest of this method that an inability to
// determine a match implies that a non-local operation is occurring.
// This is a conservative assumption that provides more user prompting.
boolean local;
if (modifiedElements == null) {
// The operation could not determine which elements are affected.
// Consider the operation non-local.
local = false;
} else {
// The operation answered some array of affected objects. Consider
// the operation local until a non-match is found. Note that an
// empty
// array of affected objects is considered a local change.
local = true;
for (int i = 0; i < modifiedElements.length; i++) {
Object modifiedElement = modifiedElements[i];
if (!elementsContains(modifiedElement)) {
// the modified element is not known by the editor
local = false;
// one last try - try to adapt the modified element if a
// preferred
// comparison class has been provided.
if (affectedObjectsClass != null) {
Object adapter = Util.getAdapter(modifiedElement,
affectedObjectsClass);
if (adapter != null && elementsContains(adapter)) {
local = true;
}
}
// if the element did not match the affected objects, no
// need to check any others.
if (!local) {
break;
}
}
}
}
if (local) {
return Status.OK_STATUS;
}
// The operation affects more than just our element. Find out if
// we should proceed, cancel, or discard the undo. Must be done in
// a syncExec because operation approval notifications may come from
// a background thread.
final int[] answer = new int[1];
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
public void run() {
MessageDialog dialog = new MessageDialog(part.getSite().getShell(), title,
null, message, MessageDialog.QUESTION, new String[] { IDialogConstants.OK_LABEL,
discardButton, IDialogConstants.CANCEL_LABEL }, 0); // yes is the default
answer[0] = dialog.open();
}});
switch (answer[0]) {
case 0:
return Status.OK_STATUS;
case 1:
return IOperationHistory.OPERATION_INVALID_STATUS;
default:
// Cancel by default to include ESC key and shell close,
// which return SWT.DEFAULT, and any other unexpected return codes
return Status.CANCEL_STATUS;
}
}
/*
* Answer whether this operation is relevant enough to this operation
* approver that it should be examined in detail.
*/
private boolean requiresApproval(IUndoableOperation operation,
IAdaptable uiInfo) {
// no approval is required if the operation doesn't have our undo
// context
if (!(operation.hasContext(context))) {
return false;
}
// no approval is required if the operation only has our context
if (operation.getContexts().length == 1) {
return false;
}
// no approval is required if we can ascertain that the operation did
// not originate
// in our context.
if (uiInfo != null) {
IUndoContext originatingContext = (IUndoContext) Util.getAdapter(uiInfo,
IUndoContext.class);
if (originatingContext != null
&& !(originatingContext.matches(context))) {
return false;
}
}
return true;
}
/*
* Return whether or not the collection of editor elements plus any of their
* adapters contains the specified object.
*/
private boolean elementsContains(Object someObject) {
if (elements == null) {
return false;
}
if (elementsAndAdapters == null) {
// Compute a list of not just the elements, but any adapters they
// may provide on the preferred class if they are not instances of
// the preferred class. This is done only once.
elementsAndAdapters = new ArrayList(elements.length);
for (int i = 0; i < elements.length; i++) {
Object element = elements[i];
elementsAndAdapters.add(element);
if (affectedObjectsClass != null
&& !affectedObjectsClass.isInstance(element)) {
Object adapter = Util.getAdapter(element, affectedObjectsClass);
if (adapter != null) {
elementsAndAdapters.add(adapter);
}
}
}
}
for (int i = 0; i < elementsAndAdapters.size(); i++) {
if (elementsAndAdapters.get(i).equals(someObject)) {
return true;
}
}
return false;
}
}