org.eclipse.compare.codereview.compareEditor.RefacContentMergeViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.compare.codereview.compareEditor.RefacContentMergeViewer.java

Source

package org.eclipse.compare.codereview.compareEditor;

/*******************************************************************************
 * Copyright (c) 2000, 2011 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
 *******************************************************************************/
import java.util.ResourceBundle;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.ICompareContainer;
import org.eclipse.compare.ICompareInputLabelProvider;
import org.eclipse.compare.IPropertyChangeNotifier;
import org.eclipse.compare.contentmergeviewer.IFlushable;
import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareEditor;
import org.eclipse.compare.internal.CompareHandlerService;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ICompareUIConstants;
import org.eclipse.compare.internal.IFlushable2;
import org.eclipse.compare.internal.ISavingSaveable;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.ViewerSwitchingCancelled;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.TextProcessor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISaveablesSource;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.Saveable;

/**
 * An abstract compare and merge viewer with two side-by-side content areas
 * and an optional content area for the ancestor. The implementation makes no
 * assumptions about the content type.
 * <p>
 * <code>ContentMergeViewer</code>
 * <ul>
 * <li>implements the overall layout and defines hooks so that subclasses
 *   can easily provide an implementation for a specific content type,
 * <li>implements the UI for making the areas resizable,
 * <li>has an action for controlling whether the ancestor area is visible or not,
 * <li>has actions for copying one side of the input to the other side,
 * <li>tracks the dirty state of the left and right sides and send out notification
 *   on state changes.
 * </ul>
 * A <code>ContentMergeViewer</code> accesses its
 * model by means of a content provider which must implement the
 * <code>IMergeViewerContentProvider</code> interface.
 * </p>
 * <p>
 * Clients may wish to use the standard concrete subclass <code>TextMergeViewer</code>,
 * or define their own subclass.
 * 
 * @see IMergeViewerContentProvider
 * @see TextMergeViewer
 */
