org.eclipse.php.internal.ui.compare.TextMergeViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.php.internal.ui.compare.TextMergeViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Zend Technologies
 *******************************************************************************/
package org.eclipse.php.internal.ui.compare;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import org.eclipse.compare.*;
import org.eclipse.compare.contentmergeviewer.IDocumentRange;
import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider;
import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.contentmergeviewer.TokenComparator;
import org.eclipse.compare.internal.*;
import org.eclipse.compare.internal.merge.DocumentMerger;
import org.eclipse.compare.internal.merge.DocumentMerger.Diff;
import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput;
import org.eclipse.compare.patch.IHunk;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.compare.structuremergeviewer.*;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.internal.core.documentModel.parser.PhpSourceParser;
import org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.*;
import org.eclipse.wst.sse.core.internal.document.StructuredDocumentFactory;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.text.JobSafeStructuredDocument;

import com.ibm.icu.text.MessageFormat;

/**
 * A text merge viewer uses the <code>RangeDifferencer</code> to perform a
 * textual, line-by-line comparison of two (or three) input documents. It is
 * based on the <code>ContentMergeViewer</code> and uses <code>TextViewer</code>
 * s to implement the ancestor, left, and right content areas.
 * <p>
 * In the three-way compare case ranges of differing lines are highlighted and
 * framed with different colors to show whether the difference is an incoming,
 * outgoing, or conflicting change. The <code>TextMergeViewer</code> supports
 * the notion of a current "differing range" and provides toolbar buttons to
 * navigate from one range to the next (or previous).
 * <p>
 * If there is a current "differing range" and the underlying document is
 * editable the <code>TextMergeViewer</code> enables actions in context menu and
 * toolbar to copy a range from one side to the other side, thereby performing a
 * merge operation.
 * <p>
 * In addition to a line-by-line comparison the <code>TextMergeViewer</code>
 * uses a token based compare on differing lines. The token compare is activated
 * when navigating into a range of differing lines. At first the lines are
 * selected as a block. When navigating into this block the token compare shows
 * for every line the differing token by selecting them.
 * <p>
 * The <code>TextMergeViewer</code>'s default token compare works on characters
 * separated by whitespace. If a different strategy is needed (for example, Java
 * tokens in a Java-aware merge viewer), clients can create their own token
 * comparators by implementing the <code>ITokenComparator</code> interface and
 * overriding the <code>TextMergeViewer.createTokenComparator</code> factory
 * method).
 * <p>
 * Access to the <code>TextMergeViewer</code>'s model is by means of an
 * <code>IMergeViewerContentProvider</code>. Its <code>get<it>X</it></code>
 * Content</code> methods must return either an <code>IDocument</code>, an
 * <code>IDocumentRange</code>, or an <code>IStreamContentAccessor</code>. In
 * the <code>IDocumentRange</code> case the <code>TextMergeViewer</code> works
 * on a subrange of a document. In the <code>IStreamContentAccessor</code> case
 * a document is created internally and initialized from the stream.
 * <p>
 * A <code>TextMergeViewer</code> can be used as is. However clients may
 * subclass to customize the behavior. For example a
 * <code>MergeTextViewer</code> for Java would override the
 * <code>configureTextViewer</code> method to configure the
 * <code>TextViewer</code> for Java source code, the
 * <code>createTokenComparator</code> method to create a Java specific
 * tokenizer.
 * 
 * @see org.eclipse.compare.rangedifferencer.RangeDifferencer
 * @see org.eclipse.jface.text.TextViewer
 * @see ITokenComparator
 * @see IDocumentRange
 * @see org.eclipse.compare.IStreamContentAccessor
 */
public class TextMergeViewer extends ContentMergeViewer implements IAdaptable {

    private static final String COPY_LEFT_TO_RIGHT_INDICATOR = ">"; //$NON-NLS-1$
    private static final String COPY_RIGHT_TO_LEFT_INDICATOR = "<"; //$NON-NLS-1$
    private static final char ANCESTOR_CONTRIBUTOR = MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR;
    private static final char RIGHT_CONTRIBUTOR = MergeViewerContentProvider.RIGHT_CONTRIBUTOR;
    private static final char LEFT_CONTRIBUTOR = MergeViewerContentProvider.LEFT_CONTRIBUTOR;

    private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$

    static final boolean DEBUG = false;

    private static final boolean FIX_47640 = true;

    private static final String[] GLOBAL_ACTIONS = { ActionFactory.UNDO.getId(), ActionFactory.REDO.getId(),
            ActionFactory.CUT.getId(), ActionFactory.COPY.getId(), ActionFactory.PASTE.getId(),
            ActionFactory.DELETE.getId(), ActionFactory.SELECT_ALL.getId(), ActionFactory.SAVE.getId(),
            ActionFactory.FIND.getId() };
    private static final String[] TEXT_ACTIONS = { MergeSourceViewer.UNDO_ID, MergeSourceViewer.REDO_ID,
            MergeSourceViewer.CUT_ID, MergeSourceViewer.COPY_ID, MergeSourceViewer.PASTE_ID,
            MergeSourceViewer.DELETE_ID, MergeSourceViewer.SELECT_ALL_ID, MergeSourceViewer.SAVE_ID,
            MergeSourceViewer.FIND_ID };

    private static final String BUNDLE_NAME = "org.eclipse.compare.contentmergeviewer.TextMergeViewerResources"; //$NON-NLS-1$

    // the following symbolic constants must match the IDs in Compare's
    // plugin.xml
    private static final String INCOMING_COLOR = "INCOMING_COLOR"; //$NON-NLS-1$
    private static final String OUTGOING_COLOR = "OUTGOING_COLOR"; //$NON-NLS-1$
    private static final String CONFLICTING_COLOR = "CONFLICTING_COLOR"; //$NON-NLS-1$
    private static final String RESOLVED_COLOR = "RESOLVED_COLOR"; //$NON-NLS-1$

    // constants
    /** Width of left and right vertical bar */
    private static final int MARGIN_WIDTH = 6;
    /** Width of center bar */
    private static final int CENTER_WIDTH = 34;
    /** Width of birds eye view */
    private static final int BIRDS_EYE_VIEW_WIDTH = 12;
    /** Width of birds eye view */
    private static final int BIRDS_EYE_VIEW_INSET = 2;
    /** */
    private static final int RESOLVE_SIZE = 5;

    /** line width of change borders */
    private static final int LW = 1;

    // determines whether a change between left and right is considered incoming
    // or outgoing
    private boolean fLeftIsLocal;
    private boolean fShowCurrentOnly = false;
    private boolean fShowCurrentOnly2 = false;
    private int fMarginWidth = MARGIN_WIDTH;
    private int fTopInset;

    // Colors
    private RGB fBackground;
    private RGB fForeground;
    private boolean fPollSystemForeground = true;
    private boolean fPollSystemBackground = true;

    private RGB SELECTED_INCOMING;
    private RGB INCOMING;
    private RGB INCOMING_FILL;
    private RGB INCOMING_TEXT_FILL;

    private RGB SELECTED_CONFLICT;
    private RGB CONFLICT;
    private RGB CONFLICT_FILL;
    private RGB CONFLICT_TEXT_FILL;

    private RGB SELECTED_OUTGOING;
    private RGB OUTGOING;
    private RGB OUTGOING_FILL;
    private RGB OUTGOING_TEXT_FILL;

    private RGB RESOLVED;

    private IPreferenceStore fPreferenceStore;
    private IPropertyChangeListener fPreferenceChangeListener;

    private HashMap fNewAncestorRanges = new HashMap();
    private HashMap fNewLeftRanges = new HashMap();
    private HashMap fNewRightRanges = new HashMap();

    private MergeSourceViewer fAncestor;
    private MergeSourceViewer fLeft;
    private MergeSourceViewer fRight;

    private int fLeftLineCount;
    private int fRightLineCount;

    private boolean fInScrolling;

    private int fPts[] = new int[8]; // scratch area for polygon drawing

    private int fInheritedDirection; // inherited direction
    private int fTextDirection; // requested direction for embedded SourceViewer

    private ActionContributionItem fIgnoreAncestorItem;
    private boolean fHighlightRanges;

    private boolean fShowPseudoConflicts = false;

    private boolean fUseSplines = true;
    private boolean fUseSingleLine = true;
    private boolean fUseResolveUI = true;
    private boolean fHighlightTokenChanges = false;

    private String fSymbolicFontName;

    private ActionContributionItem fNextDiff; // goto next difference
    private ActionContributionItem fPreviousDiff; // goto previous difference
    private ActionContributionItem fCopyDiffLeftToRightItem;
    private ActionContributionItem fCopyDiffRightToLeftItem;

    private CompareHandlerService fHandlerService;

    private boolean fSynchronizedScrolling = true;

    private MergeSourceViewer fFocusPart;

    private boolean fSubDoc = true;
    private IPositionUpdater fPositionUpdater;
    private boolean fIsMotif;
    private boolean fIsCarbon;

    private boolean fHasErrors;

    // SWT widgets
    private BufferedCanvas fAncestorCanvas;
    private BufferedCanvas fLeftCanvas;
    private BufferedCanvas fRightCanvas;
    private Canvas fScrollCanvas;
    private ScrollBar fVScrollBar;
    private Canvas fBirdsEyeCanvas;
    private Canvas fSummaryHeader;
    private HeaderPainter fHeaderPainter;

    // SWT resources to be disposed
    private Map fColors;
    private Cursor fBirdsEyeCursor;

    // points for center curves
    private double[] fBasicCenterCurve;

    private Button fCenterButton;
    private Diff fButtonDiff;

    private ContributorInfo fLeftContributor;
    private ContributorInfo fRightContributor;
    private ContributorInfo fAncestorContributor;
    private boolean isRefreshing;
    private int fSynchronziedScrollPosition;
    private ActionContributionItem fNextChange;
    private ActionContributionItem fPreviousChange;
    private ShowWhitespaceAction showWhitespaceAction;
    private InternalOutlineViewerCreator fOutlineViewerCreator;
    private TextEditorPropertyAction toggleLineNumbersAction;
    private IFindReplaceTarget fFindReplaceTarget;
    private ChangePropertyAction fIgnoreWhitespace;
    private DocumentMerger fMerger;
    /** The current diff */
    private Diff fCurrentDiff;

    private final class InternalOutlineViewerCreator extends OutlineViewerCreator
            implements ISelectionChangedListener {
        public Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent,
                CompareConfiguration configuration) {
            if (input != getInput())
                return null;
            final Viewer v = CompareUI.findStructureViewer(oldViewer, input, parent, configuration);
            if (v != null) {
                v.getControl().addDisposeListener(new DisposeListener() {
                    public void widgetDisposed(DisposeEvent e) {
                        v.removeSelectionChangedListener(InternalOutlineViewerCreator.this);
                    }
                });
                v.addSelectionChangedListener(this);
            }

            return v;
        }

        public boolean hasViewerFor(Object input) {
            return true;
        }

