org.eclipse.compare.CompareEditorInput.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.compare.CompareEditorInput.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2013 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.compare;

import java.lang.reflect.InvocationTargetException;
import java.util.ResourceBundle;

import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.internal.BinaryCompareViewer;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
import org.eclipse.compare.internal.CompareEditorInputNavigator;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareStructureViewerSwitchingPane;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.IFlushable2;
import org.eclipse.compare.internal.OutlineViewerCreator;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.ViewerDescriptor;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IOpenListener;
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.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.texteditor.ITextEditorExtension3;

/**
 * A compare operation which can present its results in a special editor.
 * Running the compare operation and presenting the results in a compare editor
 * are combined in one class because it allows a client to keep the implementation
 * all in one place while separating it from the innards of a specific UI implementation of compare/merge.
 * <p>
 * A <code>CompareEditorInput</code> defines methods for the following sequence steps:
 * <UL>
 * <LI>running a lengthy compare operation under progress monitor control,
 * <LI>creating a UI for displaying the model and initializing the some widgets with the compare result,
 * <LI>tracking the dirty state of the model in case of merge,
 * <LI>saving the model.
 * </UL>
 * The Compare plug-in's <code>openCompareEditor</code> method takes an <code>CompareEditorInput</code>
 * and starts sequencing through the above steps. If the compare result is not empty a new compare editor
 * is opened and takes over the sequence until eventually closed.
 * <p>
 * The <code>prepareInput</code> method should contain the
 * code of the compare operation. It is executed under control of a progress monitor
 * and can be canceled. If the result of the compare is not empty, that is if there are differences
 * that needs to be presented, the <code>ICompareInput</code> should hold onto them and return them with
 * the <code>getCompareResult</code> method.
 * If the value returned from <code>getCompareResult</code> is not <code>null</code>
 * a compare editor is opened on the <code>ICompareInput</code> with title and title image initialized by the
 * corresponding methods of the <code>ICompareInput</code>.
 * <p>
 * Creation of the editor's SWT controls is delegated to the <code>createContents</code> method.
 * Here the SWT controls must be created and initialized  with the result of the compare operation.
 * <p>
 * If merging is allowed, the modification state of the compared constituents must be tracked and the dirty
 * state returned from method <code>isSaveNeeded</code>. The value <code>true</code> triggers a subsequent call
 * to <code>save</code> where the modified resources can be saved.
 * <p>
 * The most important part of this implementation is the setup of the compare/merge UI.
 * The UI uses a simple browser metaphor to present compare results.
 * The top half of the layout shows the structural compare results (e.g. added, deleted, and changed files),
 * the bottom half the content compare results (e.g. textual differences between two files).
 * A selection in the top pane is fed to the bottom pane. If a content viewer is registered
 * for the type of the selected object, this viewer is installed in the pane.
 * In addition if a structure viewer is registered for the selection type the top pane
 * is split vertically to make room for another pane and the structure viewer is installed
 * in it. When comparing Java files this second structure viewer would show the structural
 * differences within a Java file, e.g. added, deleted or changed methods and fields.
 * <p>
 * Subclasses provide custom setups, e.g. for a Catch-up/Release operation
 * by passing a subclass of <code>CompareConfiguration</code> and by implementing the <code>prepareInput</code> method.
 * If a subclass cannot use the <code>DiffTreeViewer</code> which is installed by default in the
 * top left pane, method <code>createDiffViewer</code> can be overridden.
 * <p>
 * If subclasses of this class implement {@link ISaveablesSource}, the compare editor will
 * pass these models through to the workbench. The editor will still show the dirty indicator
 * if one of these underlying models is dirty. It is the responsibility of subclasses that
 * implement this interface to call {@link #setDirty(boolean)} when the dirty state of
 * any of the models managed by the subclass change dirty state.
 * 
 * @see CompareUI
 * @see CompareEditorInput
 */