public abstract class RefacContentMergeViewer extends ContentViewer
        implements IPropertyChangeNotifier, IFlushable, IFlushable2 {

    /* package */ static final int HORIZONTAL = 1;
    /* package */ static final int VERTICAL = 2;

    static final double HSPLIT = 0.5;
    static final double VSPLIT = 0.3;

    private class ContentMergeViewerLayout extends Layout {

        public Point computeSize(Composite c, int w, int h, boolean force) {
            return new Point(100, 100);
        }

        public void layout(Composite composite, boolean force) {

            // determine some derived sizes
            int headerHeight = fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
            Rectangle r = composite.getClientArea();

            int centerWidth = getCenterWidth();
            int width1 = (int) ((r.width - centerWidth) * getHorizontalSplitRatio());
            int width2 = r.width - width1 - centerWidth;

            int height1 = 0;
            int height2 = 0;
            if (fIsThreeWay && fAncestorVisible) {
                height1 = (int) ((r.height - (2 * headerHeight)) * fVSplit);
                height2 = r.height - (2 * headerHeight) - height1;
            } else {
                height1 = 0;
                height2 = r.height - headerHeight;
            }

            int y = 0;

            if (fIsThreeWay && fAncestorVisible) {
                fAncestorLabel.setBounds(0, y, r.width, headerHeight);
                fAncestorLabel.setVisible(true);
                y += headerHeight;
                handleResizeAncestor(0, y, r.width, height1);
                y += height1;
            } else {
                fAncestorLabel.setVisible(false);
                handleResizeAncestor(0, 0, 0, 0);
            }

            fLeftLabel.getSize(); // without this resizing would not always work

            if (centerWidth > 3) {
                fLeftLabel.setBounds(0, y, width1 + 1, headerHeight);
                fDirectionLabel.setVisible(true);
                fDirectionLabel.setBounds(width1 + 1, y, centerWidth - 1, headerHeight);
                fRightLabel.setBounds(width1 + centerWidth, y, width2, headerHeight);
            } else {
                fLeftLabel.setBounds(0, y, width1, headerHeight);
                fDirectionLabel.setVisible(false);
                fRightLabel.setBounds(width1, y, r.width - width1, headerHeight);
            }

            y += headerHeight;

            if (fCenter != null && !fCenter.isDisposed())
                fCenter.setBounds(width1, y, centerWidth, height2);

            handleResizeLeftRight(0, y, width1, centerWidth, width2, height2);
        }

        private double getHorizontalSplitRatio() {
            if (fHSplit < 0) {
                Object input = getInput();
                if (input instanceof ICompareInput) {
                    ICompareInput ci = (ICompareInput) input;
                    if (ci.getLeft() == null)
                        return 0.1;
                    if (ci.getRight() == null)
                        return 0.9;
                }
                return HSPLIT;
            }
            return fHSplit;
        }
    }

    class Resizer extends MouseAdapter implements MouseMoveListener {

        Control fControl;
        int fX, fY;
        int fWidth1, fWidth2;
        int fHeight1, fHeight2;
        int fDirection;
        boolean fLiveResize;
        boolean fIsDown;

        public Resizer(Control c, int dir) {
            fDirection = dir;
            fControl = c;
            fLiveResize = !(fControl instanceof Sash);
            updateCursor(c, dir);
            fControl.addMouseListener(this);
            fControl.addMouseMoveListener(this);
            fControl.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(DisposeEvent e) {
                    fControl = null;
                }
            });
        }

        public void mouseDoubleClick(MouseEvent e) {
            if ((fDirection & HORIZONTAL) != 0)
                fHSplit = -1;
            if ((fDirection & VERTICAL) != 0)
                fVSplit = VSPLIT;
            fComposite.layout(true);
        }

        public void mouseDown(MouseEvent e) {
            Composite parent = fControl.getParent();

            Point s = parent.getSize();
            Point as = fAncestorLabel.getSize();
            Point ys = fLeftLabel.getSize();
            Point ms = fRightLabel.getSize();

            fWidth1 = ys.x;
            fWidth2 = ms.x;
            fHeight1 = fLeftLabel.getLocation().y - as.y;
            fHeight2 = s.y - (fLeftLabel.getLocation().y + ys.y);

            fX = e.x;
            fY = e.y;
            fIsDown = true;
        }

        public void mouseUp(MouseEvent e) {
            fIsDown = false;
            if (!fLiveResize)
                resize(e);
        }

        public void mouseMove(MouseEvent e) {
            if (fIsDown && fLiveResize)
                resize(e);
        }

        private void resize(MouseEvent e) {
            int dx = e.x - fX;
            int dy = e.y - fY;

            int centerWidth = fCenter.getSize().x;

            if (fWidth1 + dx > centerWidth && fWidth2 - dx > centerWidth) {
                fWidth1 += dx;
                fWidth2 -= dx;
                if ((fDirection & HORIZONTAL) != 0)
                    fHSplit = (double) fWidth1 / (double) (fWidth1 + fWidth2);
            }
            if (fHeight1 + dy > centerWidth && fHeight2 - dy > centerWidth) {
                fHeight1 += dy;
                fHeight2 -= dy;
                if ((fDirection & VERTICAL) != 0)
                    fVSplit = (double) fHeight1 / (double) (fHeight1 + fHeight2);
            }

            fComposite.layout(true);
            fControl.getDisplay().update();
        }
    }

    /** Style bits for top level composite */
    private int fStyles;
    private ResourceBundle fBundle;
    private final CompareConfiguration fCompareConfiguration;
    private IPropertyChangeListener fPropertyChangeListener;
    private ICompareInputChangeListener fCompareInputChangeListener;
    private ListenerList fListenerList;
    boolean fConfirmSave = true;

    private double fHSplit = -1; // width ratio of left and right panes
    private double fVSplit = VSPLIT; // height ratio of ancestor and bottom panes

    private boolean fIsThreeWay; // whether their is an ancestor
    private boolean fAncestorVisible; // whether the ancestor pane is visible
    private ActionContributionItem fAncestorItem;

    private Action fCopyLeftToRightAction; // copy from left to right
    private Action fCopyRightToLeftAction; // copy from right to left

    private boolean fIsLeftDirty;
    private boolean fIsRightDirty;

    private CompareHandlerService fHandlerService;

    // SWT widgets
    /* package */ Composite fComposite;
    private CLabel fAncestorLabel;
    private CLabel fLeftLabel;
    private CLabel fRightLabel;
    /* package */ CLabel fDirectionLabel;
    /* package */ Control fCenter;

    //---- SWT resources to be disposed
    private Image fRightArrow;
    private Image fLeftArrow;
    private Image fBothArrow;
    Cursor fNormalCursor;
    private Cursor fHSashCursor;
    private Cursor fVSashCursor;
    private Cursor fHVSashCursor;

    private ILabelProviderListener labelChangeListener = new ILabelProviderListener() {
        public void labelProviderChanged(LabelProviderChangedEvent event) {
            Object[] elements = event.getElements();
            for (int i = 0; i < elements.length; i++) {
                Object object = elements[i];
                if (object == getInput())
                    updateHeader();
            }
        }
    };

    //---- end

    /**
     * Creates a new content merge viewer and initializes with a resource bundle and a
     * configuration.
     * 
     * @param style SWT style bits
     * @param bundle the resource bundle
     * @param cc the configuration object
     */
    protected RefacContentMergeViewer(int style, ResourceBundle bundle, CompareConfiguration cc) {

        fStyles = style & ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT); // remove BIDI direction bits
        fBundle = bundle;

        fAncestorVisible = Utilities.getBoolean(cc, ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible);
        fConfirmSave = Utilities.getBoolean(cc, CompareEditor.CONFIRM_SAVE_PROPERTY, fConfirmSave);

        setContentProvider(new MergeViewerContentProvider(cc));

        fCompareInputChangeListener = new ICompareInputChangeListener() {
            public void compareInputChanged(ICompareInput input) {
                if (input == getInput()) {
                    handleCompareInputChange();
                }
            }
        };

        // Make sure the compare configuration is not null
        if (cc == null)
            fCompareConfiguration = new CompareConfiguration();
        else
            fCompareConfiguration = cc;
        fPropertyChangeListener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                RefacContentMergeViewer.this.handlePropertyChangeEvent(event);
            }
        };
        fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener);

        fIsLeftDirty = false;
        fIsRightDirty = false;
    }

    //---- hooks ---------------------

    /**
     * Returns the viewer's name.
     *
     * @return the viewer's name
     */
    public String getTitle() {
        return Utilities.getString(getResourceBundle(), "title"); //$NON-NLS-1$
    }

    /**
     * Creates the SWT controls for the ancestor, left, and right
     * content areas of this compare viewer.
     * Implementations typically hold onto the controls
     * so that they can be initialized with the input objects in method
     * <code>updateContent</code>.
     *
     * @param composite the container for the three areas
     */
    abstract protected void createControls(Composite composite);

    /**
     * Lays out the ancestor area of the compare viewer.
     * It is called whenever the viewer is resized or when the sashes between
     * the areas are moved to adjust the size of the areas.
     *
     * @param x the horizontal position of the ancestor area within its container
     * @param y the vertical position of the ancestor area within its container
     * @param width the width of the ancestor area
     * @param height the height of the ancestor area
     */
    abstract protected void handleResizeAncestor(int x, int y, int width, int height);

    /**
     * Lays out the left and right areas of the compare viewer.
     * It is called whenever the viewer is resized or when the sashes between
     * the areas are moved to adjust the size of the areas.
     *
     * @param x the horizontal position of the left area within its container
     * @param y the vertical position of the left and right area within its container
     * @param leftWidth the width of the left area
     * @param centerWidth the width of the gap between the left and right areas
     * @param rightWidth the width of the right area
     * @param height the height of the left and right areas
     */
    abstract protected void handleResizeLeftRight(int x, int y, int leftWidth, int centerWidth, int rightWidth,
            int height);

    /**
     * Contributes items to the given <code>ToolBarManager</code>.
     * It is called when this viewer is installed in its container and if the container
     * has a <code>ToolBarManager</code>.
     * The <code>ContentMergeViewer</code> implementation of this method does nothing.
     * Subclasses may reimplement.
     *
     * @param toolBarManager the toolbar manager to contribute to
     */
    protected void createToolItems(ToolBarManager toolBarManager) {
        // empty implementation
    }

    /**
     * Initializes the controls of the three content areas with the given input objects.
     *
     * @param ancestor the input for the ancestor area
     * @param left the input for the left area
     * @param right the input for the right area
     */
    abstract protected void updateContent(Object ancestor, Object left, Object right);

    /**
     * Copies the content of one side to the other side.
     * Called from the (internal) actions for copying the sides of the viewer's input object.
     * 
     * @param leftToRight if <code>true</code>, the left side is copied to the right side;
     * if <code>false</code>, the right side is copied to the left side
     */
    abstract protected void copy(boolean leftToRight);

    /**
     * Returns the byte contents of the left or right side. If the viewer
     * has no editable content <code>null</code> can be returned.
     *
     * @param left if <code>true</code>, the byte contents of the left area is returned;
     *    if <code>false</code>, the byte contents of the right area
     * @return the content as an array of bytes, or <code>null</code>
     */
    abstract protected byte[] getContents(boolean left);

    //----------------------------

    /**
     * Returns the resource bundle of this viewer.
     *
     * @return the resource bundle
     */
    protected ResourceBundle getResourceBundle() {
        return fBundle;
    }

    /**
     * Returns the compare configuration of this viewer,
     * or <code>null</code> if this viewer does not yet have a configuration.
     *
     * @return the compare configuration, or <code>null</code> if none
     */
    protected CompareConfiguration getCompareConfiguration() {
        return fCompareConfiguration;
    }

    /**
     * The <code>ContentMergeViewer</code> implementation of this
     * <code>ContentViewer</code> method
     * checks to ensure that the content provider is an <code>IMergeViewerContentProvider</code>.
     * @param contentProvider the content provider to set. Must implement IMergeViewerContentProvider.
     */
    public void setContentProvider(IContentProvider contentProvider) {
        Assert.isTrue(contentProvider instanceof IMergeViewerContentProvider);
        super.setContentProvider(contentProvider);
    }

    /* package */ IMergeViewerContentProvider getMergeContentProvider() {
        return (IMergeViewerContentProvider) getContentProvider();
    }

    /**
     * The <code>ContentMergeViewer</code> implementation of this
     * <code>Viewer</code> method returns the empty selection. Subclasses may override.
     * @return empty selection.
     */
    public ISelection getSelection() {
        return new ISelection() {
            public boolean isEmpty() {
                return true;
            }
        };
    }

    /**
     * The <code>ContentMergeViewer</code> implementation of this
     * <code>Viewer</code> method does nothing. Subclasses may reimplement.
     * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
     */
    public void setSelection(ISelection selection, boolean reveal) {
        // empty implementation
    }

    /**
     * Callback that is invoked when a property in the compare configuration
     * ({@link #getCompareConfiguration()} changes.
     * @param event the property change event
     * @since 3.3
     */
    protected void handlePropertyChangeEvent(PropertyChangeEvent event) {

        String key = event.getProperty();

        if (key.equals(ICompareUIConstants.PROP_ANCESTOR_VISIBLE)) {
            fAncestorVisible = Utilities.getBoolean(getCompareConfiguration(),
                    ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible);
            fComposite.layout(true);

            updateCursor(fLeftLabel, VERTICAL);
            updateCursor(fDirectionLabel, HORIZONTAL | VERTICAL);
            updateCursor(fRightLabel, VERTICAL);

            return;
        }

        if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) {
            setAncestorVisibility(false, !Utilities.getBoolean(getCompareConfiguration(),
                    ICompareUIConstants.PROP_IGNORE_ANCESTOR, false));
            return;
        }
    }

    void updateCursor(Control c, int dir) {
        if (!(c instanceof Sash)) {
            Cursor cursor = null;
            switch (dir) {
            case VERTICAL:
                if (fAncestorVisible) {
                    if (fVSashCursor == null)
                        fVSashCursor = new Cursor(c.getDisplay(), SWT.CURSOR_SIZENS);
                    cursor = fVSashCursor;
                } else {
                    if (fNormalCursor == null)
                        fNormalCursor = new Cursor(c.getDisplay(), SWT.CURSOR_ARROW);
                    cursor = fNormalCursor;
                }
                break;
            case HORIZONTAL:
                if (fHSashCursor == null)
                    fHSashCursor = new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE);
                cursor = fHSashCursor;
                break;
            case VERTICAL + HORIZONTAL:
                if (fAncestorVisible) {
                    if (fHVSashCursor == null)
                        fHVSashCursor = new Cursor(c.getDisplay(), SWT.CURSOR_SIZEALL);
                    cursor = fHVSashCursor;
                } else {
                    if (fHSashCursor == null)
                        fHSashCursor = new Cursor(c.getDisplay(), SWT.CURSOR_SIZEWE);
                    cursor = fHSashCursor;
                }
                break;
            }
            if (cursor != null)
                c.setCursor(cursor);
        }
    }

    private void setAncestorVisibility(boolean visible, boolean enabled) {
        if (fAncestorItem != null) {
            Action action = (Action) fAncestorItem.getAction();
            if (action != null) {
                action.setChecked(visible);
                action.setEnabled(enabled);
            }
        }
        getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, new Boolean(visible));
    }

    //---- input

    /**
     * Return whether the input is a three-way comparison.
     * @return whether the input is a three-way comparison
     * @since 3.3
     */
    protected boolean isThreeWay() {
        return fIsThreeWay;
    }

    /**
     * Internal hook method called when the input to this viewer is
     * initially set or subsequently changed.
     * <p>
     * The <code>ContentMergeViewer</code> implementation of this <code>Viewer</code>
     * method tries to save the old input by calling <code>doSave(...)</code> and
     * then calls <code>internalRefresh(...)</code>.
     *
     * @param input the new input of this viewer, or <code>null</code> if there is no new input
     * @param oldInput the old input element, or <code>null</code> if there was previously no input
     */
    protected final void inputChanged(Object input, Object oldInput) {

        if (input != oldInput && oldInput != null) {
            ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
            if (lp != null)
                lp.removeListener(labelChangeListener);
        }

        if (input != oldInput && oldInput instanceof ICompareInput) {
            ICompareContainer container = getCompareConfiguration().getContainer();
            container.removeCompareInputChangeListener((ICompareInput) oldInput, fCompareInputChangeListener);
        }

        boolean success = doSave(input, oldInput);

        if (input != oldInput && input instanceof ICompareInput) {
            ICompareContainer container = getCompareConfiguration().getContainer();
            container.addCompareInputChangeListener((ICompareInput) input, fCompareInputChangeListener);
        }

        if (input != oldInput && input != null) {
            ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
            if (lp != null)
                lp.addListener(labelChangeListener);
        }

        if (success) {
            setLeftDirty(false);
            setRightDirty(false);
        }

        if (input != oldInput)
            internalRefresh(input);
    }

    /**
     * This method is called from the <code>Viewer</code> method <code>inputChanged</code>
     * to save any unsaved changes of the old input.
     * <p>
     * The <code>ContentMergeViewer</code> implementation of this
     * method calls <code>saveContent(...)</code>. If confirmation has been turned on
     * with <code>setConfirmSave(true)</code>, a confirmation alert is posted before saving.
     * </p>
     * Clients can override this method and are free to decide whether
     * they want to call the inherited method.
     * @param newInput the new input of this viewer, or <code>null</code> if there is no new input
     * @param oldInput the old input element, or <code>null</code> if there was previously no input
     * @return <code>true</code> if saving was successful, or if the user didn't want to save (by pressing 'NO' in the confirmation dialog).
     * @since 2.0
     */
    protected boolean doSave(Object newInput, Object oldInput) {

        // before setting the new input we have to save the old
        if (isLeftDirty() || isRightDirty()) {

            if (Utilities.RUNNING_TESTS) {
                if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) {
                    flushContent(oldInput, null);
                }
            } else if (fConfirmSave) {
                // post alert
                Shell shell = fComposite.getShell();

                MessageDialog dialog = new MessageDialog(shell,
                        Utilities.getString(getResourceBundle(), "saveDialog.title"), //$NON-NLS-1$
                        null, // accept the default window icon
                        Utilities.getString(getResourceBundle(), "saveDialog.message"), //$NON-NLS-1$
                        MessageDialog.QUESTION,
                        new String[] { IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL, }, 0); // default button index

                switch (dialog.open()) { // open returns index of pressed button
                case 0:
                    flushContent(oldInput, null);
                    break;
                case 1:
                    setLeftDirty(false);
                    setRightDirty(false);
                    break;
                case 2:
                    throw new ViewerSwitchingCancelled();
                }
            } else
                flushContent(oldInput, null);
            return true;
        }
        return false;
    }

    /**
     * Controls whether <code>doSave(Object, Object)</code> asks for confirmation before saving
     * the old input with <code>saveContent(Object)</code>.
     * @param enable a value of <code>true</code> enables confirmation
     * @since 2.0
     */
    public void setConfirmSave(boolean enable) {
        fConfirmSave = enable;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.Viewer#refresh()
     */
    public void refresh() {
        internalRefresh(getInput());
    }

    private void internalRefresh(Object input) {

        IMergeViewerContentProvider content = getMergeContentProvider();
        if (content != null) {
            Object ancestor = content.getAncestorContent(input);
            boolean oldFlag = fIsThreeWay;
            if (Utilities.isHunk(input)) {
                fIsThreeWay = true;
            } else if (input instanceof ICompareInput)
                fIsThreeWay = (((ICompareInput) input).getKind() & Differencer.DIRECTION_MASK) != 0;
            else
                fIsThreeWay = ancestor != null;

            if (fAncestorItem != null)
                fAncestorItem.setVisible(fIsThreeWay);

            if (fAncestorVisible && oldFlag != fIsThreeWay)
                fComposite.layout(true);

            Object left = content.getLeftContent(input);
            Object right = content.getRightContent(input);
            updateContent(ancestor, left, right);

            updateHeader();
            if (Utilities.okToUse(fComposite) && Utilities.okToUse(fComposite.getParent())) {
                ToolBarManager tbm = (ToolBarManager) getToolBarManager(fComposite.getParent());
                if (tbm != null) {
                    updateToolItems();
                    tbm.update(true);
                    tbm.getControl().getParent().layout(true);
                }
            }
        }
    }

    //---- layout & SWT control creation

    /**
     * Builds the SWT controls for the three areas of a compare/merge viewer.
     * <p>
     * Calls the hooks <code>createControls</code> and <code>createToolItems</code>
     * to let subclasses build the specific content areas and to add items to
     * an enclosing toolbar.
     * <p>
     * This method must only be called in the constructor of subclasses.
     *
     * @param parent the parent control
     * @return the new control
     */
    protected final Control buildControl(Composite parent) {

        fComposite = new Composite(parent, fStyles | SWT.LEFT_TO_RIGHT) { // we force a specific direction
            public boolean setFocus() {
                return RefacContentMergeViewer.this.handleSetFocus();
            }
        };
        fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle());

        hookControl(fComposite); // hook help & dispose listener

        fComposite.setLayout(new ContentMergeViewerLayout());

        int style = SWT.SHADOW_OUT;
        fAncestorLabel = new CLabel(fComposite, style | Window.getDefaultOrientation());

        fLeftLabel = new CLabel(fComposite, style | Window.getDefaultOrientation());
        new Resizer(fLeftLabel, VERTICAL);

        fDirectionLabel = new CLabel(fComposite, style);
        fDirectionLabel.setAlignment(SWT.CENTER);
        new Resizer(fDirectionLabel, HORIZONTAL | VERTICAL);

        fRightLabel = new CLabel(fComposite, style | Window.getDefaultOrientation());
        new Resizer(fRightLabel, VERTICAL);

        if (fCenter == null || fCenter.isDisposed())
            fCenter = createCenterControl(fComposite);

        createControls(fComposite);

        fHandlerService = CompareHandlerService.createFor(getCompareConfiguration().getContainer(),
                fComposite.getShell());

        initializeToolbars(parent);

        return fComposite;
    }

    /**
     * Returns the toolbar manager for this viewer.
     * 
     * Subclasses may extend this method and use either the toolbar manager
     * provided by the inherited method by calling
     * super.getToolBarManager(parent) or provide an alternate toolbar manager.
     * 
     * @param parent
     *            a <code>Composite</code> or <code>null</code>
     * @return a <code>IToolBarManager</code>
     * @since 3.4
     */
    protected IToolBarManager getToolBarManager(Composite parent) {
        return CompareViewerPane.getToolBarManager(parent);
    }

    private void initializeToolbars(Composite parent) {
        ToolBarManager tbm = (ToolBarManager) getToolBarManager(parent);
        if (tbm != null) {
            tbm.removeAll();

            // define groups
            tbm.add(new Separator("modes")); //$NON-NLS-1$
            tbm.add(new Separator("merge")); //$NON-NLS-1$
            tbm.add(new Separator("navigation")); //$NON-NLS-1$

            CompareConfiguration cc = getCompareConfiguration();

            if (cc.isRightEditable()) {
                fCopyLeftToRightAction = new Action() {
                    public void run() {
                        copy(true);
                    }
                };
                Utilities.initAction(fCopyLeftToRightAction, getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$
                tbm.appendToGroup("merge", fCopyLeftToRightAction); //$NON-NLS-1$
                fHandlerService.registerAction(fCopyLeftToRightAction, "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$
            }

            if (cc.isLeftEditable()) {
                fCopyRightToLeftAction = new Action() {
                    public void run() {
                        copy(false);
                    }
                };
                Utilities.initAction(fCopyRightToLeftAction, getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$
                tbm.appendToGroup("merge", fCopyRightToLeftAction); //$NON-NLS-1$
                fHandlerService.registerAction(fCopyRightToLeftAction, "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$
            }

            final ChangePropertyAction a = new ChangePropertyAction(fBundle, getCompareConfiguration(),
                    "action.EnableAncestor.", ICompareUIConstants.PROP_ANCESTOR_VISIBLE); //$NON-NLS-1$
            a.setChecked(fAncestorVisible);
            fAncestorItem = new ActionContributionItem(a);
            fAncestorItem.setVisible(false);
            tbm.appendToGroup("modes", fAncestorItem); //$NON-NLS-1$
            tbm.getControl().addDisposeListener(a);

            createToolItems(tbm);
            updateToolItems();

            tbm.update(true);
        }
    }

    /**
     * Callback that is invoked when the control of this merge viewer is given focus.
     * This method should return <code>true</code> if a particular widget was given focus
     * and false otherwise. By default, <code>false</code> is returned. Subclasses may override.
     * @return whether  particular widget was given focus
     * @since 3.3
     */
    protected boolean handleSetFocus() {
        return false;
    }

    /**
     * Return the desired width of the center control. This width is used
     * to calculate the values used to layout the ancestor, left and right sides.
     * @return the desired width of the center control
     * @see #handleResizeLeftRight(int, int, int, int, int, int)
     * @see #handleResizeAncestor(int, int, int, int)
     * @since 3.3
     */
    protected int getCenterWidth() {
        return 3;
    }

    /**
     * Return whether the ancestor pane is visible or not.
     * @return whether the ancestor pane is visible or not
     * @since 3.3
     */
    protected boolean isAncestorVisible() {
        return fAncestorVisible;
    }

    /**
     * Create the control that divides the left and right sides of the merge viewer.
     * @param parent the parent composite
     * @return the center control
     * @since 3.3
     */
    protected Control createCenterControl(Composite parent) {
        Sash sash = new Sash(parent, SWT.VERTICAL);
        new Resizer(sash, HORIZONTAL);
        return sash;
    }

    /**
     * Return the center control that divides the left and right sides of the merge viewer.
     * This method returns the control that was created by calling {@link #createCenterControl(Composite)}.
     * @see #createCenterControl(Composite)
     * @return the center control
     * @since 3.3
     */
    protected Control getCenterControl() {
        return fCenter;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.Viewer#getControl()
     */
    public Control getControl() {
        return fComposite;
    }

    /**
     * Called on the viewer disposal.
     * Unregisters from the compare configuration.
     * Clients may extend if they have to do additional cleanup.
     * @see org.eclipse.jface.viewers.ContentViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
     */
    protected void handleDispose(DisposeEvent event) {

        if (fHandlerService != null)
            fHandlerService.dispose();

        Object input = getInput();
        if (input instanceof ICompareInput) {
            ICompareContainer container = getCompareConfiguration().getContainer();
            container.removeCompareInputChangeListener((ICompareInput) input, fCompareInputChangeListener);
        }
        if (input != null) {
            ICompareInputLabelProvider lp = getCompareConfiguration().getLabelProvider();
            if (lp != null)
                lp.removeListener(labelChangeListener);
        }

        if (fPropertyChangeListener != null) {
            fCompareConfiguration.removePropertyChangeListener(fPropertyChangeListener);
            fPropertyChangeListener = null;
        }

        fAncestorLabel = null;
        fLeftLabel = null;
        fDirectionLabel = null;
        fRightLabel = null;
        fCenter = null;

        if (fRightArrow != null) {
            fRightArrow.dispose();
            fRightArrow = null;
        }
        if (fLeftArrow != null) {
            fLeftArrow.dispose();
            fLeftArrow = null;
        }
        if (fBothArrow != null) {
            fBothArrow.dispose();
            fBothArrow = null;
        }

        if (fNormalCursor != null) {
            fNormalCursor.dispose();
            fNormalCursor = null;
        }
        if (fHSashCursor != null) {
            fHSashCursor.dispose();
            fHSashCursor = null;
        }
        if (fVSashCursor != null) {
            fVSashCursor.dispose();
            fVSashCursor = null;
        }
        if (fHVSashCursor != null) {
            fHVSashCursor.dispose();
            fHVSashCursor = null;
        }

        super.handleDispose(event);
    }

    /**
     * Updates the enabled state of the toolbar items.
     * <p>
     * This method is called whenever the state of the items needs updating.
     * <p>
     * Subclasses may extend this method, although this is generally not required.
     */
    protected void updateToolItems() {

        IMergeViewerContentProvider content = getMergeContentProvider();

        Object input = getInput();

        if (fCopyLeftToRightAction != null) {
            boolean enable = content.isRightEditable(input);
            //         if (enable && input instanceof ICompareInput) {
            //            ITypedElement e= ((ICompareInput) input).getLeft();
            //            if (e == null)
            //               enable= false;
            //         }
            fCopyLeftToRightAction.setEnabled(enable);
        }

        if (fCopyRightToLeftAction != null) {
            boolean enable = content.isLeftEditable(input);
            //         if (enable && input instanceof ICompareInput) {
            //            ITypedElement e= ((ICompareInput) input).getRight();
            //            if (e == null)
            //               enable= false;
            //         }
            fCopyRightToLeftAction.setEnabled(enable);
        }
    }

    /**
     * Updates the headers of the three areas
     * by querying the content provider for a name and image for
     * the three sides of the input object.
     * <p>
     * This method is called whenever the header must be updated.
     * <p>
     * Subclasses may extend this method, although this is generally not required.
     */
    protected void updateHeader() {

        IMergeViewerContentProvider content = getMergeContentProvider();
        Object input = getInput();

        // Only change a label if there is a new label available
        if (fAncestorLabel != null) {
            Image ancestorImage = content.getAncestorImage(input);
            if (ancestorImage != null)
                fAncestorLabel.setImage(ancestorImage);
            String ancestorLabel = content.getAncestorLabel(input);
            if (ancestorLabel != null)
                fAncestorLabel.setText(LegacyActionTools.escapeMnemonics(TextProcessor.process(ancestorLabel)));
        }
        if (fLeftLabel != null) {
            Image leftImage = content.getLeftImage(input);
            if (leftImage != null)
                fLeftLabel.setImage(leftImage);
            String leftLabel = content.getLeftLabel(input);
            if (leftLabel != null)
                fLeftLabel.setText(LegacyActionTools.escapeMnemonics(leftLabel));
        }
        if (fRightLabel != null) {
            Image rightImage = content.getRightImage(input);
            if (rightImage != null)
                fRightLabel.setImage(rightImage);
            String rightLabel = content.getRightLabel(input);
            if (rightLabel != null)
                fRightLabel.setText(LegacyActionTools.escapeMnemonics(rightLabel));
        }
    }

    /*
     * Calculates the height of the header.
     */
    /* package */ int getHeaderHeight() {
        int headerHeight = fLeftLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
        headerHeight = Math.max(headerHeight, fDirectionLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y);
        return headerHeight;
    }

    //---- dirty state & saving state

    /* (non-Javadoc)
     * @see org.eclipse.compare.IPropertyChangeNotifier#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
     */
    public void addPropertyChangeListener(IPropertyChangeListener listener) {
        if (fListenerList == null)
            fListenerList = new ListenerList();
        fListenerList.add(listener);
    }

    /* (non-Javadoc)
     * @see org.eclipse.compare.IPropertyChangeNotifier#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
     */
    public void removePropertyChangeListener(IPropertyChangeListener listener) {
        if (fListenerList != null) {
            fListenerList.remove(listener);
            if (fListenerList.isEmpty())
                fListenerList = null;
        }
    }

    private void fireDirtyState(boolean state) {
        Utilities.firePropertyChange(fListenerList, this, CompareEditorInput.DIRTY_STATE, null, new Boolean(state));
    }

    /**
     * Sets the dirty state of the left side of this viewer.
     * If the new value differs from the old
     * all registered listener are notified with
     * a <code>PropertyChangeEvent</code> with the
     * property name <code>CompareEditorInput.DIRTY_STATE</code>.
     *
     * @param dirty the state of the left side dirty flag
     */
    protected void setLeftDirty(boolean dirty) {
        if (isLeftDirty() != dirty) {
            fIsLeftDirty = dirty;
            // Always fire the event if the dirty state has changed
            fireDirtyState(dirty);
        }
    }

    /**
     * Sets the dirty state of the right side of this viewer.
     * If the new value differs from the old
     * all registered listener are notified with
     * a <code>PropertyChangeEvent</code> with the
     * property name <code>CompareEditorInput.DIRTY_STATE</code>.
     *
     * @param dirty the state of the right side dirty flag
     */
    protected void setRightDirty(boolean dirty) {
        if (isRightDirty() != dirty) {
            fIsRightDirty = dirty;
            // Always fire the event if the dirty state has changed
            fireDirtyState(dirty);
        }
    }

    /**
     * Method from the old internal <code>ISavable</code> interface
     * Save the viewers's content.
     * Note: this method is for internal use only. Clients should not call this method.
     * @param monitor a progress monitor
     * @throws CoreException
     * @deprecated use {@link IFlushable#flush(IProgressMonitor)}.
     */
    public void save(IProgressMonitor monitor) throws CoreException {
        flush(monitor);
    }

    /**
     * Flush any modifications made in the viewer into the compare input. This method
     * calls {@link #flushContent(Object, IProgressMonitor)} with the compare input
     * of the viewer as the first parameter.
     * @param monitor a progress monitor
     * @see org.eclipse.compare.contentmergeviewer.IFlushable#flush(org.eclipse.core.runtime.IProgressMonitor)
     * @since 3.3
     */
    public final void flush(IProgressMonitor monitor) {
        flushContent(getInput(), monitor);
    }

    /**
     * Flush the modified content back to input elements via the content provider.
     * The provided input may be the current input of the viewer or it may be
     * the previous input (i.e. this method may be called to flush modified content
     * during an input change).
     * @param input the compare input
     * @param monitor a progress monitor or <code>null</code> if the method
     * was call from a place where a progress monitor was not available.
     * @since 3.3
     */
    protected void flushContent(Object input, IProgressMonitor monitor) {
        flushLeftSide(input, monitor);
        flushRightSide(input, monitor);
    }

    void flushLeftSide(Object input, IProgressMonitor monitor) {
        IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider();

        boolean rightEmpty = content.getRightContent(input) == null;

        if (getCompareConfiguration().isLeftEditable() && isLeftDirty()) {
            byte[] bytes = getContents(true);
            if (rightEmpty && bytes != null && bytes.length == 0)
                bytes = null;
            setLeftDirty(false);
            content.saveLeftContent(input, bytes);
        }
    }

    void flushRightSide(Object input, IProgressMonitor monitor) {
        IMergeViewerContentProvider content = (IMergeViewerContentProvider) getContentProvider();

        boolean leftEmpty = content.getLeftContent(input) == null;

        if (getCompareConfiguration().isRightEditable() && isRightDirty()) {
            byte[] bytes = getContents(false);
            if (leftEmpty && bytes != null && bytes.length == 0)
                bytes = null;
            setRightDirty(false);
            content.saveRightContent(input, bytes);
        }
    }

    /**
     * @param monitor
     * @noreference This method is not intended to be referenced by clients.
     */
    public void flushLeft(IProgressMonitor monitor) {
        flushLeftSide(getInput(), monitor);
    }

    /**
     * @param monitor
     * @noreference This method is not intended to be referenced by clients.
     */
    public void flushRight(IProgressMonitor monitor) {
        flushRightSide(getInput(), monitor);
    }

    /**
     * Return the dirty state of the right side of this viewer.
     * @return the dirty state of the right side of this viewer
     * @since 3.3
     */
    protected boolean isRightDirty() {
        return fIsRightDirty;
    }

    /**
     * @return the dirty state of the right side of this viewer
     * @since 3.7
     * @noreference This method is not intended to be referenced by clients.
     */
    public boolean internalIsRightDirty() {
        return isRightDirty();
    }

    /**
     * Return the dirty state of the left side of this viewer.
     * @return the dirty state of the left side of this viewer
     * @since 3.3
     */
    protected boolean isLeftDirty() {
        return fIsLeftDirty;
    }

    /**
     * @return the dirty state of the left side of this viewer
     * @since 3.7
     * @noreference This method is not intended to be referenced by clients.
     */
    public boolean internalIsLeftDirty() {
        return isLeftDirty();
    }

    /**
     * Handle a change to the given input reported from an {@link org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener}.
     * This class registers a listener with its input and reports any change events through
     * this method. By default, this method prompts for any unsaved changes and then refreshes
     * the viewer. Subclasses may override.
     * @since 3.3
     */
    protected void handleCompareInputChange() {
        // before setting the new input we have to save the old
        Object input = getInput();
        if (!isSaving() && (isLeftDirty() || isRightDirty())) {

            if (Utilities.RUNNING_TESTS) {
                if (Utilities.TESTING_FLUSH_ON_COMPARE_INPUT_CHANGE) {
                    flushContent(input, null);
                }
            } else {
                // post alert
                Shell shell = fComposite.getShell();

                MessageDialog dialog = new MessageDialog(shell,
                        CompareMessages.ContentMergeViewer_resource_changed_title, null, // accept the default window icon
                        CompareMessages.ContentMergeViewer_resource_changed_description, MessageDialog.QUESTION,
                        new String[] { IDialogConstants.YES_LABEL, // 0
                                IDialogConstants.NO_LABEL, // 1
                        }, 0); // default button index

                switch (dialog.open()) { // open returns index of pressed button
                case 0:
                    flushContent(input, null);
                    break;
                case 1:
                    setLeftDirty(false);
                    setRightDirty(false);
                    break;
                }
            }
        }
        if (isSaving() && (isLeftDirty() || isRightDirty())) {
            return; // Do not refresh until saving both sides is complete
        }
        refresh();
    }

    CompareHandlerService getCompareHandlerService() {
        return fHandlerService;
    }

    /**
     * @return true if any of the Saveables is being saved
     */
    private boolean isSaving() {
        ICompareContainer container = fCompareConfiguration.getContainer();
        ISaveablesSource source = null;
        if (container instanceof ISaveablesSource) {
            source = (ISaveablesSource) container;
        } else {
            IWorkbenchPart part = container.getWorkbenchPart();
            if (part instanceof ISaveablesSource) {
                source = (ISaveablesSource) part;
            }
        }
        if (source != null) {
            Saveable[] saveables = source.getSaveables();
            for (int i = 0; i < saveables.length; i++) {
                if (saveables[i] instanceof ISavingSaveable) {
                    ISavingSaveable saveable = (ISavingSaveable) saveables[i];
                    if (saveable.isSaving())
                        return true;
                }
            }
        }
        return false;
    }

}