blob: d656a3ce36c86bb623dba7af250065bf3dfd8624 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 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.gmf.runtime.common.ui.action;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.eclipse.gmf.runtime.common.core.util.StringStatics;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.common.ui.internal.CommonUIDebugOptions;
import org.eclipse.gmf.runtime.common.ui.internal.CommonUIPlugin;
import org.eclipse.gmf.runtime.common.ui.internal.CommonUIStatusCodes;
import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils;
import org.eclipse.gmf.runtime.common.ui.util.IPartSelector;
import org.eclipse.gmf.runtime.common.ui.util.PartListenerAdapter;
import org.eclipse.gmf.runtime.common.ui.util.StatusLineUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
/**
* The abstract parent of all concrete action handlers that execute commands.
* Logging and exception handling are done in a uniform way in the
* <code>run()</code> method. Concrete subclasses must provide a definition of
* the <code>doRun()</code> method to gather any required input and execute a
* command. As an implementer of the <code>IRepeatableAction</code> interface,
* this class implements the <code>isRepeatable()</code> method to return
* <code>true</code> if it is enabled, and implements the
* <code>repeat()</code> method to run itself. Subclasses that aren't
* repeatable or require special repeat behavior must override the default
* implementations of these interface methods.
*
* This action handler supports life cycle methods by implementing the
* <code>IDisposableAction</code> interface. Therefore, clients need to call
* the <code>init()</code> method to initialize the action, and the
* <code>dispose()</code> method when the action is no longer needed.
*
* @author khussey
*/
public abstract class AbstractActionHandler
extends Action
implements IDisposableAction, IActionWithProgress, ISelectionChangedListener,
IOperationHistoryListener, IPropertyListener {
/**
* Flag to indicate whether or not this action has been set up.
*/
private boolean setup;
/**
* My disposed state.
*/
private boolean disposed;
/**
* The workbench part to which this action handler applies.
*/
private IWorkbenchPart workbenchPart;
/**
* The workbench page this action is associated to
*/
private IWorkbenchPage workbenchPage;
/**
* The part listener of this action
*/
private IPartListener partListener;
/**
* Selects workbench parts that match the part criteria with which I was
* contributed. I will refresh myself when the selection changes on parts
* that match this criteria, and when such parts are activated.
*/
private IPartSelector partSelector;
/**
* Constructs a new action handler for the specified workbench part.
*
* @param workbenchPart
* The workbench part to which this action handler applies.
*/
protected AbstractActionHandler(IWorkbenchPart workbenchPart) {
super();
assert null != workbenchPart : "null workbenchPart"; //$NON-NLS-1$
setWorkbenchPart(workbenchPart);
this.workbenchPage = workbenchPart.getSite().getPage();
// This is needed for backward compatibility in case the creator
// of the action (using this constructor) did not dispose of it
this.partListener = new PartListenerAdapter() {
/**
* when the part closes, remove the listener to the workbench page
* and remove all listeners.
*/
public void partClosed(IWorkbenchPart part) {
if (getWorkbenchPart() == part) {
dispose();
}
}
};
workbenchPage.addPartListener(partListener);
}
/**
* Constructs a new action handler that gets its workbench part by listening
* to the given workbench page
*
* @param workbenchPage
* The workbench page associated with this action handler
*/
protected AbstractActionHandler(final IWorkbenchPage workbenchPage) {
super();
assert null != workbenchPage : "null workbenchPage"; //$NON-NLS-1$
this.workbenchPage = workbenchPage;
this.partListener = new PartListenerAdapter() {
/**
* Listens to part activation and updates the active workbench
*/
public void partActivated(IWorkbenchPart part) {
setWorkbenchPart(part);
if (part != null && contributedToPart(part))
refresh();
}
/**
* Listens to part deactivation and disables the action
* Could be improved to only consider toolbar actions
*/
public void partDeactivated(IWorkbenchPart part) {
if (part != null && contributedToPart(part)) {
setEnabled(false);
}
}
};
workbenchPage.addPartListener(partListener);
}
/**
* The basic implementation sets the workbenchpart if not already set and
* refreshes the action if the current part is not null.
* <P>
* Any subclass that overrided this method should ensure that the disposed
* state of this action is maintained by calling
* <code>setDisposed(false)</code> or calling <code>super.init()</code>.
*/
public void init() {
setDisposed(false);
if (getWorkbenchPart() == null)
setWorkbenchPart(getWorkbenchPage().getActivePart());
if (getWorkbenchPart() != null)
refresh();
}
/**
* Default implementation of dispose. Any subclass that overrided this
* method should ensure that the disposed state of this action is maintained
* by calling <code>setDisposed(true)</code> or calling
* <code>super.dispose()</code>.
*/
public void dispose() {
setWorkbenchPart(null);
if (partListener != null && workbenchPage != null) {
workbenchPage.removePartListener(partListener);
workbenchPage = null;
partListener = null;
}
setDisposed(true);
}
/**
* Sets the current workbencgPart
*
* @param workbenchPart
* The current workbenchPart
*/
protected void setWorkbenchPart(IWorkbenchPart workbenchPart) {
if (getWorkbenchPart() == workbenchPart)
return;
if (getWorkbenchPart() != null) {
if (isSelectionListener()) {
ISelectionProvider provider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (provider != null) {
provider.removeSelectionChangedListener(this);
}
}
if (isPropertyListener()) {
getWorkbenchPart().removePropertyListener(this);
}
if (isOperationHistoryListener()) {
getOperationHistory().removeOperationHistoryListener(this);
}
}
this.workbenchPart = workbenchPart;
if (workbenchPart != null) {
if (contributedToPart(workbenchPart)) {
if (isSelectionListener()) {
ISelectionProvider provider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (provider != null) {
provider.addSelectionChangedListener(this);
}
}
if (isPropertyListener()) {
getWorkbenchPart().addPropertyListener(this);
}
if (isOperationHistoryListener()) {
getOperationHistory().addOperationHistoryListener(this);
}
}
}
}
/**
* Answers whether or not I am contributed to <code>part</code>.
*
* @param part
* the workbench part to be tested
* @return <code>true</code> if I am contributed to this part,
* <code>false</code> otherwise.
*/
protected boolean contributedToPart(IWorkbenchPart part) {
return partSelector == null || partSelector.selects(part);
}
/**
* Sets my part selector. If my current workbench part doesn't match the
* part selector, I stop listening to selection, property and operation
* history changes on that part.
*
* @param partSelector
* my new part selector
*/
public final void setPartSelector(IPartSelector partSelector) {
if (this.partSelector == partSelector) {
return;
}
this.partSelector = partSelector;
IWorkbenchPart part = getWorkbenchPart();
if (part != null && !partSelector.selects(part)) {
if (isSelectionListener()) {
ISelectionProvider provider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (provider != null) {
provider.removeSelectionChangedListener(this);
}
}
if (isPropertyListener()) {
getWorkbenchPart().removePropertyListener(this);
}
if (isOperationHistoryListener()) {
getOperationHistory().removeOperationHistoryListener(this);
}
}
}
/**
* Retrieves the value of the <code>workbenchPart</code> instance
* variable.
*
* @return The value of the <code>workbenchPart</code> instance variable.
*/
protected final IWorkbenchPart getWorkbenchPart() {
return workbenchPart;
}
/**
* Retrieves the action manager for this action handler from its workbench
* part.
*
* @return The action manager for this action handler.
*/
protected ActionManager getActionManager() {
ActionManager manager = null;
IWorkbenchPart wbPart = getWorkbenchPart();
if (wbPart != null) {
manager = (ActionManager) wbPart.getAdapter(
ActionManager.class);
}
return null == manager ? ActionManager.getDefault()
: manager;
}
/**
* Returns the operation history for this action handler from its action
* manager.
*
* @return the operation history
*/
protected IOperationHistory getOperationHistory() {
return getActionManager().getOperationHistory();
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.IAction#run()
*/
public void run() {
getActionManager().run(this);
}
/**
* Runs this action handler.
*/
public void run(IProgressMonitor progressMonitor) {
if (isSetup() || !needsSetup()) {
try {
StatusLineUtil.outputErrorMessage(getWorkbenchPart(),
StringStatics.BLANK);
doRun(progressMonitor);
} catch (Exception e) {
handle(e);
}
setSetup(false);
} else {
throw new IllegalStateException(
"action must be setup before it is run"); //$NON-NLS-1$
}
}
/**
* Runs this action handler, passing the triggering SWT event.
*
* @param event
* The SWT event which triggered this action being run.
*/
public void runWithEvent(Event event) {
getActionManager().run(this);
}
/**
* Notifies this action handler that the selection has changed.
*
* @param event
* Event object describing the change.
*/
public final void selectionChanged(SelectionChangedEvent event) {
refresh();
}
/**
* Property change event handler; does nothing by default. Subclasses should
* override if they are interested in handling property change events.
*/
public void propertyChanged(Object source, int propId) {
// Do nothing by default
}
/**
* Retrieves the label for this action handler.
*
* @return The label for this action handler.
*/
public String getLabel() {
return getText();
}
/**
* Retrieves the current selection.
*
* @return The current selection.
*/
protected ISelection getSelection() {
ISelection selection = null;
ISelectionService selectionService = null;
if (getWorkbenchPart() != null && getWorkbenchPart().getSite().getWorkbenchWindow() != null) {
selectionService = getWorkbenchPart().getSite()
.getWorkbenchWindow().getSelectionService();
}
if (selectionService != null) {
selection = selectionService.getSelection();
}
return (selection != null) ? selection
: StructuredSelection.EMPTY;
}
/**
* Retrieves the current structured selection.
*
* @return The current structured selection.
*/
protected IStructuredSelection getStructuredSelection() {
ISelection selection = getSelection();
return (selection instanceof StructuredSelection) ? (StructuredSelection) selection
: StructuredSelection.EMPTY;
}
/**
* Retrieves a Boolean indicating whether this action handler can be run.
*
* @return <code>true</code> if this action handler is enabled;
* <code>false</code> otherwise.
*/
public boolean isRunnable() {
return isEnabled();
}
/**
* Retrieves a Boolean indicating whether this action handler is interested
* in selection events.
*
* @return <code>true</code> if this action handler is interested;
* <code>false</code> otherwise.
*/
protected boolean isSelectionListener() {
return false;
}
/**
* Answers whether of not this action handler is interested in property
* change events.
* <P>
* This default implementation always returns <code>false</code>.
* Subclasses must override if they are interested in property change
* events.
*
* @return <code>true</code> if this action handler is interested;
* <code>false</code> otherwise.
*/
protected boolean isPropertyListener() {
return false;
}
/**
* Retrieves a Boolean indicating whether this action handler is interested
* in operation history changed events.
*
* @return <code>true</code> if this action handler is interested;
* <code>false</code> otherwise.
*/
protected boolean isOperationHistoryListener() {
return false;
}
/**
* Handles the specified exception.
*
* @param exception
* The exception to be handled.
*/
protected void handle(Exception exception) {
Trace.catching(CommonUIPlugin.getDefault(),
CommonUIDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"handle", exception); //$NON-NLS-1$
IStatus status = new Status(IStatus.ERROR,
CommonUIPlugin.getPluginId(), CommonUIStatusCodes.ACTION_FAILURE,
String.valueOf(exception.getMessage()), exception);
Log.log(CommonUIPlugin.getDefault(), status);
openErrorDialog(status);
}
/**
* Opens an error dialog for the specified status object.
*
* @param status
* The status object for which to open an error dialog.
*
*/
protected void openErrorDialog(final IStatus status) {
final Display display = DisplayUtils.getDisplay();
if (display.getThread() == Thread.currentThread()) {
// we're already on the UI thread
ErrorDialog.openError(display.getActiveShell(),
removeMnemonics(getLabel()), null, status);
} else {
// we're not on the UI thread
display.asyncExec(new Runnable() {
public void run() {
ErrorDialog.openError(display.getActiveShell(),
removeMnemonics(getLabel()), null, status);
}
});
}
}
/**
* Performs the actual work when this action handler is run. Subclasses must
* override this method to do some work.
*
* @param progressMonitor
* the progress monitor for tracking the progress of this action
* when it is run.
*/
protected abstract void doRun(IProgressMonitor progressMonitor);
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.common.ui.action.IRepeatableAction#getWorkIndicatorType()
*/
public WorkIndicatorType getWorkIndicatorType() {
return WorkIndicatorType.BUSY;
}
/**
* Returns the part listener
*
* @return The part listener
*/
protected IPartListener getPartListener() {
return partListener;
}
/**
* Returns the workbench page
*
* @return The workbench page
*/
protected IWorkbenchPage getWorkbenchPage() {
return workbenchPage;
}
/**
* Refreshes me if the history event has my workbench part's context, and
* the event is one of:
* <UL>
* <LI>{@link OperationHistoryEvent#UNDONE}</LI>
* <LI>{@link OperationHistoryEvent#REDONE}</LI>
* <LI>{@link OperationHistoryEvent#OPERATION_ADDED}</LI>
* <LI>{@link OperationHistoryEvent#OPERATION_CHANGED}</LI>
* <LI>{@link OperationHistoryEvent#OPERATION_NOT_OK}</LI>
* <LI>{@link OperationHistoryEvent#OPERATION_REMOVED}</LI>
* </UL>
* The other operation history events are ignored because they are
* intermediate events that will be followed by one of those listed above.
* We only want to refresh the action handler once for each change to the
* operation history.
*/
public void historyNotification(OperationHistoryEvent event) {
int type = event.getEventType();
if (type == OperationHistoryEvent.UNDONE
|| type == OperationHistoryEvent.REDONE
|| type == OperationHistoryEvent.DONE
|| type == OperationHistoryEvent.OPERATION_ADDED
|| type == OperationHistoryEvent.OPERATION_CHANGED
|| type == OperationHistoryEvent.OPERATION_NOT_OK
|| type == OperationHistoryEvent.OPERATION_REMOVED) {
IUndoableOperation operation = event.getOperation();
if (operation != null) {
IUndoContext partContext = getUndoContext();
if (partContext != null && operation.hasContext(partContext)) {
refresh();
}
}
}
}
/**
* Gets the undo context from my workbench part.
*
* @return the undo context
*/
protected IUndoContext getUndoContext() {
IWorkbenchPart part = getWorkbenchPart();
if (part != null) {
return (IUndoContext) part.getAdapter(IUndoContext.class);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.common.ui.action.IDisposableAction#isDisposed()
*/
public boolean isDisposed() {
return disposed;
}
/**
* Sets my disposed state.
*
* @param b
* <code>true</code> if I am disposed, <code>false</code>
* otherwise.
*/
protected void setDisposed(boolean b) {
disposed = b;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.gmf.runtime.common.ui.action.IRepeatableAction#setup()
*/
public boolean setup() {
setSetup(true);
return true;
}
/**
* Returns the setup state of this action.
*
* @return <code>true</code> if the action has been setup,
* <code>false</code> otherwise.
*/
public boolean isSetup() {
return setup;
}
/**
* Sets the setup state of this action.
*
* @param setup
* <code>true</code> if the action has been setup,
* <code>false</code> otherwise.
*/
protected void setSetup(boolean setup) {
this.setup = setup;
}
/**
* Answers whether or not this action should be setup before it is run.
* Subclasses should override if they provide vital behaviour in the setup
* method.
*
* @return <code>true</code> if the action has a setup, <code>false</code>
* otherwise.
*/
protected boolean needsSetup() {
return false;
}
}