net.enilink.komma.edit.ui.action.AbstractActionHandler.java Source code

Java tutorial

Introduction

Here is the source code for net.enilink.komma.edit.ui.action.AbstractActionHandler.java

Source

/******************************************************************************
 * 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 net.enilink.komma.edit.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.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;
import org.eclipse.ui.PlatformUI;

import net.enilink.komma.common.util.Log;
import net.enilink.komma.common.util.StringStatics;
import net.enilink.komma.common.util.Trace;
import net.enilink.komma.edit.ui.KommaEditUIPlugin;
import net.enilink.komma.edit.ui.internal.EditUIDebugOptions;
import net.enilink.komma.edit.ui.internal.EditUIStatusCodes;
import net.enilink.komma.edit.ui.util.IPartSelector;
import net.enilink.komma.edit.ui.util.PartListenerAdapter;
import net.enilink.komma.edit.ui.util.StatusLineUtil;

/**
 * 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;

    /**
     * A static selection this action operates on.
     */
    private ISelection selection;

    /**
     * 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) {
        setWorkbenchPart(workbenchPart);
        if (workbenchPart != null) {
            this.workbenchPage = workbenchPart.getSite().getPage();
        }
    }

    /**
     * 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) {
        this.workbenchPage = workbenchPage;
        if (workbenchPage != null) {
            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) && !isDisposed())
                        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);
        }
    }

    /**
     * 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, ISelection selection) {
        setWorkbenchPart(workbenchPart);
        if (workbenchPart != null) {
            this.workbenchPage = workbenchPart.getSite().getPage();
        }
        this.selection = selection;
    }

    /**
     * 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 && getWorkbenchPage() != null) {
            setWorkbenchPart(getWorkbenchPage().getActivePart());
        }
        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() {
        if (this.selection != null) {
            return this.selection;
        }

        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(KommaEditUIPlugin.getPlugin(), EditUIDebugOptions.EXCEPTIONS_CATCHING, getClass(), "handle", //$NON-NLS-1$
                exception);

        IStatus status = new Status(IStatus.ERROR, KommaEditUIPlugin.PLUGIN_ID, EditUIStatusCodes.ACTION_FAILURE,
                String.valueOf(exception.getMessage()), exception);

        Log.log(KommaEditUIPlugin.getPlugin(), 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 = 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);
                }
            });
        }
    }

    private Display getDisplay() {
        Display display = Display.getCurrent();
        if (display == null && PlatformUI.isWorkbenchRunning()) {
            display = PlatformUI.getWorkbench().getDisplay();
        }
        return display;
    }

    /**
     * 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)
     * 
     * @seeorg.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;
    }

}