        public void selectionChanged(SelectionChangedEvent event) {
            ISelection s = event.getSelection();
            if (s instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection) s;
                Object element = ss.getFirstElement();
                Diff diff = findDiff(element);
                if (diff != null)
                    setCurrentDiff(diff, true);
            }
        }

        private Diff findDiff(Object element) {
            if (element instanceof ICompareInput) {
                ICompareInput ci = (ICompareInput) element;
                Position p = getPosition(ci.getLeft());
                if (p != null)
                    return findDiff(p, true);
                p = getPosition(ci.getRight());
                if (p != null)
                    return findDiff(p, false);
            }
            return null;
        }

        private Diff findDiff(Position p, boolean left) {
            for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                Position diffPos;
                if (left) {
                    diffPos = diff.getPosition(LEFT_CONTRIBUTOR);
                } else {
                    diffPos = diff.getPosition(RIGHT_CONTRIBUTOR);
                }
                // If the element falls within a diff, highlight that diff
                if (diffPos.offset + diffPos.length >= p.offset && diff.getKind() != RangeDifference.NOCHANGE)
                    return diff;
                // Otherwise, highlight the first diff after the elements
                // position
                if (diffPos.offset >= p.offset)
                    return diff;
            }
            return null;
        }

        private Position getPosition(ITypedElement left) {
            if (left instanceof DocumentRangeNode) {
                DocumentRangeNode drn = (DocumentRangeNode) left;
                return drn.getRange();
            }
            return null;
        }

        public Object getInput() {
            return TextMergeViewer.this.getInput();
        }
    }

    class ContributorInfo implements IElementStateListener, VerifyListener, IDocumentListener {
        private final TextMergeViewer fViewer;
        private final Object fElement;
        private char fLeg;
        private String fEncoding;
        private IDocumentProvider fDocumentProvider;
        private IEditorInput fDocumentKey;
        private ISelection fSelection;
        private int fTopIndex = -1;
        private boolean fNeedsValidation = false;
        private MergeSourceViewer fSourceViewer;

        public ContributorInfo(TextMergeViewer viewer, Object element, char leg) {
            fViewer = viewer;
            fElement = element;
            fLeg = leg;
            if (fElement instanceof IEncodedStreamContentAccessor) {
                try {
                    fEncoding = ((IEncodedStreamContentAccessor) fElement).getCharset();
                } catch (CoreException e) {
                    // silently ignored
                }
            }
        }

        public String getEncoding() {
            if (fEncoding == null)
                return ResourcesPlugin.getEncoding();
            return fEncoding;
        }

        public void setEncodingIfAbsent(ContributorInfo otherContributor) {
            if (fEncoding == null)
                fEncoding = otherContributor.fEncoding;
        }

        public IDocument getDocument() {
            if (fDocumentProvider != null) {
                IDocument document = fDocumentProvider.getDocument(getDocumentKey());
                if (document != null)
                    return document;
            }
            if (fElement instanceof IDocument)
                return (IDocument) fElement;
            if (fElement instanceof IDocumentRange)
                return ((IDocumentRange) fElement).getDocument();
            if (fElement instanceof IStreamContentAccessor)
                return DocumentManager.get(fElement);
            return null;
        }

        public void setDocument(MergeSourceViewer viewer, boolean isEditable) {
            // Ensure that this method is only called once
            Assert.isTrue(fSourceViewer == null);
            fSourceViewer = viewer;
            try {
                internalSetDocument(viewer);
            } catch (RuntimeException e) {
                // The error may be due to a stale entry in the DocumentManager
                // (see bug 184489)
                clearCachedDocument();
                throw e;
            }
            viewer.setEditable(isEditable);
            // Verify changes if the document is editable
            if (isEditable) {
                fNeedsValidation = true;
                viewer.getTextWidget().addVerifyListener(this);
            }
        }

        /*
         * Returns true if a new Document could be installed.
         */
        private boolean internalSetDocument(MergeSourceViewer tp) {

            if (tp == null)
                return false;

            // since Coloring in preview is mixed up we should
            // unconfigure this before setting new document
            tp.unconfigure();

            IDocument newDocument = null;
            Position range = null;

            if (fElement instanceof IDocumentRange) {
                newDocument = ((IDocumentRange) fElement).getDocument();
                range = ((IDocumentRange) fElement).getRange();
                connectToSharedDocument();

            } else if (fElement instanceof IDocument) {
                newDocument = (IDocument) fElement;

            } else if (fElement instanceof IStreamContentAccessor) {
                newDocument = DocumentManager.get(fElement);
                if (newDocument == null) {
                    newDocument = createDocument();
                    DocumentManager.put(fElement, newDocument);
                    setupDocument(newDocument);
                } else if (fDocumentProvider == null) {
                    // Connect to a shared document so we can get the proper
                    // save synchronization
                    connectToSharedDocument();
                }
            } else if (fElement == null) { // deletion on one side

                ITypedElement parent = this.fViewer.getParent(fLeg); // we try
                // to
                // find
                // an
                // insertion
                // position
                // within
                // the
                // deletion's
                // parent

                if (parent instanceof IDocumentRange) {
                    newDocument = ((IDocumentRange) parent).getDocument();
                    newDocument.addPositionCategory(DIFF_RANGE_CATEGORY);
                    Object input = this.fViewer.getInput();
                    range = this.fViewer.getNewRange(fLeg, input);
                    if (range == null) {
                        int pos = 0;
                        if (input instanceof ICompareInput)
                            pos = this.fViewer.findInsertionPosition(fLeg, (ICompareInput) input);
                        range = new Position(pos, 0);
                        try {
                            newDocument.addPosition(DIFF_RANGE_CATEGORY, range);
                        } catch (BadPositionCategoryException ex) {
                            // silently ignored
                            if (TextMergeViewer.DEBUG)
                                System.out.println("BadPositionCategoryException: " + ex); //$NON-NLS-1$
                        } catch (BadLocationException ex) {
                            // silently ignored
                            if (TextMergeViewer.DEBUG)
                                System.out.println("BadLocationException: " + ex); //$NON-NLS-1$
                        }
                        this.fViewer.addNewRange(fLeg, input, range);
                    }
                } else if (parent instanceof IDocument) {
                    newDocument = (IDocument) fElement;
                }
            }

            boolean enabled = true;
            if (newDocument == null) {
                newDocument = new Document(""); //$NON-NLS-1$
                enabled = false;
            }

            // Update the viewer document or range
            IDocument oldDoc = tp.getDocument();
            if (newDocument != oldDoc) {
                updateViewerDocument(tp, newDocument, range);
            } else { // same document but different range
                updateViewerDocumentRange(tp, range);
            }
            newDocument.addDocumentListener(this);

            // since Coloring in preview is mixed up we should
            // re-configure this after setting new document
            if (newDocument instanceof IStructuredDocument) {
                tp.setDocument(newDocument);
                tp.configure(new PHPStructuredTextViewerConfiguration());
            }

            tp.setEnabled(enabled);

            return enabled;
        }

        /*
         * The viewer document is the same but the range has changed
         */
        private void updateViewerDocumentRange(MergeSourceViewer tp, Position range) {
            tp.setRegion(range);
            if (this.fViewer.fSubDoc) {
                if (range != null) {
                    IRegion r = this.fViewer.normalizeDocumentRegion(tp.getDocument(),
                            TextMergeViewer.toRegion(range));
                    tp.setVisibleRegion(r.getOffset(), r.getLength());
                } else
                    tp.resetVisibleRegion();
            } else
                tp.resetVisibleRegion();
        }

        /*
         * The viewer has a new document
         */
        private void updateViewerDocument(MergeSourceViewer tp, IDocument document, Position range) {
            unsetDocument(tp);
            if (document == null)
                return;

            // Add a position updater to the document
            document.addPositionCategory(DIFF_RANGE_CATEGORY);
            if (this.fViewer.fPositionUpdater == null)
                this.fViewer.fPositionUpdater = this.fViewer.new ChildPositionUpdater(DIFF_RANGE_CATEGORY);
            else
                document.removePositionUpdater(this.fViewer.fPositionUpdater);
            document.addPositionUpdater(this.fViewer.fPositionUpdater);

            // install new document
            tp.setRegion(range);
            if (this.fViewer.fSubDoc) {
                if (range != null) {
                    IRegion r = this.fViewer.normalizeDocumentRegion(document, TextMergeViewer.toRegion(range));
                    tp.setDocument(document, r.getOffset(), r.getLength());
                } else
                    tp.setDocument(document);
            } else
                tp.setDocument(document);

            tp.rememberDocument(document);
        }

        private void unsetDocument(MergeSourceViewer tp) {
            IDocument oldDoc = internalGetDocument(tp);
            if (oldDoc != null) {
                tp.rememberDocument(null);
                try {
                    oldDoc.removePositionCategory(DIFF_RANGE_CATEGORY);
                } catch (BadPositionCategoryException ex) {
                    // Ignore
                }
                if (fPositionUpdater != null)
                    oldDoc.removePositionUpdater(fPositionUpdater);
                oldDoc.removeDocumentListener(this);
            }
        }

        private IDocument createDocument() {
            // If the content provider is a text content provider, attempt to
            // obtain
            // a shared document (i.e. file buffer)
            IDocument newDoc = connectToSharedDocument();

            if (newDoc == null || !(newDoc instanceof IStructuredDocument)) {
                IStreamContentAccessor sca = (IStreamContentAccessor) fElement;
                String s = null;

                try {
                    String encoding = getEncoding();
                    s = Utilities.readString(sca, encoding);
                } catch (CoreException ex) {
                    this.fViewer.setError(fLeg, ex.getMessage());
                }

                //            newDoc= new Document(s != null ? s : ""); //$NON-NLS-1$
                newDoc = StructuredDocumentFactory.getNewStructuredDocumentInstance(new PhpSourceParser());
                newDoc.set(s != null ? s : ""); //$NON-NLS-1$
                // newDoc= new Document(s != null ? s : ""); //$NON-NLS-1$
                IDocumentPartitioner partitioner = getDocumentPartitioner();
                if (partitioner != null) {
                    if (newDoc instanceof JobSafeStructuredDocument) {
                        ((JobSafeStructuredDocument) newDoc).setDocumentPartitioner(
                                "org.eclipse.wst.sse.core.default_structured_text_partitioning", partitioner); //$NON-NLS-1$
                    } else {
                        newDoc.setDocumentPartitioner(partitioner);
                    }
                    partitioner.connect(newDoc);
                }
            }
            return newDoc;
        }

        /**
         * Connect to a shared document if possible. Return <code>null</code> if
         * the connection was not possible.
         * 
         * @return the shared document or <code>null</code> if connection to a
         *         shared document was not possible
         */
        private IDocument connectToSharedDocument() {
            IEditorInput key = getDocumentKey();
            if (key != null) {
                if (fDocumentProvider != null) {
                    // We've already connected and setup the document
                    return fDocumentProvider.getDocument(key);
                }
                IDocumentProvider documentProvider = getDocumentProvider();
                if (documentProvider != null) {
                    try {
                        connect(documentProvider, key);
                        setCachedDocumentProvider(key, documentProvider);
                        IDocument newDoc = documentProvider.getDocument(key);
                        this.fViewer.updateDirtyState(key, documentProvider, fLeg);
                        return newDoc;
                    } catch (CoreException e) {
                        // Connection failed. Log the error and continue without
                        // a shared document
                        CompareUIPlugin.log(e);
                    }
                }
            }
            return null;
        }

        private void connect(IDocumentProvider documentProvider, IEditorInput input) throws CoreException {
            final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement,
                    ISharedDocumentAdapter.class);
            if (sda != null) {
                sda.connect(documentProvider, input);
            } else {
                documentProvider.connect(input);
            }
        }

        private void disconnect(IDocumentProvider provider, IEditorInput input) {
            final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement,
                    ISharedDocumentAdapter.class);
            if (sda != null) {
                sda.disconnect(provider, input);
            } else {
                provider.disconnect(input);
            }
        }

        private void setCachedDocumentProvider(IEditorInput key, IDocumentProvider documentProvider) {
            fDocumentKey = key;
            fDocumentProvider = documentProvider;
            documentProvider.addElementStateListener(this);
        }

        public void disconnect() {
            IDocumentProvider provider = null;
            IEditorInput input = getDocumentKey();
            synchronized (this) {
                if (fDocumentProvider != null) {
                    provider = fDocumentProvider;
                    fDocumentProvider = null;
                    fDocumentKey = null;
                }
            }
            if (provider != null) {
                disconnect(provider, input);
                provider.removeElementStateListener(this);
            }
            // If we have a listener registered with the widget, remove it
            if (fSourceViewer != null && !fSourceViewer.getTextWidget().isDisposed()) {
                if (fNeedsValidation) {
                    fSourceViewer.getTextWidget().removeVerifyListener(this);
                    fNeedsValidation = false;
                }
                IDocument oldDoc = internalGetDocument(fSourceViewer);
                if (oldDoc != null) {
                    oldDoc.removeDocumentListener(this);
                }
            }
            clearCachedDocument();
        }

        private void clearCachedDocument() {
            // Finally, remove the document from the document manager
            IDocument doc = DocumentManager.get(fElement);
            if (doc != null)
                DocumentManager.remove(doc);
        }

        private IDocument internalGetDocument(MergeSourceViewer tp) {
            IDocument oldDoc = tp.getDocument();
            if (oldDoc == null) {
                oldDoc = tp.getRememberedDocument();
            }
            return oldDoc;
        }

        /**
         * Return the document key used to obtain a shared document. A
         * <code>null</code> is returned in the following cases:
         * <ol>
         * <li>This contributor does not have a shared document adapter.</li>
         * <li>This text merge viewer has a document partitioner but uses the
         * default partitioning.</li>
         * <li>This text merge viewer does not use he default content provider.</li>
         * </ol>
         * 
         * @return the document key used to obtain a shared document or
         *         <code>null</code>
         */
        private IEditorInput getDocumentKey() {
            if (fDocumentKey != null)
                return fDocumentKey;
            if (isUsingDefaultContentProvider() && fElement != null && canHaveSharedDocument()) {
                ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement,
                        ISharedDocumentAdapter.class, true);
                if (sda != null) {
                    return sda.getDocumentKey(fElement);
                }
            }
            return null;
        }

        private IDocumentProvider getDocumentProvider() {
            if (fDocumentProvider != null)
                return fDocumentProvider;
            // We will only use document providers if the content provider is
            // the
            // default content provider
            if (isUsingDefaultContentProvider()) {
                IEditorInput input = getDocumentKey();
                if (input != null)
                    return SharedDocumentAdapter.getDocumentProvider(input);
            }
            return null;
        }

        private boolean isUsingDefaultContentProvider() {
            return fViewer.isUsingDefaultContentProvider();
        }

        private boolean canHaveSharedDocument() {
            return fViewer.canHaveSharedDocument();
        }

        boolean hasSharedDocument(Object object) {
            return (fElement == object && fDocumentProvider != null
                    && fDocumentProvider.getDocument(getDocumentKey()) != null);
        }

        public boolean flush() throws CoreException {
            if (fDocumentProvider != null) {
                IEditorInput input = getDocumentKey();
                IDocument document = fDocumentProvider.getDocument(input);
                if (document != null) {
                    final ISharedDocumentAdapter sda = (ISharedDocumentAdapter) Utilities.getAdapter(fElement,
                            ISharedDocumentAdapter.class);
                    if (sda != null) {
                        sda.flushDocument(fDocumentProvider, input, document, false);
                        return true;
                    }
                    try {
                        fDocumentProvider.aboutToChange(input);
                        fDocumentProvider.saveDocument(new NullProgressMonitor(), input, document, false);
                        return true;
                    } finally {
                        fDocumentProvider.changed(input);
                    }
                }
            }
            return false;
        }

        public void elementMoved(Object originalElement, Object movedElement) {
            IEditorInput input = getDocumentKey();
            if (input != null && input.equals(originalElement)) {
                // This method will only get called if the buffer is not dirty
                resetDocument();
            }
        }

        public void elementDirtyStateChanged(Object element, boolean isDirty) {
            if (!checkState())
                return;
            IEditorInput input = getDocumentKey();
            if (input != null && input.equals(element)) {
                this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg);
            }
        }

        public void elementDeleted(Object element) {
            IEditorInput input = getDocumentKey();
            if (input != null && input.equals(element)) {
                // This method will only get called if the buffer is not dirty
                resetDocument();
            }
        }

        private void resetDocument() {
            // Need to remove the document from the manager before refreshing
            // or the old document will still be found
            clearCachedDocument();
            // TODO: This is fine for now but may need to be revisited if a
            // refresh is performed
            // higher up as well (e.g. perhaps a refresh request that waits
            // until after all parties
            // have been notified).
            if (checkState())
                fViewer.refresh();
        }

        private boolean checkState() {
            if (fViewer == null)
                return false;
            Control control = fViewer.getControl();
            if (control == null)
                return false;
            return !control.isDisposed();
        }

        public void elementContentReplaced(Object element) {
            if (!checkState())
                return;
            IEditorInput input = getDocumentKey();
            if (input != null && input.equals(element)) {
                this.fViewer.updateDirtyState(input, getDocumentProvider(), fLeg);
            }
        }

        public void elementContentAboutToBeReplaced(Object element) {
            // Nothing to do
        }

        public Object getElement() {
            return fElement;
        }

        public void cacheSelection(MergeSourceViewer viewer) {
            if (viewer == null) {
                this.fSelection = null;
                this.fTopIndex = -1;
            } else {
                this.fSelection = viewer.getSelection();
                this.fTopIndex = viewer.getTopIndex();
            }
        }

        public void updateSelection(MergeSourceViewer viewer, boolean includeScroll) {
            if (fSelection != null)
                viewer.setSelection(fSelection);
            if (includeScroll && fTopIndex != -1) {
                viewer.setTopIndex(fTopIndex);
            }
        }

        public void transferContributorStateFrom(ContributorInfo oldContributor) {
            if (oldContributor != null) {
                fSelection = oldContributor.fSelection;
                fTopIndex = oldContributor.fTopIndex;
            }

        }

        public boolean validateChange() {
            if (fElement == null)
                return true;
            if (fDocumentProvider instanceof IDocumentProviderExtension) {
                IDocumentProviderExtension ext = (IDocumentProviderExtension) fDocumentProvider;
                if (ext.isReadOnly(fDocumentKey)) {
                    try {
                        ext.validateState(fDocumentKey, getControl().getShell());
                        ext.updateStateCache(fDocumentKey);
                    } catch (CoreException e) {
                        ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_12,
                                CompareMessages.TextMergeViewer_13, e.getStatus());
                        return false;
                    }
                }
                return !ext.isReadOnly(fDocumentKey);
            }
            IEditableContentExtension ext = (IEditableContentExtension) Utilities.getAdapter(fElement,
                    IEditableContentExtension.class);
            if (ext != null) {
                if (ext.isReadOnly()) {
                    IStatus status = ext.validateEdit(getControl().getShell());
                    if (!status.isOK()) {
                        if (status.getSeverity() == IStatus.ERROR) {
                            ErrorDialog.openError(getControl().getShell(), CompareMessages.TextMergeViewer_14,
                                    CompareMessages.TextMergeViewer_15, status);
                            return false;
                        }
                        if (status.getSeverity() == IStatus.CANCEL)
                            return false;
                    }
                }
            }
            return true;
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.
         * events.VerifyEvent)
         */
        public void verifyText(VerifyEvent e) {
            if (!validateChange()) {
                e.doit = false;
            }
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged
         * (org.eclipse.jface.text.DocumentEvent)
         */
        public void documentAboutToBeChanged(DocumentEvent e) {
            // nothing to do
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse
         * .jface.text.DocumentEvent)
         */
        public void documentChanged(DocumentEvent e) {
            boolean dirty = true;
            if (fDocumentProvider != null && fDocumentKey != null) {
                dirty = fDocumentProvider.canSaveDocument(fDocumentKey);
            }
            TextMergeViewer.this.documentChanged(e, dirty);
            // Remove our verify listener since the document is now dirty
            if (fNeedsValidation && fSourceViewer != null && !fSourceViewer.getTextWidget().isDisposed()) {
                fSourceViewer.getTextWidget().removeVerifyListener(this);
                fNeedsValidation = false;
            }
        }
    }

    class HeaderPainter implements PaintListener {

        private static final int INSET = BIRDS_EYE_VIEW_INSET;

        private RGB fIndicatorColor;
        private Color fSeparatorColor;

        public HeaderPainter() {
            fSeparatorColor = fSummaryHeader.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
        }

        /*
         * Returns true on color change
         */
        public boolean setColor(RGB color) {
            RGB oldColor = fIndicatorColor;
            fIndicatorColor = color;
            if (color == null)
                return oldColor != null;
            if (oldColor != null)
                return !color.equals(oldColor);
            return true;
        }

        private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topLeft, Color bottomRight) {
            gc.setForeground(topLeft);
            gc.drawLine(x, y, x + w - 1, y);
            gc.drawLine(x, y, x, y + h - 1);

            gc.setForeground(bottomRight);
            gc.drawLine(x + w, y, x + w, y + h);
            gc.drawLine(x, y + h, x + w, y + h);
        }

        public void paintControl(PaintEvent e) {

            Point s = fSummaryHeader.getSize();

            if (fIndicatorColor != null) {
                Display d = fSummaryHeader.getDisplay();
                e.gc.setBackground(getColor(d, fIndicatorColor));
                int min = Math.min(s.x, s.y) - 2 * INSET;
                Rectangle r = new Rectangle((s.x - min) / 2, (s.y - min) / 2, min, min);
                e.gc.fillRectangle(r);
                if (d != null)
                    drawBevelRect(e.gc, r.x, r.y, r.width - 1, r.height - 1,
                            d.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW),
                            d.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));

                e.gc.setForeground(fSeparatorColor);
                e.gc.setLineWidth(0 /* 1 */);
                e.gc.drawLine(0 + 1, s.y - 1, s.x - 1 - 1, s.y - 1);
            }
        }
    }

    /*
     * The position updater used to adapt the positions representing the child
     * document ranges to changes of the parent document.
     */
    class ChildPositionUpdater extends DefaultPositionUpdater {

        /*
         * Creates the position updated.
         */
        protected ChildPositionUpdater(String category) {
            super(category);
        }

        /*
         * Child document ranges cannot be deleted other then by calling
         * freeChildDocument.
         */
        protected boolean notDeleted() {
            return true;
        }

        /*
         * If an insertion happens at a child document's start offset, the
         * position is extended rather than shifted. Also, if something is added
         * right behind the end of the position, the position is extended rather
         * than kept stable.
         */
        protected void adaptToInsert() {

            if (fPosition == fLeft.getRegion() || fPosition == fRight.getRegion()) {
                int myStart = fPosition.offset;
                int myEnd = fPosition.offset + fPosition.length;
                myEnd = Math.max(myStart, myEnd);

                int yoursStart = fOffset;
                // int yoursEnd = fOffset + fReplaceLength - 1;
                // yoursEnd = Math.max(yoursStart, yoursEnd);

                if (myEnd < yoursStart)
                    return;

                if (myStart <= yoursStart)
                    fPosition.length += fReplaceLength;
                else
                    fPosition.offset += fReplaceLength;
            } else {
                super.adaptToInsert();
            }
        }
    }

    private class ChangeHighlighter implements ITextPresentationListener {

        private final MergeSourceViewer viewer;

        public ChangeHighlighter(MergeSourceViewer viewer) {
            this.viewer = viewer;
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation
         * (org.eclipse.jface.text.TextPresentation)
         */
        public void applyTextPresentation(TextPresentation textPresentation) {
            if (!fHighlightTokenChanges)
                return;
            IRegion region = textPresentation.getExtent();
            Diff[] changeDiffs = fMerger.getChangeDiffs(getLeg(viewer), region);
            for (int i = 0; i < changeDiffs.length; i++) {
                Diff diff = changeDiffs[i];
                StyleRange range = getStyleRange(diff, region);
                if (range != null)
                    textPresentation.mergeStyleRange(range);
            }
        }

        private StyleRange getStyleRange(Diff diff, IRegion region) {
            // Color cText = getColor(null, getTextColor());
            Color cTextFill = getColor(null, getTextFillColor(diff));
            if (cTextFill == null)
                return null;
            Position p = diff.getPosition(getLeg(viewer));
            int start = p.getOffset();
            int length = p.getLength();
            // Don't start before the region
            if (start < region.getOffset()) {
                length = length - (region.getOffset() - start);
                start = region.getOffset();
            }
            // Don't go past the end of the region
            int regionEnd = region.getOffset() + region.getLength();
            if (start + length > regionEnd) {
                length = regionEnd - start;
            }
            if (length < 0)
                return null;

            return new StyleRange(start, length, null, cTextFill);
        }

        private RGB getTextFillColor(Diff diff) {
            if (isThreeWay() && !isIgnoreAncestor()) {
                switch (diff.getKind()) {
                case RangeDifference.RIGHT:
                    if (fLeftIsLocal)
                        return INCOMING_TEXT_FILL;
                    return OUTGOING_TEXT_FILL;
                case RangeDifference.ANCESTOR:
                    return CONFLICT_TEXT_FILL;
                case RangeDifference.LEFT:
                    if (fLeftIsLocal)
                        return OUTGOING_TEXT_FILL;
                    return INCOMING_TEXT_FILL;
                case RangeDifference.CONFLICT:
                    return CONFLICT_TEXT_FILL;
                }
                return null;
            }
            return OUTGOING_TEXT_FILL;
        }

    }

    private class FindReplaceTarget implements IFindReplaceTarget {

        public boolean canPerformFind() {
            return fFocusPart != null;
        }

        public int findAndSelect(int widgetOffset, String findString, boolean searchForward, boolean caseSensitive,
                boolean wholeWord) {
            return fFocusPart.getFindReplaceTarget().findAndSelect(widgetOffset, findString, searchForward,
                    caseSensitive, wholeWord);
        }

        public Point getSelection() {
            return fFocusPart.getFindReplaceTarget().getSelection();
        }

        public String getSelectionText() {
            return fFocusPart.getFindReplaceTarget().getSelectionText();
        }

        public boolean isEditable() {
            return fFocusPart.getFindReplaceTarget().isEditable();
        }

        public void replaceSelection(String text) {
            fFocusPart.getFindReplaceTarget().replaceSelection(text);
        }

    }

    // ---- MergeTextViewer

    /**
     * Creates a text merge viewer under the given parent control.
     * 
     * @param parent
     *            the parent control
     * @param configuration
     *            the configuration object
     */
    public TextMergeViewer(Composite parent, CompareConfiguration configuration) {
        this(parent, SWT.NULL, configuration);
    }

    /**
     * Creates a text merge viewer under the given parent control.
     * 
     * @param parent
     *            the parent control
     * @param style
     *            SWT style bits for top level composite of this viewer
     * @param configuration
     *            the configuration object
     */
    public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
        super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);

        fMerger = new DocumentMerger(new IDocumentMergerInput() {
            public ITokenComparator createTokenComparator(String line) {
                return TextMergeViewer.this.createTokenComparator(line);
            }

            public CompareConfiguration getCompareConfiguration() {
                return TextMergeViewer.this.getCompareConfiguration();
            }

            public IDocument getDocument(char contributor) {
                switch (contributor) {
                case LEFT_CONTRIBUTOR:
                    return fLeft.getDocument();
                case RIGHT_CONTRIBUTOR:
                    return fRight.getDocument();
                case ANCESTOR_CONTRIBUTOR:
                    return fAncestor.getDocument();
                }
                return null;
            }

            public int getHunkStart() {
                return TextMergeViewer.this.getHunkStart();
            }

            public Position getRegion(char contributor) {
                switch (contributor) {
                case LEFT_CONTRIBUTOR:
                    return fLeft.getRegion();
                case RIGHT_CONTRIBUTOR:
                    return fRight.getRegion();
                case ANCESTOR_CONTRIBUTOR:
                    return fAncestor.getRegion();
                }
                return null;
            }

            public boolean isHunkOnLeft() {
                ITypedElement left = ((ICompareInput) getInput()).getRight();
                return left != null && Utilities.getAdapter(left, IHunk.class) != null;
            }

            public boolean isIgnoreAncestor() {
                return TextMergeViewer.this.isIgnoreAncestor();
            }

            public boolean isPatchHunk() {
                return TextMergeViewer.this.isPatchHunk();
            }

            public boolean isShowPseudoConflicts() {
                return fShowPseudoConflicts;
            }

            public boolean isThreeWay() {
                return TextMergeViewer.this.isThreeWay();
            }

            public boolean isPatchHunkOk() {
                return TextMergeViewer.this.isPatchHunkOk();
            }

        });

        int inheritedStyle = parent.getStyle();
        if ((inheritedStyle & SWT.LEFT_TO_RIGHT) != 0)
            fInheritedDirection = SWT.LEFT_TO_RIGHT;
        else if ((inheritedStyle & SWT.RIGHT_TO_LEFT) != 0)
            fInheritedDirection = SWT.RIGHT_TO_LEFT;
        else
            fInheritedDirection = SWT.NONE;

        if ((style & SWT.LEFT_TO_RIGHT) != 0)
            fTextDirection = SWT.LEFT_TO_RIGHT;
        else if ((style & SWT.RIGHT_TO_LEFT) != 0)
            fTextDirection = SWT.RIGHT_TO_LEFT;
        else
            fTextDirection = SWT.NONE;

        fSymbolicFontName = getClass().getName();

        String platform = SWT.getPlatform();
        fIsMotif = "motif".equals(platform); //$NON-NLS-1$
        fIsCarbon = "carbon".equals(platform); //$NON-NLS-1$

        if (fIsMotif)
            fMarginWidth = 0;

        Display display = parent.getDisplay();

        fPreferenceChangeListener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                TextMergeViewer.this.handlePropertyChangeEvent(event);
            }
        };

        fPreferenceStore = createChainedPreferenceStore();
        if (fPreferenceStore != null) {
            fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);

            checkForColorUpdate(display);

            fLeftIsLocal = Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
            fSynchronizedScrolling = fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
            fShowPseudoConflicts = fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);
            // fUseSplines=
            // fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
            fUseSingleLine = fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE);
            fHighlightTokenChanges = fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES);
            // fUseResolveUI=
            // fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI);
        }

        buildControl(parent);

        INavigatable nav = new INavigatable() {
            public boolean selectChange(int flag) {
                if (flag == INavigatable.FIRST_CHANGE || flag == INavigatable.LAST_CHANGE) {
                    selectFirstDiff(flag == INavigatable.FIRST_CHANGE);
                    return false;
                }
                return navigate(flag == INavigatable.NEXT_CHANGE, false, false);
            }

            public Object getInput() {
                return TextMergeViewer.this.getInput();
            }

            public boolean openSelectedChange() {
                return false;
            }

            public boolean hasChange(int flag) {
                return getNextVisibleDiff(flag == INavigatable.NEXT_CHANGE, false) != null;
            }
        };
        fComposite.setData(INavigatable.NAVIGATOR_PROPERTY, nav);

        fBirdsEyeCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);

        JFaceResources.getFontRegistry().addListener(fPreferenceChangeListener);
        JFaceResources.getColorRegistry().addListener(fPreferenceChangeListener);
        updateFont();
    }

    private ChainedPreferenceStore createChainedPreferenceStore() {
        ArrayList stores = new ArrayList(2);
        stores.add(getCompareConfiguration().getPreferenceStore());
        stores.add(EditorsUI.getPreferenceStore());
        return new ChainedPreferenceStore((IPreferenceStore[]) stores.toArray(new IPreferenceStore[stores.size()]));
    }

    private void updateFont() {
        Font f = JFaceResources.getFont(JFaceResources.TEXT_FONT);
        if (f != null) {
            if (fAncestor != null)
                fAncestor.setFont(f);
            if (fLeft != null)
                fLeft.setFont(f);
            if (fRight != null)
                fRight.setFont(f);
        }
    }

    private void checkForColorUpdate(Display display) {
        if (fPollSystemForeground) {
            RGB fg = fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
                    ? display.getSystemColor(SWT.COLOR_LIST_FOREGROUND).getRGB()
                    : new Color(display, PreferenceConverter.getColor(fPreferenceStore,
                            AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)).getRGB();
            if (fForeground == null || !fg.equals(fForeground)) {
                fForeground = fg;
                updateColors(display);
            }
        }
        if (fPollSystemBackground) {
            RGB bg = fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
                    ? display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB()
                    : new Color(display, PreferenceConverter.getColor(fPreferenceStore,
                            AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)).getRGB();
            if (fBackground == null || !bg.equals(fBackground)) {
                fBackground = bg;
                updateColors(display);
            }
        }
    }

    /**
     * Sets the viewer's background color to the given RGB value. If the value
     * is <code>null</code> the system's default background color is used.
     * 
     * @param background
     *            the background color or <code>null</code> to use the system's
     *            default background color
     * @since 2.0
     */
    public void setBackgroundColor(RGB background) {
        fPollSystemBackground = (background == null);
        fBackground = background;
        updateColors(null);
    }

    private RGB getBackground(Display display) {
        if (fBackground != null)
            return fBackground;
        if (display == null)
            display = fComposite.getDisplay();
        return display.getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
    }

    /**
     * Sets the viewer's foreground color to the given RGB value. If the value
     * is <code>null</code> the system's default foreground color is used.
     * 
     * @param foreground
     *            the foreground color or <code>null</code> to use the system's
     *            default foreground color
     * @since 2.0
     */
    public void setForegroundColor(RGB foreground) {
        fPollSystemForeground = (foreground == null);
        fForeground = foreground;
        updateColors(null);
    }

    private void updateColors(Display display) {

        if (display == null)
            display = fComposite.getDisplay();

        Color color = null;
        if (fBackground != null)
            color = getColor(display, fBackground);

        if (fAncestor != null)
            fAncestor.setBackgroundColor(color);
        if (fLeft != null)
            fLeft.setBackgroundColor(color);
        if (fRight != null)
            fRight.setBackgroundColor(color);

        ColorRegistry registry = JFaceResources.getColorRegistry();

        RGB bg = getBackground(display);
        SELECTED_INCOMING = registry.getRGB(INCOMING_COLOR);
        if (SELECTED_INCOMING == null)
            SELECTED_INCOMING = new RGB(0, 0, 255); // BLUE
        INCOMING = interpolate(SELECTED_INCOMING, bg, 0.6);
        INCOMING_FILL = interpolate(SELECTED_INCOMING, bg, 0.97);
        INCOMING_TEXT_FILL = interpolate(SELECTED_INCOMING, bg, 0.85);

        SELECTED_OUTGOING = registry.getRGB(OUTGOING_COLOR);
        if (SELECTED_OUTGOING == null)
            SELECTED_OUTGOING = new RGB(0, 0, 0); // BLACK
        OUTGOING = interpolate(SELECTED_OUTGOING, bg, 0.6);
        OUTGOING_FILL = interpolate(SELECTED_OUTGOING, bg, 0.97);
        OUTGOING_TEXT_FILL = interpolate(SELECTED_OUTGOING, bg, 0.85);

        SELECTED_CONFLICT = registry.getRGB(CONFLICTING_COLOR);
        if (SELECTED_CONFLICT == null)
            SELECTED_CONFLICT = new RGB(255, 0, 0); // RED
        CONFLICT = interpolate(SELECTED_CONFLICT, bg, 0.6);
        CONFLICT_FILL = interpolate(SELECTED_CONFLICT, bg, 0.97);
        CONFLICT_TEXT_FILL = interpolate(SELECTED_CONFLICT, bg, 0.85);

        RESOLVED = registry.getRGB(RESOLVED_COLOR);
        if (RESOLVED == null)
            RESOLVED = new RGB(0, 255, 0); // GREEN

        updatePresentation(display);
    }

    private void updatePresentation(Display display) {
        if (display == null)
            display = fComposite.getDisplay();
        refreshBirdsEyeView();
        invalidateLines();
        updateAllDiffBackgrounds(display);
        invalidateTextPresentation();
    }

    /**
     * Invalidates the current presentation by invalidating the three text
     * viewers.
     * 
     * @since 2.0
     */
    public void invalidateTextPresentation() {
        if (fAncestor != null)
            fAncestor.invalidateTextPresentation();
        if (fLeft != null)
            fLeft.invalidateTextPresentation();
        if (fRight != null)
            fRight.invalidateTextPresentation();
    }

    /**
     * Configures the passed text viewer. This method is called after the three
     * text viewers have been created for the content areas. The
     * <code>TextMergeViewer</code> implementation of this method will configure
     * the viewer with a {@link SourceViewerConfiguration}. Subclasses may
     * reimplement to provide a specific configuration for the text viewer.
     * 
     * @param textViewer
     *            the text viewer to configure
     */
    protected void configureTextViewer(TextViewer textViewer) {
        // to get undo for all text files
        // bugzilla 131895, 33665
        if (textViewer instanceof MergeSourceViewer) {
            SourceViewerConfiguration configuration = new SourceViewerConfiguration();
            ((MergeSourceViewer) textViewer).configure(configuration);
        }
    }

    /**
     * Creates an <code>ITokenComparator</code> which is used to show the intra
     * line differences. The <code>TextMergeViewer</code> implementation of this
     * method returns a tokenizer that breaks a line into words separated by
     * whitespace. Subclasses may reimplement to provide a specific tokenizer.
     * 
     * @param line
     *            the line for which to create the <code>ITokenComparator</code>
     * @return a ITokenComparator which is used for a second level token
     *         compare.
     */
    protected ITokenComparator createTokenComparator(String line) {
        return new TokenComparator(line);
    }

    /**
     * Setup the given document for use with this viewer. By default, the
     * partitioner returned from {@link #getDocumentPartitioner()} is registered
     * as the default partitioner for the document. Subclasses that return a
     * partitioner must also override {@link #getDocumentPartitioning()} if they
     * wish to be able to use shared documents (i.e. file buffers).
     * 
     * @param document
     *            the document to be set up
     * 
     * @since 3.3
     */
    protected void setupDocument(IDocument document) {
        String partitioning = getDocumentPartitioning();
        if (partitioning == null || !(document instanceof IDocumentExtension3)) {
            if (document.getDocumentPartitioner() == null) {
                IDocumentPartitioner partitioner = getDocumentPartitioner();
                if (partitioner != null) {
                    document.setDocumentPartitioner(partitioner);
                    partitioner.connect(document);
                }
            }
        } else {
            IDocumentExtension3 ex3 = (IDocumentExtension3) document;
            if (ex3.getDocumentPartitioner(partitioning) == null) {
                IDocumentPartitioner partitioner = getDocumentPartitioner();
                if (partitioner != null) {
                    ex3.setDocumentPartitioner(partitioning, partitioner);
                    partitioner.connect(document);
                }
            }
        }
    }

    /**
     * Returns a document partitioner which is suitable for the underlying
     * content type. This method is only called if the input provided by the
     * content provider is a <code>IStreamContentAccessor</code> and an internal
     * document must be obtained. This document is initialized with the
     * partitioner returned from this method.
     * <p>
     * The <code>TextMergeViewer</code> implementation of this method returns
     * <code>null</code>. Subclasses may reimplement to create a partitioner for
     * a specific content type. Subclasses that do return a partitioner should
     * also return a partitioning from {@link #getDocumentPartitioning()} in
     * order to make use of shared documents (e.g. file buffers).
     * 
     * @return a document partitioner, or <code>null</code>
     */
    protected IDocumentPartitioner getDocumentPartitioner() {
        return null;
    }

    /**
     * Return the partitioning to which the partitioner returned from
     * {@link #getDocumentPartitioner()} is to be associated. Return
     * <code>null</code> only if partitioning is not needed or if the subclass
     * overrode {@link #setupDocument(IDocument)} directly. By default,
     * <code>null</code> is returned which means that shared documents that
     * return a partitioner from {@link #getDocumentPartitioner()} will not be
     * able to use shared documents.
     * 
     * @see IDocumentExtension3
     * @return a partitioning
     * 
     * @since 3.3
     */
    protected String getDocumentPartitioning() {
        return null;
    }

    /**
     * Called on the viewer disposal. Unregisters from the compare
     * configuration. Clients may extend if they have to do additional cleanup.
     * 
     * @param event
     */
    protected void handleDispose(DisposeEvent event) {

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

        Object input = getInput();
        removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, input);
        removeFromDocumentManager(LEFT_CONTRIBUTOR, input);
        removeFromDocumentManager(RIGHT_CONTRIBUTOR, input);

        if (DEBUG)
            DocumentManager.dump();

        if (fPreferenceChangeListener != null) {
            JFaceResources.getFontRegistry().removeListener(fPreferenceChangeListener);
            JFaceResources.getColorRegistry().removeListener(fPreferenceChangeListener);
            if (fPreferenceStore != null)
                fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
            fPreferenceChangeListener = null;
        }

        fLeftCanvas = null;
        fRightCanvas = null;
        fVScrollBar = null;
        fBirdsEyeCanvas = null;
        fSummaryHeader = null;

        fAncestorContributor.unsetDocument(fAncestor);
        fLeftContributor.unsetDocument(fLeft);
        fRightContributor.unsetDocument(fRight);

        disconnect(fLeftContributor);
        disconnect(fRightContributor);
        disconnect(fAncestorContributor);

        if (fColors != null) {
            Iterator i = fColors.values().iterator();
            while (i.hasNext()) {
                Color color = (Color) i.next();
                if (!color.isDisposed())
                    color.dispose();
            }
            fColors = null;
        }

        if (fBirdsEyeCursor != null) {
            fBirdsEyeCursor.dispose();
            fBirdsEyeCursor = null;
        }

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

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

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

        super.handleDispose(event);
    }

    private void disconnect(ContributorInfo legInfo) {
        if (legInfo != null)
            legInfo.disconnect();
    }

    // -------------------------------------------------------------------------------------------------------------
    // --- internal
    // ------------------------------------------------------------------------------------------------
    // -------------------------------------------------------------------------------------------------------------

    /*
     * Creates the specific SWT controls for the content areas. Clients must not
     * call or override this method.
     */
    protected void createControls(Composite composite) {

        PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, ICompareContextIds.TEXT_MERGE_VIEW);

        // 1st row
        if (fMarginWidth > 0) {
            fAncestorCanvas = new BufferedCanvas(composite, SWT.NONE) {
                public void doPaint(GC gc) {
                    paintSides(gc, fAncestor, fAncestorCanvas, false);
                }
            };
            fAncestorCanvas.addMouseListener(new MouseAdapter() {
                public void mouseDown(MouseEvent e) {
                    setCurrentDiff2(handleMouseInSides(fAncestorCanvas, fAncestor, e.y), false);
                }
            });
        }

        fAncestor = createPart(composite);
        fAncestor.setEditable(false);
        fAncestor.getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
            public void getName(AccessibleEvent e) {
                e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_ancestor,
                        getCompareConfiguration().getAncestorLabel(getInput()));
            }
        });
        fAncestor.addTextPresentationListener(new ChangeHighlighter(fAncestor));

        fSummaryHeader = new Canvas(composite, SWT.NONE);
        fHeaderPainter = new HeaderPainter();
        fSummaryHeader.addPaintListener(fHeaderPainter);
        updateResolveStatus();

        // 2nd row
        if (fMarginWidth > 0) {
            fLeftCanvas = new BufferedCanvas(composite, SWT.NONE) {
                public void doPaint(GC gc) {
                    paintSides(gc, fLeft, fLeftCanvas, false);
                }
            };
            fLeftCanvas.addMouseListener(new MouseAdapter() {
                public void mouseDown(MouseEvent e) {
                    setCurrentDiff2(handleMouseInSides(fLeftCanvas, fLeft, e.y), false);
                }
            });
        }

        fLeft = createPart(composite);
        fLeft.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
        fLeft.addAction(MergeSourceViewer.SAVE_ID, fLeftSaveAction);
        fLeft.getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
            public void getName(AccessibleEvent e) {
                e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_left,
                        getCompareConfiguration().getLeftLabel(getInput()));
            }
        });
        fLeft.addTextPresentationListener(new ChangeHighlighter(fLeft));

        fRight = createPart(composite);
        fRight.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
        fRight.addAction(MergeSourceViewer.SAVE_ID, fRightSaveAction);
        fRight.getTextWidget().getAccessible().addAccessibleListener(new AccessibleAdapter() {
            public void getName(AccessibleEvent e) {
                e.result = NLS.bind(CompareMessages.TextMergeViewer_accessible_right,
                        getCompareConfiguration().getRightLabel(getInput()));
            }
        });
        fRight.addTextPresentationListener(new ChangeHighlighter(fRight));

        hsynchViewport(fAncestor, fLeft, fRight);
        hsynchViewport(fLeft, fAncestor, fRight);
        hsynchViewport(fRight, fAncestor, fLeft);

        if (fMarginWidth > 0) {
            fRightCanvas = new BufferedCanvas(composite, SWT.NONE) {
                public void doPaint(GC gc) {
                    paintSides(gc, fRight, fRightCanvas, fSynchronizedScrolling);
                }
            };
            fRightCanvas.addMouseListener(new MouseAdapter() {
                public void mouseDown(MouseEvent e) {
                    setCurrentDiff2(handleMouseInSides(fRightCanvas, fRight, e.y), false);
                }
            });
        }

        fScrollCanvas = new Canvas(composite, SWT.V_SCROLL);
        Rectangle trim = fLeft.getTextWidget().computeTrim(0, 0, 0, 0);
        fTopInset = trim.y;

        fVScrollBar = fScrollCanvas.getVerticalBar();
        fVScrollBar.setIncrement(1);
        fVScrollBar.setVisible(true);
        fVScrollBar.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event e) {
                int vpos = ((ScrollBar) e.widget).getSelection();
                synchronizedScrollVertical(vpos);
            }
        });

        fBirdsEyeCanvas = new BufferedCanvas(composite, SWT.NONE) {
            public void doPaint(GC gc) {
                paintBirdsEyeView(this, gc);
            }
        };
        fBirdsEyeCanvas.addMouseListener(new MouseAdapter() {
            public void mouseDown(MouseEvent e) {
                setCurrentDiff2(handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y), true);
            }
        });
        fBirdsEyeCanvas.addMouseMoveListener(new MouseMoveListener() {

            private Cursor fLastCursor;

            public void mouseMove(MouseEvent e) {
                Cursor cursor = null;
                Diff diff = handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y);
                if (diff != null && diff.getKind() != RangeDifference.NOCHANGE)
                    cursor = fBirdsEyeCursor;
                if (fLastCursor != cursor) {
                    fBirdsEyeCanvas.setCursor(cursor);
                    fLastCursor = cursor;
                }
            }
        });
    }

    private void hsynchViewport(final TextViewer tv1, final TextViewer tv2, final TextViewer tv3) {
        final StyledText st1 = tv1.getTextWidget();
        final StyledText st2 = tv2.getTextWidget();
        final StyledText st3 = tv3.getTextWidget();
        final ScrollBar sb1 = st1.getHorizontalBar();
        sb1.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                if (fSynchronizedScrolling) {
                    int v = sb1.getSelection();
                    if (st2.isVisible())
                        st2.setHorizontalPixel(v);
                    if (st3.isVisible())
                        st3.setHorizontalPixel(v);
                    workaround65205();
                }
            }
        });
    }

    /**
     * A workaround for bug #65205. On MacOS X a Display.update() is required to
     * flush pending paint requests after programmatically scrolling.
     */
    private void workaround65205() {
        if (fIsCarbon && fComposite != null && !fComposite.isDisposed())
            fComposite.getDisplay().update();
    }

    private void setCurrentDiff2(Diff diff, boolean reveal) {
        if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) {
            // fCurrentDiff= null;
            setCurrentDiff(diff, reveal);
        }
    }

    private Diff handleMouseInSides(Canvas canvas, MergeSourceViewer tp, int my) {

        int lineHeight = tp.getTextWidget().getLineHeight();
        int visibleHeight = tp.getViewportHeight();

        if (!fHighlightRanges)
            return null;

        if (fMerger.hasChanges()) {
            int shift = tp.getVerticalScrollOffset() + (2 - LW);

            Point region = new Point(0, 0);
            char leg = getLeg(tp);
            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                if (diff.isDeleted())
                    continue;

                if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                    continue;

                tp.getLineRange(diff.getPosition(leg), region);
                int y = (region.x * lineHeight) + shift;
                int h = region.y * lineHeight;

                if (y + h < 0)
                    continue;
                if (y >= visibleHeight)
                    break;

                if (my >= y && my < y + h)
                    return diff;
            }
        }
        return null;
    }

    private Diff getDiffUnderMouse(Canvas canvas, int mx, int my, Rectangle r) {

        if (!fSynchronizedScrolling)
            return null;

        int lineHeight = fLeft.getTextWidget().getLineHeight();
        int visibleHeight = fRight.getViewportHeight();

        Point size = canvas.getSize();
        int w = size.x;

        if (!fHighlightRanges)
            return null;

        if (fMerger.hasChanges()) {
            int lshift = fLeft.getVerticalScrollOffset();
            int rshift = fRight.getVerticalScrollOffset();

            Point region = new Point(0, 0);

            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                if (diff.isDeleted())
                    continue;

                if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                    continue;

                fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
                int ly = (region.x * lineHeight) + lshift;
                int lh = region.y * lineHeight;

                fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
                int ry = (region.x * lineHeight) + rshift;
                int rh = region.y * lineHeight;

                if (Math.max(ly + lh, ry + rh) < 0)
                    continue;
                if (Math.min(ly, ry) >= visibleHeight)
                    break;

                int cx = (w - RESOLVE_SIZE) / 2;
                int cy = ((ly + lh / 2) + (ry + rh / 2) - RESOLVE_SIZE) / 2;
                if (my >= cy && my < cy + RESOLVE_SIZE && mx >= cx && mx < cx + RESOLVE_SIZE) {
                    if (r != null) {
                        int SIZE = fIsCarbon ? 30 : 20;
                        r.x = cx + (RESOLVE_SIZE - SIZE) / 2;
                        r.y = cy + (RESOLVE_SIZE - SIZE) / 2;
                        r.width = SIZE;
                        r.height = SIZE;
                    }
                    return diff;
                }
            }
        }
        return null;
    }

    private Diff handlemouseInBirdsEyeView(Canvas canvas, int my) {
        return fMerger.findDiff(getViewportHeight(), fSynchronizedScrolling, canvas.getSize(), my);
    }

    private void paintBirdsEyeView(Canvas canvas, GC gc) {

        Color c;
        Rectangle r = new Rectangle(0, 0, 0, 0);
        int yy, hh;

        Point size = canvas.getSize();

        int virtualHeight = fSynchronizedScrolling ? fMerger.getVirtualHeight() : fMerger.getRightHeight();
        if (virtualHeight < getViewportHeight())
            return;

        Display display = canvas.getDisplay();
        int y = 0;
        for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
            Diff diff = (Diff) iterator.next();
            int h = fSynchronizedScrolling ? diff.getMaxDiffHeight() : diff.getRightHeight();

            if (fMerger.useChange(diff)) {

                yy = (y * size.y) / virtualHeight;
                hh = (h * size.y) / virtualHeight;
                if (hh < 3)
                    hh = 3;

                c = getColor(display, getFillColor(diff));
                if (c != null) {
                    gc.setBackground(c);
                    gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x - (2 * BIRDS_EYE_VIEW_INSET), hh);
                }
                c = getColor(display, getStrokeColor(diff));
                if (c != null) {
                    gc.setForeground(c);
                    r.x = BIRDS_EYE_VIEW_INSET;
                    r.y = yy;
                    r.width = size.x - (2 * BIRDS_EYE_VIEW_INSET) - 1;
                    r.height = hh;
                    if (isCurrentDiff(diff)) {
                        gc.setLineWidth(2);
                        r.x++;
                        r.y++;
                        r.width--;
                        r.height--;
                    } else {
                        gc.setLineWidth(0 /* 1 */);
                    }
                    gc.drawRectangle(r);
                }
            }

            y += h;
        }
    }

    private void refreshBirdsEyeView() {
        if (fBirdsEyeCanvas != null)
            fBirdsEyeCanvas.redraw();
    }

    /**
     * Override to give focus to the pane that previously had focus or to a
     * suitable default pane.
     * 
     * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleSetFocus()
     * @since 3.3
     */
    protected boolean handleSetFocus() {
        if (fFocusPart == null) {
            if (fLeft != null && fLeft.getEnabled()) {
                fFocusPart = fLeft;
            } else if (fRight != null && fRight.getEnabled()) {
                fFocusPart = fRight;
            } else if (fAncestor != null && fAncestor.getEnabled()) {
                fFocusPart = fAncestor;
            }
        }
        if (fFocusPart != null) {
            StyledText st = fFocusPart.getTextWidget();
            if (st != null)
                return st.setFocus();
        }
        return false; // could not set focus
    }

    class HoverResizer extends Resizer {
        Canvas fCanvas;

        public HoverResizer(Canvas c, int dir) {
            super(c, dir);
            fCanvas = c;
        }

        public void mouseMove(MouseEvent e) {
            if (!fIsDown && fUseSingleLine && showResolveUI() && handleMouseMoveOverCenter(fCanvas, e.x, e.y))
                return;
            super.mouseMove(e);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.compare.contentmergeviewer.ContentMergeViewer#createCenterControl
     * (org.eclipse.swt.widgets.Composite)
     */
    protected final Control createCenterControl(Composite parent) {
        if (fSynchronizedScrolling) {
            final Canvas canvas = new BufferedCanvas(parent, SWT.NONE) {
                public void doPaint(GC gc) {
                    paintCenter(this, gc);
                }
            };
            if (fUseResolveUI) {

                new HoverResizer(canvas, HORIZONTAL);

                fCenterButton = new Button(canvas, fIsCarbon ? SWT.FLAT : SWT.PUSH);
                if (fNormalCursor == null)
                    fNormalCursor = new Cursor(canvas.getDisplay(), SWT.CURSOR_ARROW);
                fCenterButton.setCursor(fNormalCursor);
                fCenterButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR);
                fCenterButton.pack();
                fCenterButton.setVisible(false);
                fCenterButton.addSelectionListener(new SelectionAdapter() {
                    public void widgetSelected(SelectionEvent e) {
                        fCenterButton.setVisible(false);
                        if (fButtonDiff != null) {
                            setCurrentDiff(fButtonDiff, false);
                            copy(fCurrentDiff, fCenterButton.getText().equals(COPY_LEFT_TO_RIGHT_INDICATOR),
                                    fCurrentDiff.getKind() != RangeDifference.CONFLICT);
                        }
                    }
                });
            } else {
                new Resizer(canvas, HORIZONTAL);
            }

            return canvas;
        }
        return super.createCenterControl(parent);
    }

    private boolean handleMouseMoveOverCenter(Canvas canvas, int x, int y) {
        Rectangle r = new Rectangle(0, 0, 0, 0);
        Diff diff = getDiffUnderMouse(canvas, x, y, r);
        if (diff != null && !diff.isUnresolvedIncomingOrConflicting())
            diff = null;
        if (diff != fButtonDiff) {
            if (diff != null) {
                if (fLeft.isEditable()) {
                    fButtonDiff = diff;
                    fCenterButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR);
                    String tt = fCopyDiffRightToLeftItem.getAction().getToolTipText();
                    fCenterButton.setToolTipText(tt);
                    fCenterButton.setBounds(r);
                    fCenterButton.setVisible(true);
                } else if (fRight.isEditable()) {
                    fButtonDiff = diff;
                    fCenterButton.setText(COPY_LEFT_TO_RIGHT_INDICATOR);
                    String tt = fCopyDiffLeftToRightItem.getAction().getToolTipText();
                    fCenterButton.setToolTipText(tt);
                    fCenterButton.setBounds(r);
                    fCenterButton.setVisible(true);
                } else
                    fButtonDiff = null;
            } else {
                fCenterButton.setVisible(false);
                fButtonDiff = null;
            }
        }
        return fButtonDiff != null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getCenterWidth
     * ()
     */
    protected final int getCenterWidth() {
        if (fSynchronizedScrolling)
            return CENTER_WIDTH;
        return super.getCenterWidth();
    }

    private int getDirection() {
        switch (fTextDirection) {
        case SWT.LEFT_TO_RIGHT:
        case SWT.RIGHT_TO_LEFT:
            if (fInheritedDirection == fTextDirection)
                return SWT.NONE;
            return fTextDirection;
        }
        return fInheritedDirection;
    }

    /*
     * Creates and initializes a text part.
     */
    private MergeSourceViewer createPart(Composite parent) {

        final MergeSourceViewer part = new MergeSourceViewer(parent, getDirection(), getResourceBundle(),
                getCompareConfiguration().getContainer());
        final StyledText te = part.getTextWidget();

        if (!fConfirmSave)
            part.hideSaveAction();

        te.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                paint(e, part);
            }
        });
        te.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                handleSelectionChanged(part);
            }
        });
        te.addMouseListener(new MouseAdapter() {
            public void mouseDown(MouseEvent e) {
                // syncViewport(part);
                handleSelectionChanged(part);
            }
        });

        te.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent fe) {
                fFocusPart = part;
                connectGlobalActions(fFocusPart);
            }

            public void focusLost(FocusEvent fe) {
                connectGlobalActions(null);
            }
        });

        part.addViewportListener(new IViewportListener() {
            public void viewportChanged(int verticalPosition) {
                syncViewport(part);
            }
        });

        Font font = JFaceResources.getFont(fSymbolicFontName);
        if (font != null)
            te.setFont(font);

        if (fBackground != null) // not default
            te.setBackground(getColor(parent.getDisplay(), fBackground));

        // Add the find action to the popup menu of the viewer
        contributeFindAction(part);

        configureTextViewer(part);

        return part;
    }

    private void contributeFindAction(MergeSourceViewer viewer) {
        IAction action;
        IWorkbenchPart wp = getCompareConfiguration().getContainer().getWorkbenchPart();
        if (wp != null)
            action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", wp); //$NON-NLS-1$
        else
            action = new FindReplaceAction(getResourceBundle(), "Editor.FindReplace.", //$NON-NLS-1$
                    viewer.getControl().getShell(), getFindReplaceTarget());
        action.setActionDefinitionId(IWorkbenchActionDefinitionIds.FIND_REPLACE);
        viewer.addAction(MergeSourceViewer.FIND_ID, action);
    }

    private void connectGlobalActions(final MergeSourceViewer part) {
        if (fHandlerService != null) {
            if (part != null)
                part.updateActions();
            fHandlerService.updatePaneActionHandlers(new Runnable() {
                public void run() {
                    for (int i = 0; i < GLOBAL_ACTIONS.length; i++) {
                        IAction action = null;
                        if (part != null) {
                            action = part.getAction(TEXT_ACTIONS[i]);
                            if (action == null && TEXT_ACTIONS[i].equals(MergeSourceViewer.SAVE_ID)) {
                                if (part == fLeft)
                                    action = fLeftSaveAction;
                                else
                                    action = fRightSaveAction;
                            }
                        }
                        fHandlerService.setGlobalActionHandler(GLOBAL_ACTIONS[i], action);
                    }
                }
            });
        }
    }

    private IDocument getElementDocument(char type, Object element) {
        if (element instanceof IDocument) {
            return (IDocument) element;
        }
        ITypedElement te = Utilities.getLeg(type, element);
        // First check the contributors for the document
        IDocument document = null;
        switch (type) {
        case ANCESTOR_CONTRIBUTOR:
            document = getDocument(te, fAncestorContributor);
            break;
        case LEFT_CONTRIBUTOR:
            document = getDocument(te, fLeftContributor);
            break;
        case RIGHT_CONTRIBUTOR:
            document = getDocument(te, fRightContributor);
            break;
        }
        if (document != null)
            return document;
        // The document is not associated with the input of the viewer so try to
        // find the document
        return Utilities.getDocument(type, element, isUsingDefaultContentProvider(), canHaveSharedDocument());
    }

    private boolean isUsingDefaultContentProvider() {
        return getContentProvider() instanceof MergeViewerContentProvider;
    }

    private boolean canHaveSharedDocument() {
        return getDocumentPartitioning() != null || getDocumentPartitioner() == null;
    }

    private IDocument getDocument(ITypedElement te, ContributorInfo info) {
        if (info != null && info.getElement() == te)
            return info.getDocument();
        return null;
    }

    IDocument getDocument(char type, Object input) {
        IDocument doc = getElementDocument(type, input);
        if (doc != null)
            return doc;

        if (input instanceof IDiffElement) {
            IDiffContainer parent = ((IDiffElement) input).getParent();
            return getElementDocument(type, parent);
        }
        return null;
    }

    /*
     * Returns true if the given inputs map to the same documents
     */
    boolean sameDoc(char type, Object newInput, Object oldInput) {
        IDocument newDoc = getDocument(type, newInput);
        IDocument oldDoc = getDocument(type, oldInput);
        return newDoc == oldDoc;
    }

    /**
     * Overridden to prevent save confirmation if new input is sub document of
     * current input.
     * 
     * @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) {
        // TODO: Would be good if this could be restated in terms of Saveables
        // and moved up
        if (oldInput != null && newInput != null) {
            // check whether underlying documents have changed.
            if (sameDoc(ANCESTOR_CONTRIBUTOR, newInput, oldInput) && sameDoc(LEFT_CONTRIBUTOR, newInput, oldInput)
                    && sameDoc(RIGHT_CONTRIBUTOR, newInput, oldInput)) {
                if (DEBUG)
                    System.out.println("----- Same docs !!!!"); //$NON-NLS-1$
                return false;
            }
        }

        if (DEBUG)
            System.out.println("***** New docs !!!!"); //$NON-NLS-1$

        removeFromDocumentManager(ANCESTOR_CONTRIBUTOR, oldInput);
        removeFromDocumentManager(LEFT_CONTRIBUTOR, oldInput);
        removeFromDocumentManager(RIGHT_CONTRIBUTOR, oldInput);

        if (DEBUG)
            DocumentManager.dump();

        return super.doSave(newInput, oldInput);
    }

    private void removeFromDocumentManager(char leg, Object oldInput) {
        IDocument document = getDocument(leg, oldInput);
        if (document != null)
            DocumentManager.remove(document);
    }

    private ITypedElement getParent(char type) {
        Object input = getInput();
        if (input instanceof IDiffElement) {
            IDiffContainer parent = ((IDiffElement) input).getParent();
            return Utilities.getLeg(type, parent);
        }
        return null;
    }

    /*
     * Initializes the text viewers of the three content areas with the given
     * input objects. Subclasses may extend.
     */
    protected void updateContent(Object ancestor, Object left, Object right) {

        boolean emptyInput = (ancestor == null && left == null && right == null);

        Object input = getInput();

        Position leftRange = null;
        Position rightRange = null;

        // if one side is empty use container
        if (FIX_47640 && !emptyInput && (left == null || right == null)) {
            if (input instanceof IDiffElement) {
                IDiffContainer parent = ((IDiffElement) input).getParent();
                if (parent instanceof ICompareInput) {
                    ICompareInput ci = (ICompareInput) parent;

                    if (ci.getAncestor() instanceof IDocumentRange || ci.getLeft() instanceof IDocumentRange
                            || ci.getRight() instanceof IDocumentRange) {

                        if (left instanceof IDocumentRange)
                            leftRange = ((IDocumentRange) left).getRange();
                        if (right instanceof IDocumentRange)
                            rightRange = ((IDocumentRange) right).getRange();

                        ancestor = ci.getAncestor();
                        left = ci.getLeft();
                        right = ci.getRight();
                    }
                }
            }
        }

        int n = 0;
        if (left != null)
            n++;
        if (right != null)
            n++;
        fHighlightRanges = n > 1;

        resetDiffs();
        fHasErrors = false; // start with no errors

        CompareConfiguration cc = getCompareConfiguration();
        IMergeViewerContentProvider cp = getMergeContentProvider();

        if (cp instanceof MergeViewerContentProvider) {
            MergeViewerContentProvider mcp = (MergeViewerContentProvider) cp;
            mcp.setAncestorError(null);
            mcp.setLeftError(null);
            mcp.setRightError(null);
        }

        // Record current contributors so we disconnect after creating the new
        // ones.
        // This is done in case the old and new use the same document.
        ContributorInfo oldLeftContributor = fLeftContributor;
        ContributorInfo oldRightContributor = fRightContributor;
        ContributorInfo oldAncestorContributor = fAncestorContributor;

        // Create the new contributor
        fLeftContributor = createLegInfoFor(left, LEFT_CONTRIBUTOR);
        fRightContributor = createLegInfoFor(right, RIGHT_CONTRIBUTOR);
        fAncestorContributor = createLegInfoFor(ancestor, ANCESTOR_CONTRIBUTOR);

        fLeftContributor.transferContributorStateFrom(oldLeftContributor);
        fRightContributor.transferContributorStateFrom(oldRightContributor);
        fAncestorContributor.transferContributorStateFrom(oldAncestorContributor);

        // Now disconnect the old ones
        disconnect(oldLeftContributor);
        disconnect(oldRightContributor);
        disconnect(oldAncestorContributor);

        // Get encodings from streams. If an encoding is null, abide by the
        // other one
        // Defaults to workbench encoding only if both encodings are null
        fLeftContributor.setEncodingIfAbsent(fRightContributor);
        fRightContributor.setEncodingIfAbsent(fLeftContributor);
        fAncestorContributor.setEncodingIfAbsent(fLeftContributor);

        // set new documents
        fLeftContributor.setDocument(fLeft, cc.isLeftEditable() && cp.isLeftEditable(input));
        fLeftLineCount = fLeft.getLineCount();

        fRightContributor.setDocument(fRight, cc.isRightEditable() && cp.isRightEditable(input));
        fRightLineCount = fRight.getLineCount();

        fAncestorContributor.setDocument(fAncestor, false);

        // if the input is part of a patch hunk, toggle synchronized scrolling
        /*
         * if (isPatchHunk()){ setSyncScrolling(false); } else {
         * setSyncScrolling
         * (fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING
         * )); }
         */
        setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING));

        update(false);

        if (!fHasErrors && !emptyInput && !fComposite.isDisposed()) {
            if (isRefreshing()) {
                fLeftContributor.updateSelection(fLeft, !fSynchronizedScrolling);
                fRightContributor.updateSelection(fRight, !fSynchronizedScrolling);
                fAncestorContributor.updateSelection(fAncestor, !fSynchronizedScrolling);
                if (fSynchronizedScrolling && fSynchronziedScrollPosition != -1) {
                    synchronizedScrollVertical(fSynchronziedScrollPosition);
                }
            } else {
                if (isPatchHunk()) {
                    if (right != null && Utilities.getAdapter(right, IHunk.class) != null)
                        fLeft.setTopIndex(getHunkStart());
                    else
                        fRight.setTopIndex(getHunkStart());
                } else {
                    Diff selectDiff = null;
                    if (FIX_47640) {
                        if (leftRange != null)
                            selectDiff = fMerger.findDiff(LEFT_CONTRIBUTOR, leftRange);
                        else if (rightRange != null)
                            selectDiff = fMerger.findDiff(RIGHT_CONTRIBUTOR, rightRange);
                    }
                    if (selectDiff != null)
                        setCurrentDiff(selectDiff, true);
                    else
                        selectFirstDiff(true);
                }
            }
        }

    }

    private boolean isRefreshing() {
        return isRefreshing;
    }

    private ContributorInfo createLegInfoFor(Object element, char leg) {
        return new ContributorInfo(this, element, leg);
    }

    private void updateDiffBackground(Diff diff) {

        if (!fHighlightRanges)
            return;

        if (diff == null || diff.isToken())
            return;

        if (fShowCurrentOnly && !isCurrentDiff(diff))
            return;

        Color c = getColor(null, getFillColor(diff));
        if (c == null)
            return;

        if (isThreeWay())
            fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
        fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
        fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
    }

    private void updateAllDiffBackgrounds(Display display) {
        if (fMerger.hasChanges()) {
            boolean threeWay = isThreeWay();
            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                Color c = getColor(display, getFillColor(diff));
                if (threeWay)
                    fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
                fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
                fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
            }
        }
    }

    /*
     * Called whenever one of the documents changes. Sets the dirty state of
     * this viewer and updates the lines. Implements IDocumentListener.
     */
    private void documentChanged(DocumentEvent e, boolean dirty) {

        IDocument doc = e.getDocument();

        if (doc == fLeft.getDocument()) {
            setLeftDirty(dirty);
        } else if (doc == fRight.getDocument()) {
            setRightDirty(dirty);
        }

        updateLines(doc);
    }

    /*
     * This method is called if a range of text on one side is copied into an
     * empty sub-document on the other side. The method returns the position
     * where the sub-document is placed into the base document. This default
     * implementation determines the position by using the text range
     * differencer. However this position is not always optimal for specific
     * types of text. So subclasses (which are aware of the type of text they
     * are dealing with) may override this method to find a better position
     * where to insert a newly added piece of text.
     * 
     * @param type the side for which the insertion position should be
     * determined: 'A' for ancestor, 'L' for left hand side, 'R' for right hand
     * side.
     * 
     * @param input the current input object of this viewer
     * 
     * @since 2.0
     */
    protected int findInsertionPosition(char type, ICompareInput input) {

        ITypedElement other = null;
        char otherType = 0;

        switch (type) {
        case ANCESTOR_CONTRIBUTOR:
            other = input.getLeft();
            otherType = LEFT_CONTRIBUTOR;
            if (other == null) {
                other = input.getRight();
                otherType = RIGHT_CONTRIBUTOR;
            }
            break;
        case LEFT_CONTRIBUTOR:
            other = input.getRight();
            otherType = RIGHT_CONTRIBUTOR;
            if (other == null) {
                other = input.getAncestor();
                otherType = ANCESTOR_CONTRIBUTOR;
            }
            break;
        case RIGHT_CONTRIBUTOR:
            other = input.getLeft();
            otherType = LEFT_CONTRIBUTOR;
            if (other == null) {
                other = input.getAncestor();
                otherType = ANCESTOR_CONTRIBUTOR;
            }
            break;
        }

        if (other instanceof IDocumentRange) {
            IDocumentRange dr = (IDocumentRange) other;
            Position p = dr.getRange();
            Diff diff = findDiff(otherType, p.offset);
            return fMerger.findInsertionPoint(diff, type);
        }
        return 0;
    }

    private void setError(char type, String message) {
        IMergeViewerContentProvider cp = getMergeContentProvider();
        if (cp instanceof MergeViewerContentProvider) {
            MergeViewerContentProvider mcp = (MergeViewerContentProvider) cp;
            switch (type) {
            case ANCESTOR_CONTRIBUTOR:
                mcp.setAncestorError(message);
                break;
            case LEFT_CONTRIBUTOR:
                mcp.setLeftError(message);
                break;
            case RIGHT_CONTRIBUTOR:
                mcp.setRightError(message);
                break;
            }
        }
        fHasErrors = true;
    }

    private void updateDirtyState(IEditorInput key, IDocumentProvider documentProvider, char type) {
        boolean dirty = documentProvider.canSaveDocument(key);
        if (type == LEFT_CONTRIBUTOR)
            setLeftDirty(dirty);
        else if (type == RIGHT_CONTRIBUTOR)
            setRightDirty(dirty);
    }

    private Position getNewRange(char type, Object input) {
        switch (type) {
        case ANCESTOR_CONTRIBUTOR:
            return (Position) fNewAncestorRanges.get(input);
        case LEFT_CONTRIBUTOR:
            return (Position) fNewLeftRanges.get(input);
        case RIGHT_CONTRIBUTOR:
            return (Position) fNewRightRanges.get(input);
        }
        return null;
    }

    private void addNewRange(char type, Object input, Position range) {
        switch (type) {
        case ANCESTOR_CONTRIBUTOR:
            fNewAncestorRanges.put(input, range);
            break;
        case LEFT_CONTRIBUTOR:
            fNewLeftRanges.put(input, range);
            break;
        case RIGHT_CONTRIBUTOR:
            fNewRightRanges.put(input, range);
            break;
        }
    }

    /**
     * Returns the contents of the underlying document as an array of bytes
     * using the current workbench encoding.
     * 
     * @param left
     *            if <code>true</code> the contents of the left side is
     *            returned; otherwise the right side
     * @return the contents of the left or right document or null
     */
    protected byte[] getContents(boolean left) {
        MergeSourceViewer v = left ? fLeft : fRight;
        if (v != null) {
            IDocument d = v.getDocument();
            if (d != null) {
                String contents = d.get();
                if (contents != null) {
                    byte[] bytes;
                    try {
                        bytes = contents
                                .getBytes(left ? fLeftContributor.getEncoding() : fRightContributor.getEncoding());
                    } catch (UnsupportedEncodingException ex) {
                        // use default encoding
                        bytes = contents.getBytes();
                    }
                    return bytes;
                }
            }
        }
        return null;
    }

    private IRegion normalizeDocumentRegion(IDocument doc, IRegion region) {

        if (region == null || doc == null)
            return region;

        int maxLength = doc.getLength();

        int start = region.getOffset();
        if (start < 0)
            start = 0;
        else if (start > maxLength)
            start = maxLength;

        int length = region.getLength();
        if (length < 0)
            length = 0;
        else if (start + length > maxLength)
            length = maxLength - start;

        return new Region(start, length);
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.eclipse.compare.contentmergeviewer.ContentMergeViewer#
     * handleResizeAncestor(int, int, int, int)
     */
    protected final void handleResizeAncestor(int x, int y, int width, int height) {
        if (width > 0) {
            Rectangle trim = fLeft.getTextWidget().computeTrim(0, 0, 0, 0);
            int scrollbarHeight = trim.height;
            if (Utilities.okToUse(fAncestorCanvas))
                fAncestorCanvas.setVisible(true);
            if (fAncestor.isControlOkToUse())
                fAncestor.getTextWidget().setVisible(true);

            if (fAncestorCanvas != null) {
                fAncestorCanvas.setBounds(x, y, fMarginWidth, height - scrollbarHeight);
                x += fMarginWidth;
                width -= fMarginWidth;
            }
            fAncestor.setBounds(x, y, width, height);
        } else {
            if (Utilities.okToUse(fAncestorCanvas))
                fAncestorCanvas.setVisible(false);
            if (fAncestor.isControlOkToUse()) {
                StyledText t = fAncestor.getTextWidget();
                t.setVisible(false);
                fAncestor.setBounds(0, 0, 0, 0);
                if (fFocusPart == fAncestor) {
                    fFocusPart = fLeft;
                    fFocusPart.getTextWidget().setFocus();
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.eclipse.compare.contentmergeviewer.ContentMergeViewer#
     * handleResizeLeftRight(int, int, int, int, int, int)
     */
    protected final void handleResizeLeftRight(int x, int y, int width1, int centerWidth, int width2, int height) {

        if (fBirdsEyeCanvas != null)
            width2 -= BIRDS_EYE_VIEW_WIDTH;

        Rectangle trim = fLeft.getTextWidget().computeTrim(0, 0, 0, 0);
        int scrollbarHeight = trim.height + trim.x;

        Composite composite = (Composite) getControl();

        int leftTextWidth = width1;
        if (fLeftCanvas != null) {
            fLeftCanvas.setBounds(x, y, fMarginWidth, height - scrollbarHeight);
            x += fMarginWidth;
            leftTextWidth -= fMarginWidth;
        }

        fLeft.setBounds(x, y, leftTextWidth, height);
        x += leftTextWidth;

        if (fCenter == null || fCenter.isDisposed())
            fCenter = createCenterControl(composite);
        fCenter.setBounds(x, y, centerWidth, height - scrollbarHeight);
        x += centerWidth;

        if (!fSynchronizedScrolling) { // canvas is to the left of text
            if (fRightCanvas != null) {
                fRightCanvas.setBounds(x, y, fMarginWidth, height - scrollbarHeight);
                fRightCanvas.redraw();
                x += fMarginWidth;
            }
            // we draw the canvas to the left of the text widget
        }

        int scrollbarWidth = 0;
        if (fSynchronizedScrolling && fScrollCanvas != null) {
            trim = fLeft.getTextWidget().computeTrim(0, 0, 0, 0);
            // one pixel was cut off
            scrollbarWidth = trim.width + 2 * trim.x + 1;
        }
        int rightTextWidth = width2 - scrollbarWidth;
        if (fRightCanvas != null)
            rightTextWidth -= fMarginWidth;
        fRight.setBounds(x, y, rightTextWidth, height);
        x += rightTextWidth;

        if (fSynchronizedScrolling) {
            if (fRightCanvas != null) { // canvas is to the right of the text
                fRightCanvas.setBounds(x, y, fMarginWidth, height - scrollbarHeight);
                x += fMarginWidth;
            }
            if (fScrollCanvas != null)
                fScrollCanvas.setBounds(x, y, scrollbarWidth, height - scrollbarHeight);
        }

        if (fBirdsEyeCanvas != null) {
            int verticalScrollbarButtonHeight = scrollbarWidth;
            int horizontalScrollbarButtonHeight = scrollbarHeight;
            if (fIsCarbon) {
                verticalScrollbarButtonHeight += 2;
                horizontalScrollbarButtonHeight = 18;
            }
            if (fSummaryHeader != null)
                fSummaryHeader.setBounds(x + scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH,
                        verticalScrollbarButtonHeight);
            y += verticalScrollbarButtonHeight;
            fBirdsEyeCanvas.setBounds(x + scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH,
                    height - (2 * verticalScrollbarButtonHeight + horizontalScrollbarButtonHeight));
        }

        // doesn't work since TextEditors don't have their correct size yet.
        updateVScrollBar();
        refreshBirdsEyeView();
    }

    /*
     * Track selection changes to update the current Diff.
     */
    private void handleSelectionChanged(MergeSourceViewer tw) {
        Point p = tw.getSelectedRange();
        Diff d = findDiff(tw, p.x, p.x + p.y);
        updateStatus(d);
        setCurrentDiff(d, false); // don't select or reveal
    }

    private static IRegion toRegion(Position position) {
        if (position != null)
            return new Region(position.getOffset(), position.getLength());
        return null;
    }

    // ---- the differencing

    /**
     * Perform a two level 2- or 3-way diff. The first level is based on line
     * comparison, the second level on token comparison.
     */
    private void doDiff() {
        IDocument lDoc = fLeft.getDocument();
        IDocument rDoc = fRight.getDocument();
        if (lDoc == null || rDoc == null)
            return;
        fAncestor.resetLineBackground();
        fLeft.resetLineBackground();
        fRight.resetLineBackground();

        fCurrentDiff = null;
        try {
            fMerger.doDiff();
        } catch (CoreException e) {
            CompareUIPlugin.log(e.getStatus());
            String title = Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
            String format = Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
            String msg = MessageFormat.format(format, new Object[] { Integer
                    .toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime() / 1000) });
            MessageDialog.openError(fComposite.getShell(), title, msg);
        }

        if (fMerger.hasChanges()) {
            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                updateDiffBackground(diff);
            }
        }
        invalidateTextPresentation();
    }

    private Diff findDiff(char type, int pos) {
        try {
            return fMerger.findDiff(type, pos);
        } catch (CoreException e) {
            CompareUIPlugin.log(e.getStatus());
            String title = Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
            String format = Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
            String msg = MessageFormat.format(format, new Object[] { Integer
                    .toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime() / 1000) });
            MessageDialog.openError(fComposite.getShell(), title, msg);
            return null;
        }
    }

    private void resetPositions(IDocument doc) {
        if (doc == null)
            return;
        try {
            doc.removePositionCategory(DIFF_RANGE_CATEGORY);
        } catch (BadPositionCategoryException e) {
            // Ignore
        }
        doc.addPositionCategory(DIFF_RANGE_CATEGORY);
    }

    // ---- update UI stuff

    private void updateControls() {

        boolean leftToRight = false;
        boolean rightToLeft = false;

        updateStatus(fCurrentDiff);
        updateResolveStatus();

        if (fCurrentDiff != null) {
            IMergeViewerContentProvider cp = getMergeContentProvider();
            if (cp != null) {
                if (!isPatchHunk()) {
                    rightToLeft = cp.isLeftEditable(getInput());
                    leftToRight = cp.isRightEditable(getInput());
                }
            }
        }

        if (fDirectionLabel != null) {
            if (fHighlightRanges && fCurrentDiff != null && isThreeWay() && !isIgnoreAncestor()) {
                fDirectionLabel.setImage(fCurrentDiff.getImage());
            } else {
                fDirectionLabel.setImage(null);
            }
        }

        if (fCopyDiffLeftToRightItem != null)
            ((Action) fCopyDiffLeftToRightItem.getAction()).setEnabled(leftToRight);
        if (fCopyDiffRightToLeftItem != null)
            ((Action) fCopyDiffRightToLeftItem.getAction()).setEnabled(rightToLeft);

        boolean enableNavigation = isNavigationPossible();

        if (fNextDiff != null) {
            IAction a = fNextDiff.getAction();
            a.setEnabled(enableNavigation || hasNextElement(true));
        }
        if (fPreviousDiff != null) {
            IAction a = fPreviousDiff.getAction();
            a.setEnabled(enableNavigation || hasNextElement(false));
        }
        if (fNextChange != null) {
            IAction a = fNextChange.getAction();
            a.setEnabled(enableNavigation);
        }
        if (fPreviousChange != null) {
            IAction a = fPreviousChange.getAction();
            a.setEnabled(enableNavigation);
        }
    }

    private void updateResolveStatus() {

        RGB rgb = null;

        if (showResolveUI()) {
            // we only show red or green if there is at least one incoming or
            // conflicting change
            int incomingOrConflicting = 0;
            int unresolvedIncoming = 0;
            int unresolvedConflicting = 0;

            if (fMerger.hasChanges()) {
                for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                    Diff d = (Diff) iterator.next();
                    if (d.isIncomingOrConflicting() /*
                                                    * &&
                                                    * useChange(d.fDirection)
                                                    * && !d.fIsWhitespace
                                                    */) {
                        incomingOrConflicting++;
                        if (!d.isResolved()) {
                            if (d.getKind() == RangeDifference.CONFLICT) {
                                unresolvedConflicting++;
                                break; // we can stop here because a conflict
                                       // has the maximum priority
                            }
                            unresolvedIncoming++;
                        }
                    }
                }
            }

            if (incomingOrConflicting > 0) {
                if (unresolvedConflicting > 0)
                    rgb = SELECTED_CONFLICT;
                else if (unresolvedIncoming > 0)
                    rgb = SELECTED_INCOMING;
                else
                    rgb = RESOLVED;
            }
        }

        if (fHeaderPainter.setColor(rgb))
            fSummaryHeader.redraw();
    }

    private void updateStatus(Diff diff) {

        String diffDescription;

        if (diff == null) {
            diffDescription = CompareMessages.TextMergeViewer_diffDescription_noDiff_format;
        } else {

            if (diff.isToken()) // we don't show special info for token diffs
                diff = diff.getParent();

            String format = CompareMessages.TextMergeViewer_diffDescription_diff_format;
            diffDescription = MessageFormat.format(format, new String[] { getDiffType(diff), // 0: diff type
                    getDiffNumber(diff), // 1: diff number
                    getDiffRange(fLeft, diff.getPosition(LEFT_CONTRIBUTOR)), // 2:
                    // left
                    // start
                    // line
                    getDiffRange(fRight, diff.getPosition(RIGHT_CONTRIBUTOR)) // 3:
                    // left
                    // end
                    // line
            });
        }

        String format = CompareMessages.TextMergeViewer_statusLine_format;
        String s = MessageFormat.format(format, new String[] { getCursorPosition(fLeft), // 0: left column
                getCursorPosition(fRight), // 1: right column
                diffDescription // 2: diff description
        });

        getCompareConfiguration().getContainer().setStatusMessage(s);
    }

    private void clearStatus() {
        getCompareConfiguration().getContainer().setStatusMessage(null);
    }

    private String getDiffType(Diff diff) {
        String s = ""; //$NON-NLS-1$
        switch (diff.getKind()) {
        case RangeDifference.LEFT:
            s = CompareMessages.TextMergeViewer_direction_outgoing;
            break;
        case RangeDifference.RIGHT:
            s = CompareMessages.TextMergeViewer_direction_incoming;
            break;
        case RangeDifference.CONFLICT:
            s = CompareMessages.TextMergeViewer_direction_conflicting;
            break;
        }
        String format = CompareMessages.TextMergeViewer_diffType_format;
        return MessageFormat.format(format, new String[] { s, diff.changeType() });
    }

    private String getDiffNumber(Diff diff) {
        // find the diff's number
        int diffNumber = 0;
        if (fMerger.hasChanges()) {
            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff d = (Diff) iterator.next();
                diffNumber++;
                if (d == diff)
                    break;
            }
        }
        return Integer.toString(diffNumber);
    }

    private String getDiffRange(MergeSourceViewer v, Position pos) {
        Point p = v.getLineRange(pos, new Point(0, 0));
        int startLine = p.x + 1;
        int endLine = p.x + p.y;

        String format;
        if (endLine < startLine)
            format = CompareMessages.TextMergeViewer_beforeLine_format;
        else
            format = CompareMessages.TextMergeViewer_range_format;
        return MessageFormat.format(format,
                new String[] { Integer.toString(startLine), Integer.toString(endLine) });
    }

    /*
     * Returns a description of the cursor position.
     * 
     * @return a description of the cursor position
     */
    private String getCursorPosition(MergeSourceViewer v) {
        if (v != null) {
            StyledText styledText = v.getTextWidget();

            IDocument document = v.getDocument();
            if (document != null) {
                int offset = v.getVisibleRegion().getOffset();
                int caret = offset + styledText.getCaretOffset();

                try {

                    int line = document.getLineOfOffset(caret);

                    int lineOffset = document.getLineOffset(line);
                    int occurrences = 0;
                    for (int i = lineOffset; i < caret; i++)
                        if ('\t' == document.getChar(i))
                            ++occurrences;

                    int tabWidth = styledText.getTabs();
                    int column = caret - lineOffset + (tabWidth - 1) * occurrences;

                    String format = CompareMessages.TextMergeViewer_cursorPosition_format;
                    return MessageFormat.format(format,
                            new String[] { Integer.toString(line + 1), Integer.toString(column + 1) });

                } catch (BadLocationException x) {
                    // silently ignored
                }
            }
        }
        return ""; //$NON-NLS-1$
    }

    protected void updateHeader() {

        super.updateHeader();

        updateControls();
    }

    /*
     * Creates the two items for copying a difference range from one side to the
     * other and adds them to the given toolbar manager.
     */
    protected void createToolItems(ToolBarManager tbm) {

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

        final String ignoreAncestorActionKey = "action.IgnoreAncestor."; //$NON-NLS-1$
        Action ignoreAncestorAction = new Action() {
            public void run() {
                // First make sure the ancestor is hidden
                if (!isIgnoreAncestor())
                    getCompareConfiguration().setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, Boolean.FALSE);
                // Then set the property to ignore the ancestor
                getCompareConfiguration().setProperty(ICompareUIConstants.PROP_IGNORE_ANCESTOR,
                        Boolean.valueOf(!isIgnoreAncestor()));
                Utilities.initToggleAction(this, getResourceBundle(), ignoreAncestorActionKey, isIgnoreAncestor());
            }
        };
        ignoreAncestorAction.setChecked(isIgnoreAncestor());
        Utilities.initAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey);
        Utilities.initToggleAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey,
                isIgnoreAncestor());

        fIgnoreAncestorItem = new ActionContributionItem(ignoreAncestorAction);
        fIgnoreAncestorItem.setVisible(false);
        tbm.appendToGroup("modes", fIgnoreAncestorItem); //$NON-NLS-1$

        tbm.add(new Separator());

        Action a = new Action() {
            public void run() {
                if (navigate(true, false, false)) {
                    endOfDocumentReached(true);
                }
            }
        };
        Utilities.initAction(a, getResourceBundle(), "action.NextDiff."); //$NON-NLS-1$
        fNextDiff = new ActionContributionItem(a);
        tbm.appendToGroup("navigation", fNextDiff); //$NON-NLS-1$
        // Don't register this action since it is probably registered by the
        // container

        a = new Action() {
            public void run() {
                if (navigate(false, false, false)) {
                    endOfDocumentReached(false);
                }
            }
        };
        Utilities.initAction(a, getResourceBundle(), "action.PrevDiff."); //$NON-NLS-1$
        fPreviousDiff = new ActionContributionItem(a);
        tbm.appendToGroup("navigation", fPreviousDiff); //$NON-NLS-1$
        // Don't register this action since it is probably registered by the
        // container

        a = new Action() {
            public void run() {
                if (navigate(true, false, true)) {
                    endOfDocumentReached(true);
                }
            }
        };
        Utilities.initAction(a, getResourceBundle(), "action.NextChange."); //$NON-NLS-1$
        fNextChange = new ActionContributionItem(a);
        tbm.appendToGroup("navigation", fNextChange); //$NON-NLS-1$
        fHandlerService.registerAction(a, "org.eclipse.compare.selectNextChange"); //$NON-NLS-1$

        a = new Action() {
            public void run() {
                if (navigate(false, false, true)) {
                    endOfDocumentReached(false);
                }
            }
        };
        Utilities.initAction(a, getResourceBundle(), "action.PrevChange."); //$NON-NLS-1$
        fPreviousChange = new ActionContributionItem(a);
        tbm.appendToGroup("navigation", fPreviousChange); //$NON-NLS-1$
        fHandlerService.registerAction(a, "org.eclipse.compare.selectPreviousChange"); //$NON-NLS-1$

        CompareConfiguration cc = getCompareConfiguration();

        if (cc.isRightEditable()) {
            a = new Action() {
                public void run() {
                    copyDiffLeftToRight();
                }
            };
            Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
            fCopyDiffLeftToRightItem = new ActionContributionItem(a);
            fCopyDiffLeftToRightItem.setVisible(true);
            tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
            fHandlerService.registerAction(a, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$
        }

        if (cc.isLeftEditable()) {
            a = new Action() {
                public void run() {
                    copyDiffRightToLeft();
                }
            };
            Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
            fCopyDiffRightToLeftItem = new ActionContributionItem(a);
            fCopyDiffRightToLeftItem.setVisible(true);
            tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
            fHandlerService.registerAction(a, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$
        }

        fIgnoreWhitespace = ChangePropertyAction.createIgnoreWhiteSpaceAction(getResourceBundle(),
                getCompareConfiguration());
        fIgnoreWhitespace.setActionDefinitionId(ICompareUIConstants.COMMAND_IGNORE_WHITESPACE);
        fLeft.addTextAction(fIgnoreWhitespace);
        fRight.addTextAction(fIgnoreWhitespace);
        fAncestor.addTextAction(fIgnoreWhitespace);
        fHandlerService.registerAction(fIgnoreWhitespace, fIgnoreWhitespace.getActionDefinitionId());

        showWhitespaceAction = new ShowWhitespaceAction(new MergeSourceViewer[] { fLeft, fRight, fAncestor });
        fHandlerService.registerAction(showWhitespaceAction,
                ITextEditorActionDefinitionIds.SHOW_WHITESPACE_CHARACTERS);

        toggleLineNumbersAction = new TextEditorPropertyAction(CompareMessages.TextMergeViewer_16,
                new MergeSourceViewer[] { fLeft, fRight, fAncestor },
                AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER);
        fHandlerService.registerAction(toggleLineNumbersAction, ITextEditorActionDefinitionIds.LINENUMBER_TOGGLE);
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.eclipse.compare.contentmergeviewer.ContentMergeViewer#
     * handlePropertyChangeEvent(org.eclipse.jface.util.PropertyChangeEvent)
     */
    protected void handlePropertyChangeEvent(PropertyChangeEvent event) {

        String key = event.getProperty();

        if (key.equals(CompareConfiguration.IGNORE_WHITESPACE)
                || key.equals(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS)) {

            fShowPseudoConflicts = fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);

            update(true);
            selectFirstDiff(true);

            // } else if (key.equals(ComparePreferencePage.USE_SPLINES)) {
            // fUseSplines=
            // fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
            // invalidateLines();

        } else if (key.equals(ComparePreferencePage.USE_SINGLE_LINE)) {
            fUseSingleLine = fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE);
            // fUseResolveUI= fUseSingleLine;
            fBasicCenterCurve = null;
            updateResolveStatus();
            invalidateLines();

        } else if (key.equals(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES)) {
            fHighlightTokenChanges = fPreferenceStore.getBoolean(ComparePreferencePage.HIGHLIGHT_TOKEN_CHANGES);
            updateResolveStatus();
            updatePresentation(null);

            // } else if (key.equals(ComparePreferencePage.USE_RESOLVE_UI)) {
            // fUseResolveUI=
            // fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI);
            // updateResolveStatus();
            // invalidateLines();

        } else if (key.equals(fSymbolicFontName)) {
            updateFont();
            invalidateLines();

        } else if (key.equals(INCOMING_COLOR) || key.equals(OUTGOING_COLOR) || key.equals(CONFLICTING_COLOR)
                || key.equals(RESOLVED_COLOR)) {
            updateColors(null);
            invalidateLines();
            invalidateTextPresentation();

        } else if (key.equals(ComparePreferencePage.SYNCHRONIZE_SCROLLING)) {
            boolean b = fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
            setSyncScrolling(b);

        } else {
            super.handlePropertyChangeEvent(event);

            if (key.equals(ICompareUIConstants.PROP_IGNORE_ANCESTOR)) {
                update(false);
                selectFirstDiff(true);
            }
        }
    }

    private void selectFirstDiff(boolean first) {

        if (fLeft == null || fRight == null) {
            return;
        }
        if (fLeft.getDocument() == null || fRight.getDocument() == null) {
            return;
        }

        Diff firstDiff = null;
        if (first)
            firstDiff = findNext(fRight, -1, -1, false);
        else
            firstDiff = findPrev(fRight, 9999999, 9999999, false);
        setCurrentDiff(firstDiff, true);
    }

    private void setSyncScrolling(boolean newMode) {
        if (fSynchronizedScrolling != newMode) {
            fSynchronizedScrolling = newMode;

            scrollVertical(0, 0, 0, null);

            // throw away central control (Sash or Canvas)
            Control center = getCenterControl();
            if (center != null && !center.isDisposed())
                center.dispose();

            fLeft.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
            fRight.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);

            fComposite.layout(true);
        }
    }

    protected void updateToolItems() {
        // only update toolbar items if diffs need to be calculated (which
        // dictates whether a toolbar gets added at all)
        if (!isPatchHunk()) {
            if (fIgnoreAncestorItem != null)
                fIgnoreAncestorItem.setVisible(isThreeWay());

            if (fCopyDiffLeftToRightItem != null) {
                IAction a = fCopyDiffLeftToRightItem.getAction();
                if (a != null)
                    a.setEnabled(a.isEnabled() && !fHasErrors);
            }
            if (fCopyDiffRightToLeftItem != null) {
                IAction a = fCopyDiffRightToLeftItem.getAction();
                if (a != null)
                    a.setEnabled(a.isEnabled() && !fHasErrors);
            }

            super.updateToolItems();
        }
    }

    // ---- painting lines

    private void updateLines(IDocument d) {

        boolean left = false;
        boolean right = false;

        // FIXME: this optimization is incorrect because
        // it doesn't take replace operations into account where
        // the old and new line count does not differ
        if (d == fLeft.getDocument()) {
            int l = fLeft.getLineCount();
            left = fLeftLineCount != l;
            fLeftLineCount = l;
        } else if (d == fRight.getDocument()) {
            int l = fRight.getLineCount();
            right = fRightLineCount != l;
            fRightLineCount = l;
        }

        if (left || right) {

            if (left) {
                if (fLeftCanvas != null)
                    fLeftCanvas.redraw();
            } else {
                if (fRightCanvas != null)
                    fRightCanvas.redraw();
            }
            Control center = getCenterControl();
            if (center != null)
                center.redraw();

            updateVScrollBar();
            refreshBirdsEyeView();
        }
    }

    private void invalidateLines() {
        if (isThreeWay()) {
            if (Utilities.okToUse(fAncestorCanvas))
                fAncestorCanvas.redraw();
            if (fAncestor != null && fAncestor.isControlOkToUse())
                fAncestor.getTextWidget().redraw();
        }

        if (Utilities.okToUse(fLeftCanvas))
            fLeftCanvas.redraw();

        if (fLeft != null && fLeft.isControlOkToUse())
            fLeft.getTextWidget().redraw();

        if (Utilities.okToUse(getCenterControl()))
            getCenterControl().redraw();

        if (fRight != null && fRight.isControlOkToUse())
            fRight.getTextWidget().redraw();

        if (Utilities.okToUse(fRightCanvas))
            fRightCanvas.redraw();
    }

    private boolean showResolveUI() {
        if (!fUseResolveUI || !isThreeWay() || isIgnoreAncestor())
            return false;
        CompareConfiguration cc = getCompareConfiguration();
        // we only enable the new resolve UI if exactly one side is editable
        boolean l = cc.isLeftEditable();
        boolean r = cc.isRightEditable();
        // return (l && !r) || (r && !l);
        return l || r;
    }

    private void paintCenter(Canvas canvas, GC g) {

        Display display = canvas.getDisplay();

        checkForColorUpdate(display);

        if (!fSynchronizedScrolling)
            return;

        int lineHeightLeft = fLeft.getTextWidget().getLineHeight();
        int lineHeightRight = fRight.getTextWidget().getLineHeight();
        int visibleHeight = fRight.getViewportHeight();

        Point size = canvas.getSize();
        int x = 0;
        int w = size.x;

        g.setBackground(canvas.getBackground());
        g.fillRectangle(x + 1, 0, w - 2, size.y);

        if (!fIsMotif) {
            // draw thin line between center ruler and both texts
            g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
            g.fillRectangle(0, 0, 1, size.y);
            g.fillRectangle(w - 1, 0, 1, size.y);
        }

        if (!fHighlightRanges)
            return;

        boolean showResolveUI = showResolveUI();

        if (fMerger.hasChanges()) {
            int lshift = fLeft.getVerticalScrollOffset();
            int rshift = fRight.getVerticalScrollOffset();

            Point region = new Point(0, 0);

            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                if (diff.isDeleted())
                    continue;

                if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                    continue;

                fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
                int ly = (region.x * lineHeightLeft) + lshift;
                int lh = region.y * lineHeightLeft;

                fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
                int ry = (region.x * lineHeightRight) + rshift;
                int rh = region.y * lineHeightRight;

                if (Math.max(ly + lh, ry + rh) < 0)
                    continue;
                if (Math.min(ly, ry) >= visibleHeight)
                    break;

                fPts[0] = x;
                fPts[1] = ly;
                fPts[2] = w;
                fPts[3] = ry;
                fPts[6] = x;
                fPts[7] = ly + lh;
                fPts[4] = w;
                fPts[5] = ry + rh;

                Color fillColor = getColor(display, getFillColor(diff));
                Color strokeColor = getColor(display, getStrokeColor(diff));

                if (fUseSingleLine) {
                    int w2 = 3;

                    g.setBackground(fillColor);
                    g.fillRectangle(0, ly, w2, lh); // left
                    g.fillRectangle(w - w2, ry, w2, rh); // right

                    g.setLineWidth(0 /* LW */);
                    g.setForeground(strokeColor);
                    g.drawRectangle(0 - 1, ly, w2, lh); // left
                    g.drawRectangle(w - w2, ry, w2, rh); // right

                    if (fUseSplines) {
                        int[] points = getCenterCurvePoints(w2, ly + lh / 2, w - w2, ry + rh / 2);
                        for (int i = 1; i < points.length; i++)
                            g.drawLine(w2 + i - 1, points[i - 1], w2 + i, points[i]);
                    } else {
                        g.drawLine(w2, ly + lh / 2, w - w2, ry + rh / 2);
                    }
                } else {
                    // two lines
                    if (fUseSplines) {
                        g.setBackground(fillColor);

                        g.setLineWidth(0 /* LW */);
                        g.setForeground(strokeColor);

                        int[] topPoints = getCenterCurvePoints(fPts[0], fPts[1], fPts[2], fPts[3]);
                        int[] bottomPoints = getCenterCurvePoints(fPts[6], fPts[7], fPts[4], fPts[5]);
                        g.setForeground(fillColor);
                        g.drawLine(0, bottomPoints[0], 0, topPoints[0]);
                        for (int i = 1; i < bottomPoints.length; i++) {
                            g.setForeground(fillColor);
                            g.drawLine(i, bottomPoints[i], i, topPoints[i]);
                            g.setForeground(strokeColor);
                            g.drawLine(i - 1, topPoints[i - 1], i, topPoints[i]);
                            g.drawLine(i - 1, bottomPoints[i - 1], i, bottomPoints[i]);
                        }
                    } else {
                        g.setBackground(fillColor);
                        g.fillPolygon(fPts);

                        g.setLineWidth(0 /* LW */);
                        g.setForeground(strokeColor);
                        g.drawLine(fPts[0], fPts[1], fPts[2], fPts[3]);
                        g.drawLine(fPts[6], fPts[7], fPts[4], fPts[5]);
                    }
                }

                if (fUseSingleLine && showResolveUI && diff.isUnresolvedIncomingOrConflicting()) {
                    // draw resolve state
                    int cx = (w - RESOLVE_SIZE) / 2;
                    int cy = ((ly + lh / 2) + (ry + rh / 2) - RESOLVE_SIZE) / 2;

                    g.setBackground(fillColor);
                    g.fillRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE);

                    g.setForeground(strokeColor);
                    g.drawRectangle(cx, cy, RESOLVE_SIZE, RESOLVE_SIZE);
                }
            }
        }
    }

    private int[] getCenterCurvePoints(int startx, int starty, int endx, int endy) {
        if (fBasicCenterCurve == null)
            buildBaseCenterCurve(endx - startx);
        double height = endy - starty;
        height = height / 2;
        int width = endx - startx;
        int[] points = new int[width];
        for (int i = 0; i < width; i++) {
            points[i] = (int) (-height * fBasicCenterCurve[i] + height + starty);
        }
        return points;
    }

    private void buildBaseCenterCurve(int w) {
        double width = w;
        fBasicCenterCurve = new double[getCenterWidth()];
        for (int i = 0; i < getCenterWidth(); i++) {
            double r = i / width;
            fBasicCenterCurve[i] = Math.cos(Math.PI * r);
        }
    }

    private void paintSides(GC g, MergeSourceViewer tp, Canvas canvas, boolean right) {

        Display display = canvas.getDisplay();

        int lineHeight = tp.getTextWidget().getLineHeight();
        int visibleHeight = tp.getViewportHeight();

        Point size = canvas.getSize();
        int x = 0;
        int w = fMarginWidth;
        int w2 = w / 2;

        g.setBackground(canvas.getBackground());
        g.fillRectangle(x, 0, w, size.y);

        if (!fIsMotif) {
            // draw thin line between ruler and text
            g.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
            if (right)
                g.fillRectangle(0, 0, 1, size.y);
            else
                g.fillRectangle(size.x - 1, 0, 1, size.y);
        }

        if (!fHighlightRanges)
            return;

        if (fMerger.hasChanges()) {
            int shift = tp.getVerticalScrollOffset() + (2 - LW);

            Point region = new Point(0, 0);
            char leg = getLeg(tp);
            for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                Diff diff = (Diff) iterator.next();
                if (diff.isDeleted())
                    continue;

                if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                    continue;

                tp.getLineRange(diff.getPosition(leg), region);
                int y = (region.x * lineHeight) + shift;
                int h = region.y * lineHeight;

                if (y + h < 0)
                    continue;
                if (y >= visibleHeight)
                    break;

                g.setBackground(getColor(display, getFillColor(diff)));
                if (right)
                    g.fillRectangle(x, y, w2, h);
                else
                    g.fillRectangle(x + w2, y, w2, h);

                g.setLineWidth(0 /* LW */);
                g.setForeground(getColor(display, getStrokeColor(diff)));
                if (right)
                    g.drawRectangle(x - 1, y - 1, w2, h);
                else
                    g.drawRectangle(x + w2, y - 1, w2, h);
            }
        }
    }

    private void paint(PaintEvent event, MergeSourceViewer tp) {

        if (!fHighlightRanges)
            return;
        if (!fMerger.hasChanges())
            return;

        Control canvas = (Control) event.widget;
        GC g = event.gc;

        Display display = canvas.getDisplay();

        int lineHeight = tp.getTextWidget().getLineHeight();
        int w = canvas.getSize().x;
        int shift = tp.getVerticalScrollOffset() + (2 - LW);
        int maxh = event.y + event.height; // visibleHeight

        // if (fIsMotif)
        shift += fTopInset;

        Point range = new Point(0, 0);

        char leg = getLeg(tp);
        for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
            Diff diff = (Diff) iterator.next();
            if (diff.isDeleted())
                continue;

            if (fShowCurrentOnly && !isCurrentDiff(diff))
                continue;

            tp.getLineRange(diff.getPosition(leg), range);
            int y = (range.x * lineHeight) + shift;
            int h = range.y * lineHeight;

            if (y + h < event.y)
                continue;
            if (y > maxh)
                break;

            g.setBackground(getColor(display, getStrokeColor(diff)));
            g.fillRectangle(0, y - 1, w, LW);
            g.fillRectangle(0, y + h - 1, w, LW);
        }
    }

    private RGB getFillColor(Diff diff) {
        boolean selected = fCurrentDiff != null && fCurrentDiff.getParent() == diff;
        RGB selected_fill = getBackground(null);
        if (isThreeWay() && !isIgnoreAncestor()) {
            switch (diff.getKind()) {
            case RangeDifference.RIGHT:
                if (fLeftIsLocal)
                    return selected ? selected_fill : INCOMING_FILL;
                return selected ? selected_fill : OUTGOING_FILL;
            case RangeDifference.ANCESTOR:
                return selected ? selected_fill : CONFLICT_FILL;
            case RangeDifference.LEFT:
                if (fLeftIsLocal)
                    return selected ? selected_fill : OUTGOING_FILL;
                return selected ? selected_fill : INCOMING_FILL;
            case RangeDifference.CONFLICT:
                return selected ? selected_fill : CONFLICT_FILL;
            }
            return null;
        }
        return selected ? selected_fill : OUTGOING_FILL;
    }

    private RGB getStrokeColor(Diff diff) {
        boolean selected = fCurrentDiff != null && fCurrentDiff.getParent() == diff;

        if (isThreeWay() && !isIgnoreAncestor()) {
            switch (diff.getKind()) {
            case RangeDifference.RIGHT:
                if (fLeftIsLocal)
                    return selected ? SELECTED_INCOMING : INCOMING;
                return selected ? SELECTED_OUTGOING : OUTGOING;
            case RangeDifference.ANCESTOR:
                return selected ? SELECTED_CONFLICT : CONFLICT;
            case RangeDifference.LEFT:
                if (fLeftIsLocal)
                    return selected ? SELECTED_OUTGOING : OUTGOING;
                return selected ? SELECTED_INCOMING : INCOMING;
            case RangeDifference.CONFLICT:
                return selected ? SELECTED_CONFLICT : CONFLICT;
            }
            return null;
        }
        return selected ? SELECTED_OUTGOING : OUTGOING;
    }

    private Color getColor(Display display, RGB rgb) {
        if (rgb == null)
            return null;
        if (fColors == null)
            fColors = new HashMap(20);
        Color c = (Color) fColors.get(rgb);
        if (c == null) {
            c = new Color(display, rgb);
            fColors.put(rgb, c);
        }
        return c;
    }

    static RGB interpolate(RGB fg, RGB bg, double scale) {
        if (fg != null && bg != null)
            return new RGB((int) ((1.0 - scale) * fg.red + scale * bg.red),
                    (int) ((1.0 - scale) * fg.green + scale * bg.green),
                    (int) ((1.0 - scale) * fg.blue + scale * bg.blue));
        if (fg != null)
            return fg;
        if (bg != null)
            return bg;
        return new RGB(128, 128, 128); // a gray
    }

    // ---- Navigating and resolving Diffs

    private Diff getNextVisibleDiff(boolean down, boolean deep) {
        Diff diff = null;
        MergeSourceViewer part = getNavigationPart();
        if (part == null)
            return null;
        Point s = part.getSelectedRange();
        char leg = getLeg(part);
        for (;;) {
            diff = null;
            diff = internalGetNextDiff(down, deep, part, s);
            if (diff != null && diff.getKind() == RangeDifference.ANCESTOR && !isAncestorVisible()) {
                Position position = diff.getPosition(leg);
                s = new Point(position.getOffset(), position.getLength());
                diff = null;
                continue;
            }
            break;
        }
        return diff;
    }

    private Diff internalGetNextDiff(boolean down, boolean deep, MergeSourceViewer part, Point s) {
        if (fMerger.hasChanges()) {
            if (down)
                return findNext(part, s.x, s.x + s.y, deep);
            return findPrev(part, s.x, s.x + s.y, deep);
        }
        return null;
    }

    private MergeSourceViewer getNavigationPart() {
        MergeSourceViewer part = fFocusPart;
        if (part == null)
            part = fRight;
        return part;
    }

    private Diff getWrappedDiff(Diff diff, boolean down) {
        return fMerger.getWrappedDiff(diff, down);
    }

    /*
     * Returns true if end (or beginning) of document reached.
     */
    private boolean navigate(boolean down, boolean wrap, boolean deep) {
        Diff diff = null;
        boolean wrapped = false;
        for (;;) {
            diff = getNextVisibleDiff(down, deep);
            if (diff == null && wrap) {
                if (wrapped)
                    // We've already wrapped once so break out
                    break;
                wrapped = true;
                diff = getWrappedDiff(null, down);
            }
            if (diff != null)
                setCurrentDiff(diff, true, deep);
            if (diff != null && diff.getKind() == RangeDifference.ANCESTOR && !isAncestorVisible())
                continue;
            break;
        }
        return diff == null;
    }

    private void endOfDocumentReached(boolean down) {
        Control c = getControl();
        if (Utilities.okToUse(c)) {
            handleEndOfDocumentReached(c.getShell(), down);
        }
    }

    private void handleEndOfDocumentReached(Shell shell, boolean next) {
        boolean hasNextElement = hasNextElement(next);
        IPreferenceStore store = CompareUIPlugin.getDefault().getPreferenceStore();
        String value = store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION);
        if (!value.equals(ICompareUIConstants.PREF_VALUE_PROMPT)) {
            // We only want to do the automatic thing if there is something to
            // do
            if (hasNextElement || store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION)
                    .equals(ICompareUIConstants.PREF_VALUE_LOOP)) {
                performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION, next);
                return;
            }
        }
        shell.getDisplay().beep();
        if (hasNextElement) {
            String loopMessage;
            String nextMessage;
            String message;
            String title;
            if (next) {
                title = CompareMessages.TextMergeViewer_0;
                message = CompareMessages.TextMergeViewer_1;
                loopMessage = CompareMessages.TextMergeViewer_2;
                nextMessage = CompareMessages.TextMergeViewer_3;
            } else {
                title = CompareMessages.TextMergeViewer_4;
                message = CompareMessages.TextMergeViewer_5;
                loopMessage = CompareMessages.TextMergeViewer_6;
                nextMessage = CompareMessages.TextMergeViewer_7;
            }
            String[] localLoopOption = new String[] { loopMessage, ICompareUIConstants.PREF_VALUE_LOOP };
            String[] nextElementOption = new String[] { nextMessage, ICompareUIConstants.PREF_VALUE_NEXT };
            NavigationEndDialog dialog = new NavigationEndDialog(shell, title, null, message,
                    new String[][] { localLoopOption, nextElementOption, });
            int result = dialog.open();
            if (result == Window.OK) {
                performEndOfDocumentAction(shell, store, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL,
                        next);
                if (dialog.getToggleState()) {
                    store.putValue(ICompareUIConstants.PREF_NAVIGATION_END_ACTION,
                            store.getString(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL));
                }
            }
        } else {
            String message;
            String title;
            if (next) {
                title = CompareMessages.TextMergeViewer_8;
                message = CompareMessages.TextMergeViewer_9;
            } else {
                title = CompareMessages.TextMergeViewer_10;
                message = CompareMessages.TextMergeViewer_11;
            }
            if (MessageDialog.openQuestion(shell, title, message)) {
                selectFirstDiff(next);
            }
        }
    }

    private void performEndOfDocumentAction(Shell shell, IPreferenceStore store, String key, boolean next) {
        String value = store.getString(key);
        if (value.equals(ICompareUIConstants.PREF_VALUE_NEXT)) {
            ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
            if (hasNextElement(next))
                navigator.selectChange(next);
            else
                shell.getDisplay().beep();
        } else {
            selectFirstDiff(next);
        }
    }

    private boolean hasNextElement(boolean down) {
        ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
        if (navigator instanceof CompareNavigator) {
            CompareNavigator n = (CompareNavigator) navigator;
            return n.hasChange(down);
        }
        return false;
    }

    /*
     * Find the Diff that overlaps with the given TextPart's text range. If the
     * range doesn't overlap with any range <code>null</code> is returned.
     */
    private Diff findDiff(MergeSourceViewer tp, int rangeStart, int rangeEnd) {
        char contributor = getLeg(tp);
        return fMerger.findDiff(contributor, rangeStart, rangeEnd);
    }

    private Diff findNext(MergeSourceViewer tp, int start, int end, boolean deep) {
        return fMerger.findNext(getLeg(tp), start, end, deep);
    }

    private Diff findPrev(MergeSourceViewer tp, int start, int end, boolean deep) {
        return fMerger.findPrev(getLeg(tp), start, end, deep);
    }

    /*
     * Set the currently active Diff and update the toolbars controls and lines.
     * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed
     * and selected in both TextParts.
     */
    private void setCurrentDiff(Diff d, boolean revealAndSelect) {
        setCurrentDiff(d, revealAndSelect, false);
    }

    /*
     * Set the currently active Diff and update the toolbars controls and lines.
     * If <code>revealAndSelect</code> is <code>true</code> the Diff is revealed
     * and selected in both TextParts.
     */
    private void setCurrentDiff(Diff d, boolean revealAndSelect, boolean deep) {

        // if (d == fCurrentDiff)
        // return;

        if (fCenterButton != null && !fCenterButton.isDisposed())
            fCenterButton.setVisible(false);

        Diff oldDiff = fCurrentDiff;

        if (d != null && revealAndSelect) {

            // before we set fCurrentDiff we change the selection
            // so that the paint code uses the old background colors
            // otherwise selection isn't drawn correctly
            if (d.isToken() || !fHighlightTokenChanges || deep || !d.hasChildren()) {
                if (isThreeWay() && !isIgnoreAncestor())
                    fAncestor.setSelection(d.getPosition(ANCESTOR_CONTRIBUTOR));
                fLeft.setSelection(d.getPosition(LEFT_CONTRIBUTOR));
                fRight.setSelection(d.getPosition(RIGHT_CONTRIBUTOR));
            } else {
                if (isThreeWay() && !isIgnoreAncestor())
                    fAncestor.setSelection(new Position(d.getPosition(ANCESTOR_CONTRIBUTOR).offset, 0));
                fLeft.setSelection(new Position(d.getPosition(LEFT_CONTRIBUTOR).offset, 0));
                fRight.setSelection(new Position(d.getPosition(RIGHT_CONTRIBUTOR).offset, 0));
            }

            // now switch diffs
            fCurrentDiff = d;
            revealDiff(d, d.isToken());
        } else {
            fCurrentDiff = d;
        }

        Diff d1 = oldDiff != null ? oldDiff.getParent() : null;
        Diff d2 = fCurrentDiff != null ? fCurrentDiff.getParent() : null;
        if (d1 != d2) {
            updateDiffBackground(d1);
            updateDiffBackground(d2);
        }

        updateControls();
        invalidateLines();
        refreshBirdsEyeView();
    }

    /*
     * Smart determines whether
     */
    private void revealDiff(Diff d, boolean smart) {

        boolean ancestorIsVisible = false;
        boolean leftIsVisible = false;
        boolean rightIsVisible = false;

        if (smart) {
            Point region = new Point(0, 0);
            // find the starting line of the diff in all text widgets
            int ls = fLeft.getLineRange(d.getPosition(LEFT_CONTRIBUTOR), region).x;
            int rs = fRight.getLineRange(d.getPosition(RIGHT_CONTRIBUTOR), region).x;

            if (isThreeWay() && !isIgnoreAncestor()) {
                int as = fAncestor.getLineRange(d.getPosition(ANCESTOR_CONTRIBUTOR), region).x;
                if (as >= fAncestor.getTopIndex() && as <= fAncestor.getBottomIndex())
                    ancestorIsVisible = true;
            }

            if (ls >= fLeft.getTopIndex() && ls <= fLeft.getBottomIndex())
                leftIsVisible = true;

            if (rs >= fRight.getTopIndex() && rs <= fRight.getBottomIndex())
                rightIsVisible = true;
        }

        // vertical scrolling
        if (!leftIsVisible || !rightIsVisible) {
            int avpos = 0, lvpos = 0, rvpos = 0;

            MergeSourceViewer allButThis = null;
            if (leftIsVisible) {
                avpos = lvpos = rvpos = realToVirtualPosition(LEFT_CONTRIBUTOR, fLeft.getTopIndex());
                allButThis = fLeft;
            } else if (rightIsVisible) {
                avpos = lvpos = rvpos = realToVirtualPosition(RIGHT_CONTRIBUTOR, fRight.getTopIndex());
                allButThis = fRight;
            } else if (ancestorIsVisible) {
                avpos = lvpos = rvpos = realToVirtualPosition(ANCESTOR_CONTRIBUTOR, fAncestor.getTopIndex());
                allButThis = fAncestor;
            } else {
                int vpos = 0;
                for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
                    Diff diff = (Diff) iterator.next();
                    if (diff == d)
                        break;
                    if (fSynchronizedScrolling) {
                        vpos += diff.getMaxDiffHeight();
                    } else {
                        avpos += diff.getAncestorHeight();
                        lvpos += diff.getLeftHeight();
                        rvpos += diff.getRightHeight();
                    }
                }
                if (fSynchronizedScrolling)
                    avpos = lvpos = rvpos = vpos;
                int delta = fRight.getViewportLines() / 4;
                avpos -= delta;
                if (avpos < 0)
                    avpos = 0;
                lvpos -= delta;
                if (lvpos < 0)
                    lvpos = 0;
                rvpos -= delta;
                if (rvpos < 0)
                    rvpos = 0;
            }

            scrollVertical(avpos, lvpos, rvpos, allButThis);

            if (fVScrollBar != null)
                fVScrollBar.setSelection(avpos);
        }

        // horizontal scrolling
        if (d.isToken()) {
            // we only scroll horizontally for token diffs
            reveal(fAncestor, d.getPosition(ANCESTOR_CONTRIBUTOR));
            reveal(fLeft, d.getPosition(LEFT_CONTRIBUTOR));
            reveal(fRight, d.getPosition(RIGHT_CONTRIBUTOR));
        } else {
            // in all other cases we reset the horizontal offset
            hscroll(fAncestor);
            hscroll(fLeft);
            hscroll(fRight);
        }
    }

    private static void reveal(MergeSourceViewer v, Position p) {
        if (v != null && p != null) {
            StyledText st = v.getTextWidget();
            if (st != null) {
                Rectangle r = st.getClientArea();
                if (!r.isEmpty()) // workaround for #7320: Next diff scrolls
                                  // when going into current diff
                    v.revealRange(p.offset, p.length);
            }
        }
    }

    private static void hscroll(MergeSourceViewer v) {
        if (v != null) {
            StyledText st = v.getTextWidget();
            if (st != null)
                st.setHorizontalIndex(0);
        }
    }

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

    void copyAllUnresolved(boolean leftToRight) {
        if (fMerger.hasChanges() && isThreeWay() && !isIgnoreAncestor()) {
            IRewriteTarget target = leftToRight ? fRight.getRewriteTarget() : fLeft.getRewriteTarget();
            boolean compoundChangeStarted = false;
            try {
                for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
                    Diff diff = (Diff) iterator.next();
                    switch (diff.getKind()) {
                    case RangeDifference.LEFT:
                        if (leftToRight) {
                            if (!compoundChangeStarted) {
                                target.beginCompoundChange();
                                compoundChangeStarted = true;
                            }
                            copy(diff, leftToRight);
                        }
                        break;
                    case RangeDifference.RIGHT:
                        if (!leftToRight) {
                            if (!compoundChangeStarted) {
                                target.beginCompoundChange();
                                compoundChangeStarted = true;
                            }
                            copy(diff, leftToRight);
                        }
                        break;
                    default:
                        continue;
                    }
                }
            } finally {
                if (compoundChangeStarted) {
                    target.endCompoundChange();
                }
            }
        }
    }

    /*
     * Copy whole document from one side to the other.
     */
    protected void copy(boolean leftToRight) {
        if (!validateChange(!leftToRight))
            return;
        if (showResolveUI()) {
            copyAllUnresolved(leftToRight);
            invalidateLines();
            return;
        }

        if (leftToRight) {
            if (fLeft.getEnabled()) {
                // copy text
                String text = fLeft.getTextWidget().getText();
                fRight.getTextWidget().setText(text);
                fRight.setEnabled(true);
            } else {
                // delete
                fRight.getTextWidget().setText(""); //$NON-NLS-1$
                fRight.setEnabled(false);
            }
            fRightLineCount = fRight.getLineCount();
            setRightDirty(true);
        } else {
            if (fRight.getEnabled()) {
                // copy text
                String text = fRight.getTextWidget().getText();
                fLeft.getTextWidget().setText(text);
                fLeft.setEnabled(true);
            } else {
                // delete
                fLeft.getTextWidget().setText(""); //$NON-NLS-1$
                fLeft.setEnabled(false);
            }
            fLeftLineCount = fLeft.getLineCount();
            setLeftDirty(true);
        }
        update(false);
        selectFirstDiff(true);
    }

    private void copyDiffLeftToRight() {
        copy(fCurrentDiff, true, false);
    }

    private void copyDiffRightToLeft() {
        copy(fCurrentDiff, false, false);
    }

    /*
     * Copy the contents of the given diff from one side to the other.
     */
    private void copy(Diff diff, boolean leftToRight, boolean gotoNext) {
        if (copy(diff, leftToRight)) {
            if (gotoNext) {
                navigate(true, true, false /* don't step in */);
            } else {
                revealDiff(diff, true);
                updateControls();
            }
        }
    }

    /*
     * Copy the contents of the given diff from one side to the other but
     * doesn't reveal anything. Returns true if copy was successful.
     */
    private boolean copy(Diff diff, boolean leftToRight) {

        if (diff != null && !diff.isResolved()) {
            if (!validateChange(!leftToRight))
                return false;
            if (leftToRight) {
                fRight.setEnabled(true);
            } else {
                fLeft.setEnabled(true);
            }
            boolean result = fMerger.copy(diff, leftToRight);
            if (result)
                updateResolveStatus();
            return result;
        }
        return false;
    }

    private boolean validateChange(boolean left) {
        ContributorInfo info;
        if (left)
            info = fLeftContributor;
        else
            info = fRightContributor;

        return info.validateChange();
    }

    // ---- scrolling

    /*
     * The height of the TextEditors in lines.
     */
    private int getViewportHeight() {
        StyledText te = fLeft.getTextWidget();

        int vh = te.getClientArea().height;
        if (vh == 0) {
            Rectangle trim = te.computeTrim(0, 0, 0, 0);
            int scrollbarHeight = trim.height;

            int headerHeight = getHeaderHeight();

            Composite composite = (Composite) getControl();
            Rectangle r = composite.getClientArea();

            vh = r.height - headerHeight - scrollbarHeight;
        }

        return vh / te.getLineHeight();
    }

    /*
     * Returns the virtual position for the given view position.
     */
    private int realToVirtualPosition(char contributor, int vpos) {
        if (!fSynchronizedScrolling)
            return vpos;
        return fMerger.realToVirtualPosition(contributor, vpos);
    }

    private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) {

        int s = 0;

        if (fSynchronizedScrolling) {
            s = fMerger.getVirtualHeight() - rvpos;
            int height = fRight.getViewportLines() / 4;
            if (s < 0)
                s = 0;
            if (s > height)
                s = height;
        }

        fInScrolling = true;

        if (isThreeWay() && allBut != fAncestor) {
            if (fSynchronizedScrolling || allBut == null) {
                int y = virtualToRealPosition(ANCESTOR_CONTRIBUTOR, avpos + s) - s;
                fAncestor.vscroll(y);
            }
        }

        if (allBut != fLeft) {
            if (fSynchronizedScrolling || allBut == null) {
                int y = virtualToRealPosition(LEFT_CONTRIBUTOR, lvpos + s) - s;
                fLeft.vscroll(y);
            }
        }

        if (allBut != fRight) {
            if (fSynchronizedScrolling || allBut == null) {
                int y = virtualToRealPosition(RIGHT_CONTRIBUTOR, rvpos + s) - s;
                fRight.vscroll(y);
            }
        }

        fInScrolling = false;

        if (isThreeWay() && fAncestorCanvas != null)
            fAncestorCanvas.repaint();

        if (fLeftCanvas != null)
            fLeftCanvas.repaint();

        Control center = getCenterControl();
        if (center instanceof BufferedCanvas)
            ((BufferedCanvas) center).repaint();

        if (fRightCanvas != null)
            fRightCanvas.repaint();
    }

    /*
     * Updates Scrollbars with viewports.
     */
    private void syncViewport(MergeSourceViewer w) {

        if (fInScrolling)
            return;

        int ix = w.getTopIndex();
        int ix2 = w.getDocumentRegionOffset();

        int viewPosition = realToVirtualPosition(getLeg(w), ix - ix2);

        scrollVertical(viewPosition, viewPosition, viewPosition, w); // scroll
        // all
        // but
        // the
        // given
        // views

        if (fVScrollBar != null) {
            int value = Math.max(0, Math.min(viewPosition, fMerger.getVirtualHeight() - getViewportHeight()));
            fVScrollBar.setSelection(value);
            // refreshBirdEyeView();
        }
    }

    /**
     */
    private void updateVScrollBar() {

        if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) {
            int virtualHeight = fMerger.getVirtualHeight();
            int viewPortHeight = getViewportHeight();
            int pageIncrement = viewPortHeight - 1;
            int thumb = (viewPortHeight > virtualHeight) ? virtualHeight : viewPortHeight;

            fVScrollBar.setPageIncrement(pageIncrement);
            fVScrollBar.setMaximum(virtualHeight);
            fVScrollBar.setThumb(thumb);
        }
    }

    /*
     * maps given virtual position into a real view position of this view.
     */
    private int virtualToRealPosition(char contributor, int v) {
        if (!fSynchronizedScrolling)
            return v;
        return fMerger.virtualToRealPosition(contributor, v);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.compare.contentmergeviewer.ContentMergeViewer#flushContent
     * (java.lang.Object, org.eclipse.core.runtime.IProgressMonitor)
     */
    protected void flushContentOld(Object oldInput, IProgressMonitor monitor) {

        // check and handle any shared buffers
        IMergeViewerContentProvider content = getMergeContentProvider();
        if (content == null) {
            return;
        }
        Object leftContent = content.getLeftContent(oldInput);
        Object rightContent = content.getRightContent(oldInput);

        if (leftContent != null && getCompareConfiguration().isLeftEditable() && isLeftDirty()) {
            if (fLeftContributor.hasSharedDocument(leftContent)) {
                if (flush(fLeftContributor))
                    setLeftDirty(false);
            }
        }

        if (rightContent != null && getCompareConfiguration().isRightEditable() && isRightDirty()) {
            if (fRightContributor.hasSharedDocument(rightContent)) {
                if (flush(fRightContributor))
                    setRightDirty(false);
            }
        }

        if (!(content instanceof MergeViewerContentProvider) || isLeftDirty() || isRightDirty()) {
            super.flushContent(oldInput, monitor);
        }
    }

    protected void flushContent(Object oldInput, IProgressMonitor monitor) {
        flushLeftSide(oldInput, monitor);
        flushRightSide(oldInput, monitor);

        IMergeViewerContentProvider content = getMergeContentProvider();
        if (content == null) {
            return;
        }

        if (!(content instanceof MergeViewerContentProvider) || isLeftDirty() || isRightDirty()) {
            super.flushContent(oldInput, monitor);
        }
    }

    private boolean flush(final ContributorInfo info) {
        try {
            return info.flush();
        } catch (CoreException e) {
            handleException(e);
        }
        return false;
    }

    private void handleException(Throwable throwable) {
        // TODO: Should show error to the user
        if (throwable instanceof InvocationTargetException) {
            InvocationTargetException ite = (InvocationTargetException) throwable;
            handleException(ite.getTargetException());
            return;
        }
        CompareUIPlugin.log(throwable);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class adapter) {
        if (adapter == IMergeViewerTestAdapter.class) {
            return new IMergeViewerTestAdapter() {
                public IDocument getDocument(char leg) {
                    switch (leg) {
                    case LEFT_CONTRIBUTOR:
                        return fLeft.getDocument();
                    case RIGHT_CONTRIBUTOR:
                        return fRight.getDocument();
                    case ANCESTOR_CONTRIBUTOR:
                        return fAncestor.getDocument();
                    }
                    return null;
                }

                public int getChangesCount() {
                    return 0;
                }
            };
        }
        if (adapter == OutlineViewerCreator.class) {
            if (fOutlineViewerCreator == null)
                fOutlineViewerCreator = new InternalOutlineViewerCreator();
            return fOutlineViewerCreator;

        }
        if (adapter == IFindReplaceTarget.class)
            return getFindReplaceTarget();
        if (adapter == CompareHandlerService.class)
            return fHandlerService;
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.eclipse.compare.contentmergeviewer.ContentMergeViewer#
     * handleCompareInputChange()
     */
    protected void handleCompareInputChange() {
        try {
            beginRefresh();
            super.handleCompareInputChange();
        } finally {
            endRefresh();
        }
    }

    private void beginRefresh() {
        isRefreshing = true;
        fLeftContributor.cacheSelection(fLeft);
        fRightContributor.cacheSelection(fRight);
        fAncestorContributor.cacheSelection(fAncestor);
        if (fSynchronizedScrolling) {
            fSynchronziedScrollPosition = fVScrollBar.getSelection();
        }

    }

    private void endRefresh() {
        isRefreshing = false;
        fLeftContributor.cacheSelection(null);
        fRightContributor.cacheSelection(null);
        fAncestorContributor.cacheSelection(null);
        fSynchronziedScrollPosition = -1;
    }

    private void synchronizedScrollVertical(int vpos) {
        scrollVertical(vpos, vpos, vpos, null);
        workaround65205();
    }

    private boolean isIgnoreAncestor() {
        return Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false);
    }

    /* package */void update(boolean includeControls) {
        if (getControl().isDisposed())
            return;
        if (fHasErrors) {
            resetDiffs();
        } else {
            doDiff();
        }
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=453799
        // Viewer may be disposed when there are no more diffs available
        if (getControl().isDisposed())
            return;

        if (includeControls)
            updateControls();

        updateVScrollBar();
        updatePresentation(null);
    }

    private void resetDiffs() {
        // clear stuff
        fCurrentDiff = null;
        fMerger.reset();
        resetPositions(fLeft.getDocument());
        resetPositions(fRight.getDocument());
        resetPositions(fAncestor.getDocument());
    }

    private boolean isPatchHunk() {
        return Utilities.isHunk(getInput());
    }

    private boolean isPatchHunkOk() {
        if (isPatchHunk())
            return Utilities.isHunkOk(getInput());
        return false;
    }

    /**
     * Return the provided start position of the hunk in the target file.
     * 
     * @return the provided start position of the hunk in the target file
     */
    private int getHunkStart() {
        Object input = getInput();
        if (input != null && input instanceof DiffNode) {
            ITypedElement right = ((DiffNode) input).getRight();
            if (right != null) {
                Object element = Utilities.getAdapter(right, IHunk.class);
                if (element instanceof IHunk)
                    return ((IHunk) element).getStartPosition();
            }
            ITypedElement left = ((DiffNode) input).getLeft();
            if (left != null) {
                Object element = Utilities.getAdapter(left, IHunk.class);
                if (element instanceof IHunk)
                    return ((IHunk) element).getStartPosition();
            }
        }
        return 0;
    }

    private IFindReplaceTarget getFindReplaceTarget() {
        if (fFindReplaceTarget == null)
            fFindReplaceTarget = new FindReplaceTarget();
        return fFindReplaceTarget;
    }

    /* package */char getLeg(MergeSourceViewer w) {
        if (w == fLeft)
            return LEFT_CONTRIBUTOR;
        if (w == fRight)
            return RIGHT_CONTRIBUTOR;
        if (w == fAncestor)
            return ANCESTOR_CONTRIBUTOR;
        return ANCESTOR_CONTRIBUTOR;
    }

    private boolean isCurrentDiff(Diff diff) {
        if (diff == null)
            return false;
        if (diff == fCurrentDiff)
            return true;
        if (fCurrentDiff != null && fCurrentDiff.getParent() == diff)
            return true;
        return false;
    }

    private boolean isNavigationPossible() {
        if (fCurrentDiff == null && fMerger.hasChanges())
            return true;
        else if (fMerger.changesCount() > 1)
            return true;
        else if (fCurrentDiff != null && fCurrentDiff.hasChildren())
            return true;
        else if (fCurrentDiff != null && fCurrentDiff.isToken())
            return true;
        return false;
    }

    void flushLeftSide(Object oldInput, IProgressMonitor monitor) {
        IMergeViewerContentProvider content = getMergeContentProvider();
        if (content == null) {
            return;
        }
        Object leftContent = content.getLeftContent(oldInput);

        if (leftContent != null && getCompareConfiguration().isLeftEditable() && isLeftDirty()) {
            if (fLeftContributor.hasSharedDocument(leftContent)) {
                if (flush(fLeftContributor))
                    setLeftDirty(false);
            }
        }

        if (!(content instanceof MergeViewerContentProvider) || isLeftDirty()) {
            super.flushLeftSide(oldInput, monitor);
        }
    }

    void flushRightSide(Object oldInput, IProgressMonitor monitor) {
        IMergeViewerContentProvider content = getMergeContentProvider();
        if (content == null) {
            return;
        }
        Object rightContent = content.getRightContent(oldInput);

        if (rightContent != null && getCompareConfiguration().isRightEditable() && isRightDirty()) {
            if (fRightContributor.hasSharedDocument(rightContent)) {
                if (flush(fRightContributor))
                    setRightDirty(false);
            }
        }

        if (!(content instanceof MergeViewerContentProvider) || isRightDirty()) {
            super.flushRightSide(oldInput, monitor);
        }
    }
}