blob: 971b38d634c311b3f669dc60f6adc6f529b879c0 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2010 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.ExecutionException;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.common.core.util.Log;
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.PartListenerAdapter;
import org.eclipse.jface.action.ContributionItem;
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.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
/**
* A custom contribution item that goes into a workbenchpart's toolbar
*
* @author melaasar
*/
public abstract class AbstractContributionItem
extends ContributionItem
implements ISelectionChangedListener, IOperationHistoryListener,
IActionWithProgress {
/**
* Flag to indicate whether or not this action has been set up.
*/
private boolean setup;
// the part service
private IWorkbenchPage workbenchPage;
// the part listener
private IPartListener partListener;
// the current workbenchpart
private IWorkbenchPart workbenchPart;
// the item listener
private Listener itemListener;
// the item widget
private Item item;
// the control in case of a widget with a SWT.SEPARATOR style
private Control control;
// the control text
private String label;
// the enablement state of the item
private boolean enabled = true;
/**
* Creates a new WorkbenchPartContributionItem
*
* @param workbenchPage
* The workbench Page
*/
public AbstractContributionItem(IWorkbenchPage workbenchPage) {
this(workbenchPage, null);
}
/**
* Creates a new WorkbenchPartContributionItem
*
* @param workbenchPage
* The workbench Page
* @param id
* The id of the contribution item
*/
public AbstractContributionItem(IWorkbenchPage workbenchPage, String id) {
super(id);
assert null != workbenchPage : "workbenchPage is null"; //$NON-NLS-1$
this.workbenchPage = workbenchPage;
partListener = new PartListenerAdapter() {
public void partActivated(IWorkbenchPart part) {
setWorkbenchPart(part);
update();
}
};
itemListener = new Listener() {
public void handleEvent(Event event) {
AbstractContributionItem.this.handleWidgetEvent(event);
}
};
}
/**
* <code>init</code> is used to initialize the common part of filling this
* item in a contribution manager. The <code>dispose</code> method is
* later called to clean up what has been initialized in the
* <code>fill</code> and <code>init</code> methods
*/
protected void init() {
if (getWorkbenchPart() == null)
setWorkbenchPart(workbenchPage.getActivePart());
workbenchPage.addPartListener(partListener);
}
/**
* Dispose should only clean up what was done in the <code>fill</code>
* methods It is not meant to clean up what was done in constructors
*/
public void dispose() {
workbenchPage.removePartListener(partListener);
setWorkbenchPart(null);
item = null;
control = null;
super.dispose();
}
/**
* Gets the current workbench part.
*
* @return The current workbench part.
*/
protected IWorkbenchPart getWorkbenchPart() {
return workbenchPart;
}
/**
* 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;
}
/**
* Gets the item control
*
* @return The item control
*/
protected Control getControl() {
return control;
}
/**
* Gets the item widget
*
* @return The item widget
*/
protected Item getItem() {
return item;
}
/**
* Gets the tool item widget
*
* @return The tool item widget
*/
protected ToolItem getToolItem() {
return item instanceof ToolItem ? (ToolItem) item
: null;
}
/**
* Gets the tool item widget
*
* @return The tool item widget
*/
protected MenuItem getMenuItem() {
return item instanceof MenuItem ? (MenuItem) item
: null;
}
/**
* Gets the control tooltip text
*
* @return The control tooltip text
*/
public String getLabel() {
return label;
}
/**
* @param item
* widget
*/
public void setItem(Item item) {
this.item = item;
}
/**
* Sets the control label
*
* @param label
* The control label
*/
protected void setLabel(String label) {
this.label = label;
}
/**
* Sets the current workbench part
*
* @param workbenchPart
* The current workbench part
*/
protected void setWorkbenchPart(IWorkbenchPart workbenchPart) {
if (getWorkbenchPart() != null) {
if (isSelectionListener()) {
ISelectionProvider provider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (provider != null) {
provider.removeSelectionChangedListener(this);
}
}
if (isOperationHistoryListener()) {
getOperationHistory().removeOperationHistoryListener(this);
}
}
this.workbenchPart = workbenchPart;
if (workbenchPart != null) {
if (isSelectionListener()) {
ISelectionProvider provider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (provider != null) {
provider.addSelectionChangedListener(this);
}
}
if (isOperationHistoryListener()) {
getOperationHistory().addOperationHistoryListener(this);
}
}
}
/**
* The control item implementation of this <code>IContributionItem</code>
* method calls the <code>createControl</code> framework method.
* Subclasses must implement <code>createControl</code> rather than
* overriding this method.
*
* @param parent
* The parent of the control to fill
*/
public final void fill(Composite parent) {
init();
control = createControl(parent);
if (control != null) {
update();
}
assert null != control : "The contribution item cannot fill in composites"; //$NON-NLS-1$
}
/**
* The control item implementation of this <code>IContributionItem</code>
* method throws an exception since controls cannot be added to menus.
*
* @param parent
* The menu
* @param index
* Menu index
*/
public final void fill(Menu parent, int index) {
init();
MenuItem menuItem = createMenuItem(parent, index);
if (menuItem != null) {
menuItem.setData(this);
menuItem.setText(getLabel());
menuItem.addListener(SWT.Dispose, getItemListener());
setItem(menuItem);
update();
}
assert null != menuItem : "The contribution item cannot fill in menus"; //$NON-NLS-1$
}
/**
* The control item implementation of this <code>IContributionItem</code>
* method calls the <code>createControl</code> framework method to create
* a control under the given parent, and then creates a new tool item to
* hold it. Subclasses must implement <code>createControl</code> rather
* than overriding this method.
*
* @param parent
* The ToolBar to add the new control to
* @param index
* Index
*/
public final void fill(ToolBar parent, int index) {
init();
ToolItem toolItem = createToolItem(parent, index);
if (toolItem != null) {
toolItem.setData(this);
toolItem.setToolTipText(getLabel());
toolItem.addListener(SWT.Dispose, getItemListener());
setItem(toolItem);
update();
}
assert null != toolItem : "The contribution item cannot fill in toolbars"; //$NON-NLS-1$
}
/**
* Creates the <code>ToolItem</code> with the given parent and index.
*
* @param parent
* The ToolBar to add the new control to
* @param index
* Index
* @return <code>ToolItem</code> for specified <code>ToolBar</code> at specifiec index
*/
protected ToolItem createToolItem(ToolBar parent, int index) {
control = createControl(parent);
if (control != null) {
ToolItem anItem = new ToolItem(parent, SWT.SEPARATOR, index);
anItem.setControl(control);
anItem.setWidth(computeWidth(control));
return anItem;
}
return null;
}
/**
* Creates the menuitem with the given parent and index.
*
* @param parent
* The Menu to add the new control to
* @param index
* Index
* @return created <code>MenuItem</code>
*/
protected MenuItem createMenuItem(Menu parent, int index) {
return null;
}
/**
* Creates the control of this contributor - override only if a custom control
* is needed.
*
* @param parent the parent <code>Composite</code>
* @return control for the specified parent <code>Composite</code>
*/
protected Control createControl(Composite parent) {
return null;
}
/**
* Method is being called when there control created by subclasses is not
* null.
*
* Computes the width of the given control which is being added to a tool
* bar. This is needed to determine the width of the tool bar item
* containing the given control.
*
* @param cont
* the control being added
* @return the width of the control
*/
protected int computeWidth(Control cont) {
return cont.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x;
}
/**
* updates the properties of this contribution item Subclasses should call
* this method when an update is requested
*
* This method is not intended to be overriden. Instead override the
* <code>refresh()</code> method
*/
public final void update() {
if (getWorkbenchPart() == null)
setWorkbenchPart(workbenchPage.getActivePart());
if (getWorkbenchPart() != null) {
refresh();
}
}
/**
* refreshed the properties of this contribution item This method should not
* be called directly, instead <code>update</code> method should be
* called.
*
* Subclasses could override this method to add to the refresh and at the
* end call <code>super.refresh()</code>
*/
public void refresh() {
setEnabled(calculateEnabled());
if (getControl() != null || getItem() != null)
refreshItem();
}
/**
* Refreshes the item's GUI
*/
protected void refreshItem() {
if (getControl() != null)
getControl().setEnabled(isEnabled());
else if (getToolItem() != null)
getToolItem().setEnabled(isEnabled());
else if (getMenuItem() != null)
getMenuItem().setEnabled(isEnabled());
}
/**
* Calculates enablement of the widget. Subclasses must implement. The
* enablement will used every time the widget is refreshed. It is a
* resposcibility of the subclasses to call refresh() when it is
* appropriate.
*
* @return boolean
*/
protected abstract boolean calculateEnabled();
/**
* Method setEnabled.
*
* @param enabled
*/
protected void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.IContributionItem#isEnabled()
*/
public boolean isEnabled() {
return enabled;
}
/**
* Executes the given <code>ommand</code>.
*
* @param command <code>ICommand</code> to be executed
*/
protected void execute(ICommand command) {
if (command == null || !command.canExecute())
return;
command.addContext(getUndoContext());
try {
getOperationHistory().execute(command, new NullProgressMonitor(), null);
} catch (ExecutionException e) {
Trace.catching(CommonUIPlugin.getDefault(),
CommonUIDebugOptions.EXCEPTIONS_CATCHING, getClass(),
"execute", e); //$NON-NLS-1$
Log.error(CommonUIPlugin.getDefault(),
CommonUIStatusCodes.ACTION_FAILURE, e.getLocalizedMessage(), e);
}
return;
}
/**
* Retrieves the action manager for this action delegate from its workbench
* part.
*
* @return The action manager for this action delegate.
*/
protected ActionManager getActionManager() {
ActionManager manager = (ActionManager) getWorkbenchPart().getAdapter(
ActionManager.class);
return null == manager ? ActionManager.getDefault()
: manager;
}
/**
* Returns the operation history for this contribution item from its action
* manager.
*
* @return the operation history
*/
protected IOperationHistory getOperationHistory() {
return getActionManager().getOperationHistory();
}
/**
* A generalized convinience method. Should be called by subclasses whenever
* run() must be ivoked (e.g. whenever a button is pushed)
*
* @param event
* an optional associated SWT event
*/
protected void runWithEvent(Event event) {
getActionManager().run(this);
}
/**
* 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);
/**
* 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(IStatus status) {
ErrorDialog.openError(getWorkbenchPart().getSite().getShell(),
getLabel(), null, status);
}
/**
* Handles an event from the widget (forwarded from nested listener).
*
* @param e <code>Event</code> to be handled by this method
*/
protected void handleWidgetEvent(Event e) {
switch (e.type) {
case SWT.Dispose:
handleWidgetDispose(e);
break;
}
}
/**
* Handles a widget dispose event for the widget corresponding to this item.
*
* @param e widget dispose <code>Event</code>
*/
protected void handleWidgetDispose(Event e) {
dispose();
}
/**
* 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;
}
/**
* Retrieves a Boolean indicating whether this contribution item 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;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
*/
public final void selectionChanged(SelectionChangedEvent event) {
update();
}
/**
* 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)
&& PlatformUI.isWorkbenchRunning()) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
update();
}
});
}
}
}
}
/**
* Retrieves the current selection.
*
* @return The current selection.
*/
protected ISelection getSelection() {
ISelection selection = null;
ISelectionProvider selectionProvider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (selectionProvider != null) {
selection = selectionProvider.getSelection();
}
return (selection != null) ? selection
: StructuredSelection.EMPTY;
}
/**
* Retrieves the current structured selection.
*
* @return <code>IStructuredSelection</code> for current selection
*/
protected IStructuredSelection getStructuredSelection() {
IStructuredSelection selection = null;
ISelectionProvider selectionProvider = getWorkbenchPart().getSite()
.getSelectionProvider();
if (selectionProvider != null
&& selectionProvider.getSelection() instanceof IStructuredSelection) {
selection = (IStructuredSelection) selectionProvider.getSelection();
}
return (selection != null) ? selection
: StructuredSelection.EMPTY;
}
/**
* Returns the item listenr
*
* @return The item listener
*/
protected Listener getItemListener() {
return itemListener;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.common.ui.action.IRepeatableAction#getWorkIndicatorType()
*/
public WorkIndicatorType getWorkIndicatorType() {
return WorkIndicatorType.BUSY;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.common.ui.action.IRepeatableAction#isRunnable()
*/
public boolean isRunnable() {
return isEnabled();
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.common.ui.action.IRepeatableAction#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public void run(IProgressMonitor progressMonitor) {
if (isSetup() || !needsSetup()) {
try {
doRun(progressMonitor);
} catch (Exception e) {
handle(e);
}
setSetup(false);
} else {
throw new IllegalStateException(
"action must be setup before it is run"); //$NON-NLS-1$
}
}
/* (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;
}
}