public abstract class CompareEditorInput extends PlatformObject
        implements IEditorInput, IPropertyChangeNotifier, IRunnableWithProgress, ICompareContainer {

    private static final boolean DEBUG = false;

    /**
     * The name of the "dirty" property (value <code>"DIRTY_STATE"</code>).
     */
    public static final String DIRTY_STATE = "DIRTY_STATE"; //$NON-NLS-1$

    /**
     * The name of the "title" property. This property is fired when the title
     * of the compare input changes. Clients should also re-obtain the tool tip
     * when this property changes.
     * @see #getTitle()
     * @since 3.3
     */
    public static final String PROP_TITLE = ICompareUIConstants.PROP_TITLE;

    /**
     * The name of the "title image" property. This property is fired when the title
     * image of the compare input changes.
     * @see #getTitleImage()
     * @since 3.3
     */
    public static final String PROP_TITLE_IMAGE = ICompareUIConstants.PROP_TITLE_IMAGE;

    /**
     * The name of the "selected edition" property. This property is fired when the selected
     * edition of the compare input changes.
     * @see #isEditionSelectionDialog()
     * @see #getSelectedEdition()
     * @since 3.3
     */
    public static final String PROP_SELECTED_EDITION = ICompareUIConstants.PROP_SELECTED_EDITION;

    private static final String COMPARE_EDITOR_IMAGE_NAME = "eview16/compare_view.gif"; //$NON-NLS-1$
    private static Image fgTitleImage;

    private Splitter fComposite;
    private CompareConfiguration fCompareConfiguration;
    private CompareViewerPane fStructureInputPane;
    private CompareViewerSwitchingPane fStructurePane1;
    private CompareViewerSwitchingPane fStructurePane2;
    private CompareViewerSwitchingPane fContentInputPane;
    private CompareViewerPane fFocusPane;
    private String fMessage;
    private Object fInput;
    private String fTitle;
    private ListenerList fListenerList = new ListenerList();
    private CompareNavigator fNavigator;
    private boolean fLeftDirty = false;
    private boolean fRightDirty = false;
    private IPropertyChangeListener fDirtyStateListener;

    boolean fStructureCompareOnSingleClick = true;

    private ICompareContainer fContainer;
    private boolean fContainerProvided;
    private String fHelpContextId;
    private InternalOutlineViewerCreator fOutlineView;
    private ViewerDescriptor fContentViewerDescriptor;
    private ViewerDescriptor fStructureViewerDescriptor;

    private class InternalOutlineViewerCreator extends OutlineViewerCreator {
        private OutlineViewerCreator getWrappedCreator() {
            if (fContentInputPane != null) {
                Viewer v = fContentInputPane.getViewer();
                if (v != null) {
                    return (OutlineViewerCreator) Utilities.getAdapter(v, OutlineViewerCreator.class);
                }
            }
            return null;
        }

        public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent,
                CompareConfiguration configuration) {
            OutlineViewerCreator creator = getWrappedCreator();
            if (creator != null)
                return creator.findStructureViewer(oldViewer, input, parent, configuration);
            return null;
        }

        public boolean hasViewerFor(Object input) {
            OutlineViewerCreator creator = getWrappedCreator();
            return creator != null;
        }

        public Object getInput() {
            OutlineViewerCreator creator = getWrappedCreator();
            if (creator != null)
                return creator.getInput();
            return null;
        }
    }

    /**
     * Creates a <code>CompareEditorInput</code> which is initialized with the given
     * compare configuration.
     * The compare configuration is passed to subsequently created viewers.
     *
     * @param configuration the compare configuration
     */
    public CompareEditorInput(CompareConfiguration configuration) {
        fCompareConfiguration = configuration;
        Assert.isNotNull(configuration);

        fDirtyStateListener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
                String propertyName = e.getProperty();
                if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
                    boolean changed = false;
                    Object newValue = e.getNewValue();
                    if (newValue instanceof Boolean)
                        changed = ((Boolean) newValue).booleanValue();
                    setDirty(e.getSource(), changed);
                }
            }
        };

        IPreferenceStore ps = configuration.getPreferenceStore();
        if (ps != null)
            fStructureCompareOnSingleClick = ps.getBoolean(ComparePreferencePage.OPEN_STRUCTURE_COMPARE);

        fContainer = configuration.getContainer();
        configuration.setContainer(this);
    }

    private boolean structureCompareOnSingleClick() {
        return fStructureCompareOnSingleClick;
    }

    private boolean isShowStructureInOutlineView() {
        Object object = getCompareConfiguration().getProperty(CompareConfiguration.USE_OUTLINE_VIEW);
        return object instanceof Boolean && ((Boolean) object).booleanValue();
    }

    /* (non Javadoc)
     * see IAdaptable.getAdapter
     */
    public Object getAdapter(Class adapter) {
        if (ICompareNavigator.class.equals(adapter) || CompareNavigator.class.equals(adapter)) {
            return getNavigator();
        }
        if (adapter == IShowInSource.class) {
            final IFile file = (IFile) Utilities.getAdapter(this, IFile.class);
            if (file != null)
                return new IShowInSource() {
                    public ShowInContext getShowInContext() {
                        return new ShowInContext(new FileEditorInput(file), StructuredSelection.EMPTY);
                    }
                };
        }
        if (adapter == OutlineViewerCreator.class) {
            synchronized (this) {
                if (fOutlineView == null)
                    fOutlineView = new InternalOutlineViewerCreator();
                return fOutlineView;
            }
        }
        if (adapter == IFindReplaceTarget.class) {
            if (fContentInputPane != null) {
                Viewer v = fContentInputPane.getViewer();
                if (v != null) {
                    return Utilities.getAdapter(v, IFindReplaceTarget.class);
                }
            }
        }
        if (adapter == IEditorInput.class) {
            if (fContentInputPane != null) {
                Viewer v = fContentInputPane.getViewer();
                if (v != null) {
                    return Utilities.getAdapter(v, IEditorInput.class);
                }
            }
        }

        if (adapter == ITextEditorExtension3.class) {
            if (fContentInputPane != null) {
                Viewer v = fContentInputPane.getViewer();
                if (v != null) {
                    return Utilities.getAdapter(v, ITextEditorExtension3.class);
                }
            }
        }

        return super.getAdapter(adapter);
    }

    public synchronized ICompareNavigator getNavigator() {
        if (fNavigator == null)
            fNavigator = new CompareEditorInputNavigator(
                    new Object[] { fStructureInputPane, fStructurePane1, fStructurePane2, fContentInputPane });
        return fNavigator;
    }

    /* (non Javadoc)
     * see IEditorInput.getImageDescriptor
     */
    public ImageDescriptor getImageDescriptor() {
        return null;
    }

    /* (non Javadoc)
     * see IEditorInput.getToolTipText
     */
    public String getToolTipText() {
        return getTitle();
    }

    /* (non Javadoc)
     * see IEditorInput.getName
     */
    public String getName() {
        return getTitle();
    }

    /**
     * Returns <code>null</code> since this editor cannot be persisted.
     *
     * @return <code>null</code> because this editor cannot be persisted
     */
    public IPersistableElement getPersistable() {
        return null;
    }

    /**
     * Returns <code>false</code> to indicate that this input
     * should not appear in the "File Most Recently Used" menu.
     *
     * @return <code>false</code>
     */
    public boolean exists() {
        return false;
    }

    /*
     * FIXME!
      */
    protected void setMessage(String message) {
        fMessage = message;
    }

    /*
     * FIXME!
      */
    public String getMessage() {
        return fMessage;
    }

    /**
     * Returns the title which will be used in the compare editor's title bar.
     * It can be set with <code>setTitle</code>.
     *
     * @return the title
     */
    public String getTitle() {
        if (fTitle == null)
            return Utilities.getString("CompareEditorInput.defaultTitle"); //$NON-NLS-1$
        return fTitle;
    }

    /**
     * Sets the title which will be used when presenting the compare result.
     * This method must be called before the editor is opened.
     * 
     * @param title the title to use for the CompareEditor
     */
    public void setTitle(String title) {
        String oldTitle = fTitle;
        fTitle = title;
        Utilities.firePropertyChange(fListenerList, this, PROP_TITLE, oldTitle, title);
    }

    /**
     * Returns the title image which will be used in the compare editor's title bar.
     * Returns the title image which will be used when presenting the compare result.
     * This implementation returns a generic compare icon.
     * Subclasses can override.
     *
     * @return the title image, or <code>null</code> if none
     */
    public Image getTitleImage() {
        if (fgTitleImage == null) {
            fgTitleImage = CompareUIPlugin.getImageDescriptor(COMPARE_EDITOR_IMAGE_NAME).createImage();
            CompareUI.disposeOnShutdown(fgTitleImage);
        }
        return fgTitleImage;
    }

    /**
     * Returns the configuration object for the viewers within the compare editor.
     * Returns the configuration which was passed to the constructor.
     *
     * @return the compare configuration
     */
    public CompareConfiguration getCompareConfiguration() {
        return fCompareConfiguration;
    }

    /**
     * Adds standard actions to the given <code>ToolBarManager</code>.
     * <p>
     * Subclasses may override to add their own actions.
     * </p>
     *
     * @param toolBarManager the <code>ToolBarManager</code> to which to contribute
     */
    public void contributeToToolBar(ToolBarManager toolBarManager) {
        ResourceBundle bundle = CompareUI.getResourceBundle();
        ChangePropertyAction ignoreWhitespace = ChangePropertyAction.createIgnoreWhiteSpaceAction(bundle,
                getCompareConfiguration());
        toolBarManager.getControl().addDisposeListener(ignoreWhitespace);
        ChangePropertyAction showPseudoConflicts = ChangePropertyAction.createShowPseudoConflictsAction(bundle,
                getCompareConfiguration());
        toolBarManager.getControl().addDisposeListener(showPseudoConflicts);
        toolBarManager.add(new Separator());
        toolBarManager.add(ignoreWhitespace);
        toolBarManager.add(showPseudoConflicts);
    }

    /**
     * Runs the compare operation and stores the compare result.
     *
     * @param monitor the progress monitor to use to display progress and receive
     *   requests for cancelation
     * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
     *    it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
     *  wrapped in an <code>InvocationTargetException</code> by the calling context
     * @exception InterruptedException if the operation detects a request to cancel,
     *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
     *  <code>InterruptedException</code>
     */
    public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
        fInput = prepareInput(monitor);
    }

    /**
     * Runs the compare operation and returns the compare result.
     * If <code>null</code> is returned no differences were found and no compare editor needs to be opened.
     * Progress should be reported to the given progress monitor.
     * A request to cancel the operation should be honored and acknowledged
     * by throwing <code>InterruptedException</code>.
     * <p>
     * Note: this method is typically called in a modal context thread which doesn't have a Display assigned.
     * Implementors of this method shouldn't therefore allocated any SWT resources in this method.
     * </p>
     *
     * @param monitor the progress monitor to use to display progress and receive
     *   requests for cancelation
     * @return the result of the compare operation, or <code>null</code> if there are no differences
     * @exception InvocationTargetException if the <code>prepareInput</code> method must propagate a checked exception,
     *    it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions are automatically
     *  wrapped in an <code>InvocationTargetException</code> by the calling context
     * @exception InterruptedException if the operation detects a request to cancel,
     *  using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
     *  <code>InterruptedException</code>
     */
    protected abstract Object prepareInput(IProgressMonitor monitor)
            throws InvocationTargetException, InterruptedException;

    /**
     * Returns the compare result computed by the most recent call to the
     * <code>run</code> method. Returns <code>null</code> if no
     * differences were found.
     *
     * @return the compare result prepared in method <code>prepareInput</code>
     *   or <code>null</code> if there were no differences
     */
    public Object getCompareResult() {
        return fInput;
    }

    /**
     * Create the SWT controls that are used to display the result of the compare operation.
     * Creates the SWT Controls and sets up the wiring between the individual panes.
     * This implementation creates all four panes but makes only the necessary ones visible.
     * Finally it feeds the compare result into the top left structure viewer
     * and the content viewer.
     * <p>
     * Subclasses may override if they need to change the layout or wiring between panes.
     *
     * @param parent the parent control under which the control must be created
     * @return the SWT control hierarchy for the compare editor
     */
    public Control createContents(Composite parent) {

        fComposite = new Splitter(parent, SWT.VERTICAL);
        fComposite.setData(this);

        Control outline = createOutlineContents(fComposite, SWT.HORIZONTAL);

        fContentInputPane = createContentViewerSwitchingPane(fComposite, SWT.BORDER | SWT.FLAT, this);

        if (fFocusPane == null)
            fFocusPane = fContentInputPane;
        if (outline != null)
            fComposite.setVisible(outline, false);
        fComposite.setVisible(fContentInputPane, true);

        if (fStructureInputPane != null && fComposite.getChildren().length == 2)
            fComposite.setWeights(new int[] { 30, 70 });

        fComposite.layout();

        feedInput();

        fComposite.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                /*
                 * When the UI associated with this compare editor input is
                 * disposed each composite being part of the UI releases its
                 * children first. A dispose listener is added to the last
                 * widget found in that structure. Therefore, compare editor
                 * input is disposed at the end making it possible to refer
                 * during widgets disposal.
                 */
                Composite composite = fComposite;
                Control control = composite;
                while (composite.getChildren().length > 0) {
                    control = composite.getChildren()[composite.getChildren().length - 1];
                    if (control instanceof Composite)
                        composite = (Composite) control;
                    else
                        break;
                }
                control.addDisposeListener(new DisposeListener() {
                    public void widgetDisposed(DisposeEvent ev) {
                        handleDispose();
                    }
                });
            }
        });
        if (fHelpContextId != null)
            PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, fHelpContextId);
        contentsCreated();
        return fComposite;
    }

    /**
     * @param parent the parent control under which the control must be created
     * @param style  the style of widget to construct
     * @param cei the compare editor input for the viewer
     * @return the pane displaying content changes
     * @nooverride This method is not intended to be re-implemented or extended by clients.
     * @noreference This method is not intended to be referenced by clients.
     */
    protected CompareViewerSwitchingPane createContentViewerSwitchingPane(Splitter parent, int style,
            CompareEditorInput cei) {
        return new CompareContentViewerSwitchingPane(parent, style, cei);
    }

    /**
     * Callback that occurs when the UI associated with this compare editor
     * input is disposed. This method will only be invoked if the UI has been
     * created (i.e. after the call to {@link #createContents(Composite)}.
     * Subclasses can extend this method but ensure that the overridden method
     * is invoked.
     * 
     * @since 3.3
     */
    protected void handleDispose() {
        fContainerProvided = false;
        fContainer = null;
        fComposite = null;
        fStructureInputPane = null;
        fStructurePane1 = null;
        fStructurePane2 = null;
        fContentInputPane = null;
        fFocusPane = null;
        fNavigator = null;
        fCompareConfiguration.dispose();
    }

    /**
     * Callback that occurs after the control for the input has
     * been created. If this method gets invoked then {@link #handleDispose()}
     * will be invoked when the control is disposed. Subclasses may extend this
     * method to register any listeners that need to be de-registered when the
     * input is disposed.
     * @since 3.3
     */
    protected void contentsCreated() {
        // Default is to do nothing
    }

    /**
     * @param parent the parent control under which the control must be created
     * @param direction the layout direction of the contents, either </code>SWT.HORIZONTAL<code> or </code>SWT.VERTICAL<code>
     * @return the SWT control hierarchy for the outline part of the compare editor
     * @since 3.0
     */
    public Control createOutlineContents(Composite parent, int direction) {
        final Splitter h = new Splitter(parent, direction);

        fStructureInputPane = createStructureInputPane(h);
        if (hasChildren(getCompareResult()))
            fFocusPane = fStructureInputPane;

        fStructurePane1 = new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
        h.setVisible(fStructurePane1, false);

        fStructurePane2 = new CompareStructureViewerSwitchingPane(h, SWT.BORDER | SWT.FLAT, true, this);
        h.setVisible(fStructurePane2, false);

        // setup the wiring for top left pane
        fStructureInputPane.addOpenListener(new IOpenListener() {
            public void open(OpenEvent oe) {
                feed1(oe.getSelection());
            }
        });
        fStructureInputPane.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent e) {
                ISelection s = e.getSelection();
                if (s == null || s.isEmpty())
                    feed1(s);
                if (isEditionSelectionDialog())
                    firePropertyChange(
                            new PropertyChangeEvent(this, PROP_SELECTED_EDITION, null, getSelectedEdition()));
            }
        });
        fStructureInputPane.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                feedDefault1(event.getSelection());
            }
        });

        fStructurePane1.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent e) {
                feed2(e.getSelection());
            }
        });

        fStructurePane2.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent e) {
                feed3(e.getSelection());
            }
        });

        return h;
    }

    /**
     * Create the pane that will contain the structure input pane (upper left).
     * By default, a {@link CompareViewerSwitchingPane} is returned. Subclasses
     * may override to provide an alternate pane.
     * @param parent the parent composite
     * @return the structure input pane
     * @since 3.3
     */
    protected CompareViewerPane createStructureInputPane(final Composite parent) {
        return new CompareStructureViewerSwitchingPane(parent, SWT.BORDER | SWT.FLAT, true, this) {
            protected Viewer getViewer(Viewer oldViewer, Object input) {
                if (CompareEditorInput.this.hasChildren(input)) {
                    return createDiffViewer(this);
                }
                return super.getViewer(oldViewer, input);
            }
        };
    }

    /* private */ boolean hasChildren(Object input) {
        if (input instanceof IDiffContainer) {
            IDiffContainer dn = (IDiffContainer) input;
            return dn.hasChildren();
        }
        return false;
    }

    private void feedInput() {
        if (fStructureInputPane != null && (fInput instanceof ICompareInput || isCustomStructureInputPane())) {
            if (hasChildren(fInput) || isCustomStructureInputPane()) {
                // The input has multiple entries so set the input of the structure input pane
                fStructureInputPane.setInput(fInput);
            } else if (!structureCompareOnSingleClick() || isShowStructureInOutlineView()) {
                // We want to avoid showing the structure in the editor if we can so first
                // we'll set the content pane to see if we need to provide a structure
                internalSetContentPaneInput(fInput);
                // If the content viewer is unusable
                if (hasUnusableContentViewer() || (structureCompareOnSingleClick() && isShowStructureInOutlineView()
                        && !hasOutlineViewer(fInput))) {
                    fStructureInputPane.setInput(fInput);
                }
            } else {
                fStructureInputPane.setInput(fInput);
            }
            ISelection sel = fStructureInputPane.getSelection();
            if (sel == null || sel.isEmpty())
                feed1(sel); // we only feed downstream viewers if the top left pane is empty
        }
    }

    private boolean hasOutlineViewer(Object input) {
        if (!isShowStructureInOutlineView())
            return false;
        OutlineViewerCreator creator = (OutlineViewerCreator) getAdapter(OutlineViewerCreator.class);
        if (creator != null)
            return creator.hasViewerFor(input);
        return false;
    }

    private boolean hasUnusableContentViewer() {
        return fContentInputPane.isEmpty() || fContentInputPane.getViewer() instanceof BinaryCompareViewer;
    }

    private boolean isCustomStructureInputPane() {
        return !(fStructureInputPane instanceof CompareViewerSwitchingPane);
    }

    private void feed1(final ISelection selection) {
        BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() {
            public void run() {
                if (selection == null || selection.isEmpty()) {
                    Object input = fStructureInputPane.getInput();
                    if (input != null)
                        internalSetContentPaneInput(input);
                    if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
                        return;
                    fStructurePane2.setInput(null); // clear downstream pane
                    fStructurePane1.setInput(null);
                } else {
                    Object input = getElement(selection);
                    internalSetContentPaneInput(input);
                    if (!Utilities.okToUse(fStructurePane1) || !Utilities.okToUse(fStructurePane2))
                        return;
                    if (structureCompareOnSingleClick() || hasUnusableContentViewer())
                        fStructurePane1.setInput(input);
                    fStructurePane2.setInput(null); // clear downstream pane
                    if (fStructurePane1.getInput() != input)
                        fStructurePane1.setInput(null);
                }
            }
        });
    }

    private void feedDefault1(final ISelection selection) {
        BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() {
            public void run() {
                if (!selection.isEmpty())
                    fStructurePane1.setInput(getElement(selection));
            }
        });
    }

    private void feed2(final ISelection selection) {
        BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() {
            public void run() {
                if (selection.isEmpty()) {
                    Object input = fStructurePane1.getInput();
                    internalSetContentPaneInput(input);
                    fStructurePane2.setInput(null);
                } else {
                    Object input = getElement(selection);
                    internalSetContentPaneInput(input);
                    fStructurePane2.setInput(input);
                }
            }
        });
    }

    private void feed3(final ISelection selection) {
        BusyIndicator.showWhile(fComposite.getDisplay(), new Runnable() {
            public void run() {
                if (selection.isEmpty())
                    internalSetContentPaneInput(fStructurePane2.getInput());
                else
                    internalSetContentPaneInput(getElement(selection));
            }
        });

    }

    private void internalSetContentPaneInput(Object input) {
        Object oldInput = fContentInputPane.getInput();
        fContentInputPane.setInput(input);
        if (fOutlineView != null)
            fOutlineView.fireInputChange(oldInput, input);
    }

    /**
     * Returns the first element of the given selection if the selection
     * is a <code>IStructuredSelection</code> with exactly one element. Returns
     * <code>null</code> otherwise.
     *
     * @param selection the selection
     * @return the first element of the selection, or <code>null</code>
     */
    private static Object getElement(ISelection selection) {
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ss = (IStructuredSelection) selection;
            if (ss.size() == 1)
                return ss.getFirstElement();
        }
        return null;
    }

    /**
     * Asks this input to take focus within its container (editor).
     * 
     * @noreference Clients should not call this method but they may override if
     *              they implement a different layout with different visual
     *              components. Clients are free to call the inherited method.
     * 
     * @deprecated Please use {@link #setFocus2()} instead.
     */
    public void setFocus() {
        setFocus2();
    }

    /**
     * Asks this input to take focus within its container (editor).
     * 
     * @noreference Clients should not call this method but they may override if
     *              they implement a different layout with different visual
     *              components. Clients are free to call the inherited method.
     * 
     * @return <code>true</code> if the input got focus, and <code>false</code>
     *         if it was unable to.
     * @since 3.5
     */
    public boolean setFocus2() {
        if (fFocusPane != null) {
            return fFocusPane.setFocus();
        } else if (fComposite != null)
            return fComposite.setFocus();
        return false;
    }

    /**
     * Factory method for creating a differences viewer for the top left pane.
     * It is called from <code>createContents</code> and returns a <code>DiffTreeViewer</code>.
     * <p>
     * Subclasses may override if they need a different viewer.
     * </p>
     *
     * @param parent the SWT parent control under which to create the viewer's SWT controls
     * @return a compare viewer for the top left pane
     */
    public Viewer createDiffViewer(Composite parent) {
        return new DiffTreeViewer(parent, fCompareConfiguration);
    }

    /**
     * Implements the dynamic viewer switching for structure viewers.
     * The method must return a compare viewer based on the old (or current) viewer
     * and a new input object. If the old viewer is suitable for showing the new input the old viewer
     * can be returned. Otherwise a new viewer must be created under the given parent composite or
     * <code>null</code> can be returned to indicate that no viewer could be found.
     * <p>
     * This implementation forwards the request to <code>CompareUI.findStructureViewer</code>.
     * <p>
     * Subclasses may override to implement a different strategy.
     * </p>
     * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
     * @param input the input object for which to find a structure viewer
     * @param parent the SWT parent composite under which the new viewer is created
     * @return a compare viewer which is suitable for the given input object or <code>null</code>
     */
    public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent) {
        return fStructureViewerDescriptor != null
                ? fStructureViewerDescriptor.createViewer(oldViewer, parent, fCompareConfiguration)
                : CompareUI.findStructureViewer(oldViewer, input, parent, fCompareConfiguration);
    }

    /**
     * Implements the dynamic viewer switching for content viewers.
     * The method must return a compare viewer based on the old (or current) viewer
     * and a new input object. If the old viewer is suitable for showing the new input the old viewer
     * can be returned. Otherwise a new viewer must be created under the given parent composite or
     * <code>null</code> can be returned to indicate that no viewer could be found.
     * <p>
     * This implementation forwards the request to <code>CompareUI.findContentViewer</code>.
     * <p>
     * Subclasses may override to implement a different strategy.
     * </p>
     * @param oldViewer a new viewer is only created if this old viewer cannot show the given input
     * @param input the input object for which to find a structure viewer
     * @param parent the SWT parent composite under which the new viewer is created
     * @return a compare viewer which is suitable for the given input object or <code>null</code>
     */
    public Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent) {

        Viewer newViewer = fContentViewerDescriptor != null
                ? fContentViewerDescriptor.createViewer(oldViewer, parent, fCompareConfiguration)
                : CompareUI.findContentViewer(oldViewer, input, parent, fCompareConfiguration);

        boolean isNewViewer = newViewer != oldViewer;
        if (DEBUG)
            System.out.println("CompareEditorInput.findContentViewer: " + isNewViewer); //$NON-NLS-1$

        if (isNewViewer && newViewer instanceof IPropertyChangeNotifier) {
            final IPropertyChangeNotifier dsp = (IPropertyChangeNotifier) newViewer;
            dsp.addPropertyChangeListener(fDirtyStateListener);

            Control c = newViewer.getControl();
            c.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    dsp.removePropertyChangeListener(fDirtyStateListener);
                }
            });
        }

        return newViewer;
    }

    /**
     * @param vd
     *            the content viewer descriptor
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    public void setContentViewerDescriptor(ViewerDescriptor vd) {
        this.fContentViewerDescriptor = vd;
    }

    /**
     * @return the content viewer descriptor set for the input
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    public ViewerDescriptor getContentViewerDescriptor() {
        return this.fContentViewerDescriptor;
    }

    /**
     * @param vd
     *            the structure viewer descriptor
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    public void setStructureViewerDescriptor(ViewerDescriptor vd) {
        this.fStructureViewerDescriptor = vd;
    }

    /**
     * @return the structure viewer descriptor set for the input
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    public ViewerDescriptor getStructureViewerDescriptor() {
        return this.fStructureViewerDescriptor;
    }

    /**
     * Returns <code>true</code> if there are unsaved changes in either left or
     * right side. The value returned is the value of the
     * <code>DIRTY_STATE</code> property of this input object.
     * 
     * Returns <code>true</code> if left or right side has unsaved changes
     * Subclasses don't have to override if the functionality provided by
     * <code>setDirty</code> is sufficient.
     * 
     * @return <code>true</code> if there are changes that need to be saved
     */
    public boolean isSaveNeeded() {
        return isLeftSaveNeeded() || isRightSaveNeeded();
    }

    /**
     * Returns <code>true</code> if there are unsaved changes for left side.
     * 
     * @return <code>true</code> if there are changes that need to be saved
     * @noreference This method is not intended to be referenced by clients.
     */
    protected boolean isLeftSaveNeeded() {
        return fLeftDirty;
    }

    /**
     * Returns <code>true</code> if there are unsaved changes for right side.
     * 
     * @return <code>true</code> if there are changes that need to be saved
     * @noreference This method is not intended to be referenced by clients.
     */
    protected boolean isRightSaveNeeded() {
        return fRightDirty;
    }

    /**
     * Returns <code>true</code> if there are unsaved changes.
     * The method should be called by any parts or dialogs
     * that contain the input.
     * By default, this method calls {@link #isSaveNeeded()}
     * but subclasses may extend.
     * @return <code>true</code> if there are unsaved changes
     * @since 3.3
     */
    public boolean isDirty() {
        return isSaveNeeded();
    }

    /**
     * Sets the dirty state of this input to the given value and sends out a
     * <code>PropertyChangeEvent</code> if the new value differs from the old
     * value. Direct calling this method with parameter dirty equal to
     * <code>false</code> when there are unsaved changes in viewers, results in
     * inconsistent state. The dirty state of compare input should be based only
     * on the information if there are changes in viewers for left or right
     * side.
     * 
     * @param dirty
     *            the dirty state for this compare input
     */
    public void setDirty(boolean dirty) {
        boolean oldDirty = isSaveNeeded();
        fLeftDirty = dirty;
        fRightDirty = dirty;

        if (oldDirty != isSaveNeeded()) {
            Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty),
                    Boolean.valueOf(isSaveNeeded()));
        }
    }

    /**
     * Sets the dirty state of left site of this input to the given value and
     * sends out a <code>PropertyChangeEvent</code> if the new value for whole
     * input differs from the old value. Direct calling this method with
     * parameter dirty equal to <code>false</code> when there are unsaved
     * changes in left viewer, results in inconsistent state. The dirty state of
     * compare input should be based only on the information if there are
     * changes in viewers for left side.
     * 
     * @param dirty
     *            the dirty state for this compare input
     * @since 3.7
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    protected void setLeftDirty(boolean dirty) {
        boolean oldDirty = isSaveNeeded();
        fLeftDirty = dirty;

        if (oldDirty != isSaveNeeded()) {
            Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty),
                    Boolean.valueOf(isSaveNeeded()));
        }
    }

    /**
     * Sets the dirty state of right site of this input to the given value and
     * sends out a <code>PropertyChangeEvent</code> if the new value for whole
     * input differs from the old value. Direct calling this method with
     * parameter dirty equal to <code>false</code> when there are unsaved
     * changes in right viewer, results in inconsistent state. The dirty state
     * of compare input should be based only on the information if there are
     * changes in viewers for right side.
     * 
     * @param dirty
     *            the dirty state for this compare input
     * @since 3.7
     * @noreference This method is not intended to be referenced by clients.
     * @nooverride This method is not intended to be re-implemented or extended
     *             by clients.
     */
    protected void setRightDirty(boolean dirty) {
        boolean oldDirty = isSaveNeeded();
        fRightDirty = dirty;

        if (oldDirty != isSaveNeeded()) {
            Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty),
                    Boolean.valueOf(isSaveNeeded()));
        }
    }

    /**
     * Method adds or removes viewers that changed left or right side of this
     * compare input. Any modification of any of the list of viewers may result
     * in dirty state change.
     * 
     * @param source
     *            the object that fired <code>PropertyChangeEvent</code>
     *            modifying the dirty state
     * @param dirty
     *            value that describes if the changes were added or removed
     */
    private void setDirty(Object source, boolean dirty) {
        Assert.isNotNull(source);
        boolean oldDirty = isSaveNeeded();

        if (source instanceof ContentMergeViewer) {
            ContentMergeViewer cmv = (ContentMergeViewer) source;

            if (dirty == cmv.internalIsLeftDirty()) {
                fLeftDirty = cmv.internalIsLeftDirty();
            }

            if (dirty == cmv.internalIsRightDirty()) {
                fRightDirty = cmv.internalIsRightDirty();
            }
        } else {
            fLeftDirty = dirty;
        }

        boolean newDirty = isSaveNeeded();
        if (DEBUG) {
            System.out.println("setDirty(" + source + ", " + dirty + "): " + newDirty); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }
        if (oldDirty != newDirty) {
            Utilities.firePropertyChange(fListenerList, this, DIRTY_STATE, Boolean.valueOf(oldDirty),
                    Boolean.valueOf(newDirty));
        }
    }

    /* (non Javadoc)
     * see IPropertyChangeNotifier.addListener
     */
    public void addPropertyChangeListener(IPropertyChangeListener listener) {
        if (listener != null)
            fListenerList.add(listener);
    }

    /* (non Javadoc)
     * see IPropertyChangeNotifier.removeListener
     */
    public void removePropertyChangeListener(IPropertyChangeListener listener) {
        if (fListenerList != null) {
            fListenerList.remove(listener);
        }
    }

    /**
     * Save any unsaved changes.
     * Empty implementation.
     * Subclasses must override to save any changes.
     *
     * @param pm an <code>IProgressMonitor</code> that the implementation of save may use to show progress
     * @deprecated Override method saveChanges instead.
     */
    public void save(IProgressMonitor pm) {
        // empty default implementation
    }

    /**
     * Save any unsaved changes.
     * Subclasses must override to save any changes.
     * This implementation tries to flush changes in all viewers by
     * calling <code>ISavable.save</code> on them.
     *
     * @param monitor an <code>IProgressMonitor</code> that the implementation of save may use to show progress
     * @throws CoreException
     * @since 2.0
     */
    public void saveChanges(IProgressMonitor monitor) throws CoreException {

        flushViewers(monitor);

        save(monitor);
    }

    /**
     * Flush the viewer contents into the input.
     * @param monitor a progress monitor
     * @since 3.3
     */
    protected void flushViewers(IProgressMonitor monitor) {
        // flush changes in any dirty viewer
        flushViewer(fStructureInputPane, monitor);
        flushViewer(fStructurePane1, monitor);
        flushViewer(fStructurePane2, monitor);
        flushViewer(fContentInputPane, monitor);
    }

    /**
     * @param monitor
     * @noreference This method is not intended to be referenced by clients.
     */
    protected void flushLeftViewers(IProgressMonitor monitor) {
        // flush changes in left dirty viewer
        flushViewer(fStructureInputPane, monitor);
        flushViewer(fStructurePane1, monitor);
        flushViewer(fStructurePane2, monitor);
        flushLeftViewer(fContentInputPane, monitor);
    }

    /**
     * @param monitor
     * @noreference This method is not intended to be referenced by clients.
     */
    protected void flushRightViewers(IProgressMonitor monitor) {
        // flush changes in right dirty viewer
        flushViewer(fStructureInputPane, monitor);
        flushViewer(fStructurePane1, monitor);
        flushViewer(fStructurePane2, monitor);
        flushRightViewer(fContentInputPane, monitor);
    }

    private static void flushViewer(CompareViewerPane pane, IProgressMonitor pm) {
        if (pane != null) {
            IFlushable flushable = (IFlushable) Utilities.getAdapter(pane, IFlushable.class);
            if (flushable != null)
                flushable.flush(pm);
        }
    }

    private static void flushLeftViewer(CompareViewerPane pane, IProgressMonitor pm) {
        if (pane != null) {
            IFlushable2 flushable = (IFlushable2) Utilities.getAdapter(pane, IFlushable2.class);
            if (flushable != null)
                flushable.flushLeft(pm);
        }
    }

    private static void flushRightViewer(CompareViewerPane pane, IProgressMonitor pm) {
        if (pane != null) {
            IFlushable2 flushable = (IFlushable2) Utilities.getAdapter(pane, IFlushable2.class);
            if (flushable != null)
                flushable.flushRight(pm);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#addCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
     */
    public void addCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener) {
        if (fContainer == null) {
            input.addCompareInputChangeListener(listener);
        } else {
            fContainer.addCompareInputChangeListener(input, listener);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#removeCompareInputChangeListener(org.eclipse.compare.structuremergeviewer.ICompareInput, org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener)
     */
    public void removeCompareInputChangeListener(ICompareInput input, ICompareInputChangeListener listener) {
        if (fContainer == null) {
            input.removeCompareInputChangeListener(listener);
        } else {
            fContainer.removeCompareInputChangeListener(input, listener);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#registerContextMenu(org.eclipse.jface.action.MenuManager, org.eclipse.jface.viewers.ISelectionProvider)
     */
    public void registerContextMenu(MenuManager menu, ISelectionProvider selectionProvider) {
        if (fContainer != null)
            fContainer.registerContextMenu(menu, selectionProvider);
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#setStatusMessage(java.lang.String)
     */
    public void setStatusMessage(String message) {
        if (!fContainerProvided) {
            // Try the action bars directly
            IActionBars actionBars = getActionBars();
            if (actionBars != null) {
                IStatusLineManager slm = actionBars.getStatusLineManager();
                if (slm != null) {
                    slm.setMessage(message);
                }
            }
        } else if (fContainer != null) {
            fContainer.setStatusMessage(message);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#getActionBars()
     */
    public IActionBars getActionBars() {
        if (fContainer != null) {
            IActionBars actionBars = fContainer.getActionBars();
            if (actionBars == null && !fContainerProvided) {
                // The old way to find the action bars
                return Utilities.findActionBars(fComposite);
            }
            return actionBars;
        }
        return null;
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#getServiceLocator()
     */
    public IServiceLocator getServiceLocator() {
        IServiceLocator serviceLocator = fContainer.getServiceLocator();
        if (serviceLocator == null && !fContainerProvided) {
            // The old way to find the service locator
            return Utilities.findSite(fComposite);
        }
        return serviceLocator;
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.ICompareContainer#getWorkbenchPart()
     */
    public IWorkbenchPart getWorkbenchPart() {
        if (fContainer != null)
            return fContainer.getWorkbenchPart();
        return null;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.operation.IRunnableContext#run(boolean, boolean, org.eclipse.jface.operation.IRunnableWithProgress)
     */
    public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
            throws InvocationTargetException, InterruptedException {
        if (fContainer != null)
            fContainer.run(fork, cancelable, runnable);
    }

    public void runAsynchronously(IRunnableWithProgress runnable) {
        if (fContainer != null)
            fContainer.runAsynchronously(runnable);
    }

    /**
     * Set the container of this input to the given container
     * @param container the container
     * @since 3.3
     */
    public void setContainer(ICompareContainer container) {
        Assert.isNotNull(container);
        this.fContainer = container;
        fContainerProvided = true;
    }

    /**
     * Return the container of this input or <code>null</code> if there is no container
     * set.
     * @return the container of this input or <code>null</code>
     * @since 3.3
     */
    public final ICompareContainer getContainer() {
        return fContainer;
    }

    /**
     * Fire the given property change event to all listeners
     * registered with this compare editor input.
     * @param event the property change event
     * @since 3.3
     */
    protected void firePropertyChange(PropertyChangeEvent event) {
        Utilities.firePropertyChange(fListenerList, event);
    }

    /**
     * Return whether this compare editor input can be run as a job.
     * By default, <code>false</code> is returned since traditionally inputs
     * were prepared in the foreground (i.e the UI was blocked when the
     * {@link #run(IProgressMonitor)} method (and indirectly the
     * {@link #prepareInput(IProgressMonitor)} method) was invoked. Subclasses
     * may override.
     * @return whether this compare editor input can be run in the background
     * @since 3.3
     */
    public boolean canRunAsJob() {
        return false;
    }

    /**
     * Return whether this input belongs to the given family
     * when it is run as a job.
     * @see #canRunAsJob()
     * @see Job#belongsTo(Object)
     * @param family the job family
     * @return whether this input belongs to the given family
     * @since 3.3
     */
    public boolean belongsTo(Object family) {
        return family == this;
    }

    /**
     * Return whether this input is intended to be used to select
     * a particular edition of an element in a dialog. The result
     * of this method is only consider if neither sides of the
     * input are editable. By default, <code>false</code> is returned.
     * @return whether this input is intended to be used to select
     * a particular edition of an element in a dialog
     * @see #getOKButtonLabel()
     * @see #okPressed()
     * @see #getSelectedEdition()
     * @since 3.3
     */
    public boolean isEditionSelectionDialog() {
        return false;
    }

    /**
     * Return the label to be used for the <code>OK</code>
     * button when this input is displayed in a dialog.
     * By default, different labels are used depending on
     * whether the input is editable or is for edition selection
     * (see {@link #isEditionSelectionDialog()}.
     * @return the label to be used for the <code>OK</code>
     * button when this input is displayed in a dialog
     * @since 3.3
     */
    public String getOKButtonLabel() {
        if (isEditable())
            return CompareMessages.CompareDialog_commit_button;
        if (isEditionSelectionDialog())
            return CompareMessages.CompareEditorInput_0;
        return IDialogConstants.OK_LABEL;
    }

    /**
     * Return the label used for the <code>CANCEL</code>
     * button when this input is shown in a compare dialog
     * using {@link CompareUI#openCompareDialog(CompareEditorInput)}.
     * @return the label used for the <code>CANCEL</code> button
     * @since 3.3
     */
    public String getCancelButtonLabel() {
        return IDialogConstants.CANCEL_LABEL;
    }

    private boolean isEditable() {
        return getCompareConfiguration().isLeftEditable() || getCompareConfiguration().isRightEditable();
    }

    /**
     * The <code>OK</code> button was pressed in a dialog. If one or both of
     * the sides of the input is editable then any changes will be saved. If the
     * input is for edition selection (see {@link #isEditionSelectionDialog()}),
     * it is up to subclasses to override this method in order to perform the
     * appropriate operation on the selected edition.
     * 
     * @return whether the dialog should be closed or not.
     * @since 3.3
     */
    public boolean okPressed() {
        if (isEditable()) {
            if (!saveChanges())
                return false;
        }
        return true;
    }

    /**
     * The <code>CANCEL</code> button was pressed in a dialog.
     * By default, nothing is done. Subclasses may override.
     * @since 3.3
     */
    public void cancelPressed() {
        // Do nothing
    }

    private boolean saveChanges() {
        try {
            PlatformUI.getWorkbench().getProgressService().run(true, true, new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    try {
                        saveChanges(monitor);
                    } catch (CoreException e) {
                        throw new InvocationTargetException(e);
                    }
                }

            });
            return true;
        } catch (InterruptedException x) {
            // Ignore
        } catch (OperationCanceledException x) {
            // Ignore
        } catch (InvocationTargetException x) {
            ErrorDialog.openError(fComposite.getShell(), CompareMessages.CompareDialog_error_title, null,
                    new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, NLS
                            .bind(CompareMessages.CompareDialog_error_message, x.getTargetException().getMessage()),
                            x.getTargetException()));
        }
        return false;
    }

    /**
     * Return the selected edition or <code>null</code> if no edition is selected.
     * The result of this method should only be considered if {@link #isEditionSelectionDialog()}
     * returns <code>true</code>.
     * @return the selected edition or <code>null</code>
     * @since 3.3
     */
    public Object getSelectedEdition() {
        if (fStructureInputPane != null) {
            ISelection selection = fStructureInputPane.getSelection();
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection) selection;
                if (!ss.isEmpty())
                    return ss.getFirstElement();

            }
        }
        return null;
    }

    /**
     * Set the help context id for this input.
     * @param helpContextId the help context id.
     * @since 3.3
     */
    public void setHelpContextId(String helpContextId) {
        this.fHelpContextId = helpContextId;
    }

}