Logo Search packages:      
Sourcecode: eclipse version File versions  Download package

TextMergeViewer.java

/*******************************************************************************
 * Copyright (c) 2000, 2005 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
 *     channingwalton@mac.com - curved line code
 *******************************************************************************/
package org.eclipse.compare.contentmergeviewer;

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.ResourceBundle;
import java.text.MessageFormat;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.custom.*;

import org.eclipse.jface.action.*;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.*;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.CoreException;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IKeyBindingService;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.progress.IProgressService;

import org.eclipse.compare.*;
import org.eclipse.compare.internal.ICompareContextIds;
import org.eclipse.compare.internal.MergeSourceViewer;
import org.eclipse.compare.internal.BufferedCanvas;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.TokenComparator;
import org.eclipse.compare.internal.DocLineComparator;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.INavigatable;
import org.eclipse.compare.internal.CompareNavigator;
import org.eclipse.compare.internal.DocumentManager;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.rangedifferencer.*;
import org.eclipse.compare.structuremergeviewer.*;

/**
 * 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
 */
00120 public class TextMergeViewer extends ContentMergeViewer  {
      
      private 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()
      };
      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
      };
                              
      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 */
00157       private static final int MARGIN_WIDTH= 6;
      /** Width of center bar */
00159       private static final int CENTER_WIDTH= 34;
      /** Width of birds eye view */
00161       private static final int BIRDS_EYE_VIEW_WIDTH= 12;
      /** Width of birds eye view */
00163       private static final int BIRDS_EYE_VIEW_INSET= 2;
      /** */
00165       private static final int RESOLVE_SIZE= 5;
      /** if true copying conflicts from one side to other concatenates both sides */
00167       private static final boolean APPEND_CONFLICT= true;

      /** line width of change borders */
00170       private static final int LW= 1;
      /** Selects between smartTokenDiff and mergingTokenDiff */
00172       private static final boolean USE_MERGING_TOKEN_DIFF= false;
            
      // 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 SELECTED_CONFLICT;
      private RGB CONFLICT;
      private RGB CONFLICT_FILL;
      
      private RGB SELECTED_OUTGOING;
      private RGB OUTGOING;
      private RGB OUTGOING_FILL;
      
      private RGB RESOLVED;

      private boolean fEndOfDocReached;
      private IDocumentListener fDocumentListener;
      
      private IPreferenceStore fPreferenceStore;
      private IPropertyChangeListener fPreferenceChangeListener;
      
      /** All diffs for calculating scrolling position (includes line ranges without changes) */
00208       private ArrayList fAllDiffs;
      /** Subset of above: just real differences. */
00210       private ArrayList fChangeDiffs;
      /** The current diff */
00212       private Diff fCurrentDiff;
      
      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 String fLeftEncoding;
      private String fRightEncoding;
      
      private boolean fInScrolling;
      
      private int fPts[]= new int[8];     // scratch area for polygon drawing
      
      private boolean fIgnoreAncestor= false;
      private ActionContributionItem fIgnoreAncestorItem;
      private boolean fHighlightRanges;
      
      private boolean fShowPseudoConflicts= false;
      
      private boolean fUseSplines= true;
      private boolean fUseSingleLine= true;
      private boolean fUseResolveUI= true;

      private String fSymbolicFontName;

      private ActionContributionItem fNextItem; // goto next difference
      private ActionContributionItem fPreviousItem;   // goto previous difference
      private ActionContributionItem fCopyDiffLeftToRightItem;
      private ActionContributionItem fCopyDiffRightToLeftItem;
      
      private IKeyBindingService fKeyBindingService;
      
      private boolean fSynchronizedScrolling= true;
      private boolean fShowMoreInfo= false;
      
      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;
                              
      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(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();
                  }                 
            }
      }
      
      /*
       * A Diff represents synchronized character ranges in two or three Documents.
       * The MergeTextViewer uses Diffs to find differences in line and token ranges.
       */
      /* package */ class Diff {
            /** character range in ancestor document */
            Position fAncestorPos;
            /** character range in left document */
            Position fLeftPos;
            /** character range in right document */
            Position fRightPos;
            /** if this is a TokenDiff fParent points to the enclosing LineDiff */
            Diff fParent;     
            /** if Diff has been resolved */
            boolean fResolved;
            int fDirection;
            boolean fIsToken= false;
            /** child token diffs */
            ArrayList fDiffs;
            boolean fIsWhitespace= false;

            /*
             * Create Diff from two ranges and an optional parent diff.
             */
            Diff(Diff parent, int dir, IDocument ancestorDoc, Position aRange, int ancestorStart, int ancestorEnd,
                                           IDocument leftDoc, Position lRange, int leftStart, int leftEnd,
                                           IDocument rightDoc, Position rRange, int rightStart, int rightEnd) {
                  fParent= parent != null ? parent : this;
                  fDirection= dir;
                  
                  fLeftPos= createPosition(leftDoc, lRange, leftStart, leftEnd);
                  fRightPos= createPosition(rightDoc, rRange, rightStart, rightEnd);
                  if (ancestorDoc != null)
                        fAncestorPos= createPosition(ancestorDoc, aRange, ancestorStart, ancestorEnd);
            }
            
            Position getPosition(char type) {
                  switch (type) {
                  case 'A':
                        return fAncestorPos;
                  case 'L':
                        return fLeftPos;
                  case 'R':
                        return fRightPos;
                  }
                  return null;
            }
            
            boolean isInRange(char type, int pos) {
                  Position p= getPosition(type);
                  return (pos >= p.offset) && (pos < (p.offset+p.length));
            }
            
            String changeType() {
                  boolean leftEmpty= fLeftPos.length == 0;
                  boolean rightEmpty= fRightPos.length == 0;
                  
                  if (fDirection == RangeDifference.LEFT) {
                        if (!leftEmpty && rightEmpty)
                              return CompareMessages.TextMergeViewer_changeType_addition; 
                        if (leftEmpty && !rightEmpty)
                              return CompareMessages.TextMergeViewer_changeType_deletion; 
                  } else {
                        if (leftEmpty && !rightEmpty)
                              return CompareMessages.TextMergeViewer_changeType_addition; 
                        if (!leftEmpty && rightEmpty)
                              return CompareMessages.TextMergeViewer_changeType_deletion; 
                  }
                  return CompareMessages.TextMergeViewer_changeType_change; 
            }
            
            Image getImage() {
                  int code= Differencer.CHANGE;
                  switch (fDirection) {
                  case RangeDifference.RIGHT:
                        code+= Differencer.LEFT;
                        break;
                  case RangeDifference.LEFT:
                        code+= Differencer.RIGHT;
                        break;
                  case RangeDifference.ANCESTOR:
                  case RangeDifference.CONFLICT:
                        code+= Differencer.CONFLICTING;
                        break;
                  }
                  if (code != 0)
                        return getCompareConfiguration().getImage(code);
                  return null;
            }
            
            Position createPosition(IDocument doc, Position range, int start, int end) {
                  try {
                        int l= end-start;
                        if (range != null) {
                              int dl= range.length;
                              if (l > dl)
                                    l= dl;                              
                        } else {
                              int dl= doc.getLength();
                              if (start+l > dl)
                                    l= dl-start;
                        }
                              
                        Position p= null;
                        try {
                              p= new Position(start, l);
                        } catch (RuntimeException ex) {
                              // silently ignored
                        }
                        
                        try {
                              doc.addPosition(IDocumentRange.RANGE_CATEGORY, p);
                        } catch (BadPositionCategoryException ex) {
                              // silently ignored
                        }
                        return p;
                  } catch (BadLocationException ee) {
                        // silently ignored
                  }
                  return null;
            }

            void add(Diff d) {
                  if (fDiffs == null)
                        fDiffs= new ArrayList();
                  fDiffs.add(d);
            }
            
            boolean isDeleted() {
                  if (fAncestorPos != null && fAncestorPos.isDeleted())
                        return true;
                  return fLeftPos.isDeleted() || fRightPos.isDeleted();
            }
            
            void setResolved(boolean r) {
                  fResolved= r;
                  if (r)
                        fDiffs= null;
            }

            boolean isResolved() {
                  if (!fResolved && fDiffs != null) {
                        Iterator e= fDiffs.iterator();
                        while (e.hasNext()) {
                              Diff d= (Diff) e.next();
                              if (!d.isResolved())
                                    return false;
                        }
                        return true;
                  }
                  return fResolved;
            }
            
//          private boolean isIncoming() {
//                switch (fDirection) {
//                case RangeDifference.RIGHT:
//                      if (fLeftIsLocal)
//                            return true;
//                      break;
//                case RangeDifference.LEFT:
//                      if (!fLeftIsLocal)
//                            return true;
//                      break;
//                }
//                return false;
//          }
            
            private boolean isIncomingOrConflicting() {
                  switch (fDirection) {
                  case RangeDifference.RIGHT:
                        if (fLeftIsLocal)
                              return true;
                        break;
                  case RangeDifference.LEFT:
                        if (!fLeftIsLocal)
                              return true;
                        break;
                  case RangeDifference.CONFLICT:
                        return true;
                  }
                  return false;
            }
            
//          private boolean isUnresolvedIncoming() {
//                if (fResolved)
//                      return false;
//                return isIncoming();
//          }
            
            private boolean isUnresolvedIncomingOrConflicting() {
                  if (fResolved)
                        return false;
                  return isIncomingOrConflicting();
            }
                        
            Position getPosition(MergeSourceViewer w) {
                  if (w == fLeft)
                        return fLeftPos;
                  if (w == fRight)
                        return fRightPos;
                  if (w == fAncestor)
                        return fAncestorPos;
                  return null;
            }
            
            /*
             * Returns true if given character range overlaps with this Diff.
             */
            boolean overlaps(MergeSourceViewer w, int start, int end) {
                  Position h= getPosition(w);
                  if (h != null) {
                        int ds= h.getOffset();
                        int de= ds + h.getLength();
                        if ((start < de) && (end >= ds))
                              return true;      
                  }
                  return false;
            }
                        
            int getMaxDiffHeight(boolean withAncestor) {
                  Point region= new Point(0, 0);
                  int h= fLeft.getLineRange(fLeftPos, region).y;
                  if (withAncestor)
                        h= Math.max(h, fAncestor.getLineRange(fAncestorPos, region).y);
                  return Math.max(h, fRight.getLineRange(fRightPos, region).y);
            }
            
            int getAncestorHeight() {
                  Point region= new Point(0, 0);                  
                  return fAncestor.getLineRange(fAncestorPos, region).y;
            }

            int getLeftHeight() {
                  Point region= new Point(0, 0);                  
                  return fLeft.getLineRange(fLeftPos, region).y;
            }

            int getRightHeight() {
                  Point region= new Point(0, 0);                  
                  return fRight.getLineRange(fRightPos, region).y;
            }
      }

      //---- MergeTextViewer
      
      /**
       * Creates a text merge viewer under the given parent control.
       *
       * @param parent the parent control
       * @param configuration the configuration object
       */
00640       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
       */
00651       public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
            super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
            
            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.propertyChange(event);
                  }
            };

            fPreferenceStore= configuration.getPreferenceStore();
            if (fPreferenceStore != null) {
                  fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
                  
                  checkForColorUpdate(display);

                  fLeftIsLocal= Utilities.getBoolean(configuration, "LEFT_IS_LOCAL", false); //$NON-NLS-1$
                  fSynchronizedScrolling= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
                  fShowMoreInfo= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_MORE_INFO);
                  fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);
                  //fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
                  fUseSingleLine= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SINGLE_LINE);
                  //fUseResolveUI= fPreferenceStore.getBoolean(ComparePreferencePage.USE_RESOLVE_UI);
            }
            
            fDocumentListener= new IDocumentListener() {
                  
                  public void documentAboutToBeChanged(DocumentEvent e) {
                        // nothing to do
                  }
                  
                  public void documentChanged(DocumentEvent e) {
                        TextMergeViewer.this.documentChanged(e);
                  }
            };
            
            buildControl(parent);
            
            INavigatable nav= new INavigatable() {
                  public boolean gotoDifference(boolean next) {
                        return navigate(next, false, false);
                  }
            };
            fComposite.setData(INavigatable.NAVIGATOR_PROPERTY, nav);
            
            fBirdsEyeCursor= new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
            
            JFaceResources.getFontRegistry().addListener(fPreferenceChangeListener);
            JFaceResources.getColorRegistry().addListener(fPreferenceChangeListener);
            updateFont();
      }
      
      private void updateFont() {
            Font f= JFaceResources.getFont(fSymbolicFontName);
            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= display.getSystemColor(SWT.COLOR_LIST_FOREGROUND).getRGB();
                  if (fForeground == null || !fg.equals(fForeground)) {
                        fForeground= fg;
                        updateColors(display);
                  }
            }
            if (fPollSystemBackground) {
                  RGB bg= display.getSystemColor(SWT.COLOR_LIST_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
       */
00748       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
       */
00768       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);

            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);
            
            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);
      
            RESOLVED= registry.getRGB(RESOLVED_COLOR);
            if (RESOLVED == null)
                  RESOLVED= new RGB(0, 255, 0); // GREEN
                        
            refreshBirdsEyeView();
            invalidateLines();
            
            updateAllDiffBackgrounds(display);
      }
      
      /**
       * Invalidates the current presentation by invalidating the three text viewers.
       * @since 2.0
       */
00825       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 does nothing.
       * Subclasses may reimplement to provide a specific configuration for the text viewer.
       *
       * @param textViewer the text viewer to configure
       */
00843       protected void configureTextViewer(TextViewer textViewer) {
            // empty impl
      }
                        
      /**
       * 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.
       */
00856       protected ITokenComparator createTokenComparator(String line) {
            return new TokenComparator(line);
      }
      
      /**
       * 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 created. 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.
       *
       * @return a document partitioner, or <code>null</code>
       */
00872       protected IDocumentPartitioner getDocumentPartitioner() {
            return null;
      }
      
      /**
       * Called on the viewer disposal.
       * Unregisters from the compare configuration.
       * Clients may extend if they have to do additional cleanup.
       * @param event
       */
00882       protected void handleDispose(DisposeEvent event) {
            
            if (fKeyBindingService != null) {
                  IAction a;
                  if (fNextItem != null) {
                        a= fNextItem.getAction();
                        if (a != null)
                              fKeyBindingService.unregisterAction(a);
                  }
                  if (fPreviousItem != null) {
                        a= fPreviousItem.getAction();
                        if (a != null)
                              fKeyBindingService.unregisterAction(a);
                  }
                  if (fCopyDiffLeftToRightItem != null) {
                        a= fCopyDiffLeftToRightItem.getAction();
                        if (a != null)
                              fKeyBindingService.unregisterAction(a);
                  }
                  if (fCopyDiffRightToLeftItem != null) {
                        a= fCopyDiffRightToLeftItem.getAction();
                        if (a != null)
                              fKeyBindingService.unregisterAction(a);
                  }
                  fKeyBindingService= null;
            }
            
            Object input= getInput();
            DocumentManager.remove(getDocument2('A', input));
            DocumentManager.remove(getDocument2('L', input));
            DocumentManager.remove(getDocument2('R', 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;

            unsetDocument(fAncestor);
            unsetDocument(fLeft);
            unsetDocument(fRight);
            
            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;
            }
            
            super.handleDispose(event);
      }
                                                
      //-------------------------------------------------------------------------------------------------------------
      //--- internal ------------------------------------------------------------------------------------------------
      //-------------------------------------------------------------------------------------------------------------
      
      /*
       * Creates the specific SWT controls for the content areas.
       * Clients must not call or override this method.
       */
00961       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);
            
            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);
                  
            fRight= createPart(composite);
            fRight.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
            fRight.addAction(MergeSourceViewer.SAVE_ID, fRightSaveAction);
            
            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();
                              scrollVertical(vpos, vpos, vpos, null);
                              workaround65205();
                        }
                  }
            );
            
            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.fDirection != 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 max= sb1.getMaximum()-sb1.getThumb();
                              double v= 0.0;
                              if (max > 0)
                                    v= (float)sb1.getSelection() / (float)max;
                              if (st2.isVisible()) {
                                    ScrollBar sb2= st2.getHorizontalBar();
                                    st2.setHorizontalPixel((int)((sb2.getMaximum()-sb2.getThumb()) * v));
                              }
                              if (st3.isVisible()) {
                                    ScrollBar sb3= st3.getHorizontalBar();
                                    st3.setHorizontalPixel((int)((sb3.getMaximum()-sb3.getThumb()) * v));
                              }
                              workaround65205();
                      }
                  }
            });
      }

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

      private void setCurrentDiff2(Diff diff, boolean reveal) {
            if (diff != null && diff.fDirection != 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 (fChangeDiffs != null) {
                  int shift= tp.getVerticalScrollOffset() + (2-LW);

                  Point region= new Point(0, 0);
                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff diff= (Diff) e.next();
                        if (diff.isDeleted())
                              continue;

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

                        tp.getLineRange(diff.getPosition(tp), 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 (fChangeDiffs != null) {
                  int lshift= fLeft.getVerticalScrollOffset();
                  int rshift= fRight.getVerticalScrollOffset();

                  Point region= new Point(0, 0);

                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff diff= (Diff) e.next();
                        if (diff.isDeleted())
                              continue;

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

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

                        fRight.getLineRange(diff.fRightPos, 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) {
            int yy, hh;
            
            Point size= canvas.getSize();
            
            int virtualHeight= fSynchronizedScrolling ? getVirtualHeight() : getRightHeight();        
            if (virtualHeight < getViewportHeight())
                  return null;
            
            int y= 0;
            if (fAllDiffs != null) {
                  Iterator e= fAllDiffs.iterator();
                  for (int i= 0; e.hasNext(); i++) {
                        Diff diff= (Diff) e.next();
                        int h= fSynchronizedScrolling ? diff.getMaxDiffHeight(fShowAncestor)
                                                                    : diff.getRightHeight();
                        if (useChange(diff.fDirection) && !diff.fIsWhitespace) {
                                                      
                              yy= (y*size.y)/virtualHeight;
                              hh= (h*size.y)/virtualHeight;
                              if (hh < 3)
                                    hh= 3;
                                    
                              if (my >= yy && my < yy+hh)
                                    return diff;
                        }
                        y+= h;
                  }
            }
            return null;
      }
      
      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 ? getVirtualHeight() : getRightHeight();        
            if (virtualHeight < getViewportHeight())
                  return;
                        
            Display display= canvas.getDisplay();
            int y= 0;
            if (fAllDiffs != null) {
                  Iterator e= fAllDiffs.iterator();
                  for (int i= 0; e.hasNext(); i++) {
                        Diff diff= (Diff) e.next();
                        int h= fSynchronizedScrolling ? diff.getMaxDiffHeight(fShowAncestor)
                                                                    : diff.getRightHeight();
                                                
                        if (useChange(diff.fDirection) && !diff.fIsWhitespace) {
                              
                              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 (diff == fCurrentDiff ||
                                                (fCurrentDiff != null && diff == fCurrentDiff.fParent)) {
                                          gc.setLineWidth(2);
                                          r.x++;
                                          r.y++;
                                          r.width--;
                                          r.height--;
                                    } else {
                                          gc.setLineWidth(1);
                                    }
                                    gc.drawRectangle(r);
                              }
                        }
                        
                        y+= h;
                  }
            }
      }
      
      private void refreshBirdsEyeView() {
            if (fBirdsEyeCanvas != null)
                  fBirdsEyeCanvas.redraw();
      }
      
      /*
       * Called whenever setFocus() is called on the ContentViewer's top level SWT Composite.
       * This implementation sets the focus to the first enabled text widget.
       */
      /* package */ boolean internalSetFocus() {
            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);
            }
      }
      
      /*
       * Creates the central Canvas.
       * Called from ContentMergeViewer.
       */
      /* package */ Control createCenter(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("<");    //$NON-NLS-1$
                        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, false, fCurrentDiff.fDirection == RangeDifference.CONFLICT);
                                          }
                                    }
                              }
                        );                      
                  } else {
                        new Resizer(canvas, HORIZONTAL);
                  }
                  
                  return canvas;
            }
            return super.createCenter(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("<");         //$NON-NLS-1$
                              String tt= fCopyDiffRightToLeftItem.getAction().getToolTipText();
                              fCenterButton.setToolTipText(tt);
                              fCenterButton.setBounds(r);
                              fCenterButton.setVisible(true);
                        } else if (fRight.isEditable()) {
                              fButtonDiff= diff;
                              fCenterButton.setText(">");         //$NON-NLS-1$
                              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;
      }
      
      /*
       * Returns width of central canvas.
       * Overridden from ContentMergeViewer.
       */
      /* package */ int getCenterWidth() {
            if (fSynchronizedScrolling)
                  return CENTER_WIDTH;
            return super.getCenterWidth();
      }

      /*
       * Creates and initializes a text part.
       */
      private MergeSourceViewer createPart(Composite parent) {
            
            final MergeSourceViewer part= new MergeSourceViewer(parent, getResourceBundle());
            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));                 
            
            configureTextViewer(part);
            
            return part;
      }
      
      private void connectGlobalActions(MergeSourceViewer part) {
            IActionBars actionBars= Utilities.findActionBars(fComposite);
            if (actionBars != null) {
                  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;
                              }
                        }
                        actionBars.setGlobalActionHandler(GLOBAL_ACTIONS[i], action);
                  }
                  actionBars.updateActionBars();
            }
      }
      
      ITypedElement getLeg(char type, Object input) {
            if (input instanceof ICompareInput) {
                  switch (type) {
                  case 'A':
                        return ((ICompareInput)input).getAncestor();
                  case 'L':
                        return ((ICompareInput)input).getLeft();
                  case 'R':
                        return ((ICompareInput)input).getRight();
                  }
            }
            return null;
      }

      IDocument getDocument(char type, Object input) {
            ITypedElement te= getLeg(type, input);
            if (te instanceof IDocument)
                  return (IDocument) te;
            if (te instanceof IDocumentRange)
                  return ((IDocumentRange) te).getDocument();
            if (te instanceof IStreamContentAccessor)
                  return DocumentManager.get(te);
            return null;
      }
      
      IDocument getDocument2(char type, Object input) {
            IDocument doc= getDocument(type, input);
            if (doc != null)
                  return doc;
                  
            if (input instanceof IDiffElement) {
                  IDiffContainer parent= ((IDiffElement)input).getParent();
                  return getDocument(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= getDocument2(type, newInput);
            IDocument oldDoc= getDocument2(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
       */
01572       protected boolean doSave(Object newInput, Object oldInput) {
            
            if (oldInput != null && newInput != null) {
                  // check whether underlying documents have changed.
                  if (sameDoc('A', newInput, oldInput) &&
                              sameDoc('L', newInput, oldInput) &&
                                    sameDoc('R', newInput, oldInput)) {
                        if (DEBUG) System.out.println("----- Same docs !!!!");      //$NON-NLS-1$
                        return false;
                  }
            }
            
            if (DEBUG) System.out.println("***** New docs !!!!"); //$NON-NLS-1$
            
            IDocument aDoc= getDocument2('A', oldInput);
            DocumentManager.remove(aDoc);
            IDocument lDoc= getDocument2('L', oldInput);
            DocumentManager.remove(lDoc);
            IDocument rDoc= getDocument2('R', oldInput);
            DocumentManager.remove(rDoc);
            
            if (DEBUG)
                  DocumentManager.dump();
            
            return super.doSave(newInput, oldInput);
      }
      
      private ITypedElement getParent(char type) {
            Object input= getInput();
            if (input instanceof IDiffElement) {
                  IDiffContainer parent= ((IDiffElement)input).getParent();
                  return getLeg(type, parent);
            }
            return null;
      }
            
      /*
       * Initializes the text viewers of the three content areas with the given input objects.
       * Subclasses may extend.
       */
01612       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;
            
            // clear stuff
            fCurrentDiff= null;
            fChangeDiffs= null;
            fAllDiffs= null;
            fEndOfDocReached= false;
            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);
            }
            
            // set new documents
            setDocument(fLeft, 'L', left);
            fLeftLineCount= fLeft.getLineCount();
            fLeftEncoding= getEncoding(left);

            setDocument(fRight, 'R', right);
            fRightLineCount= fRight.getLineCount();
            fRightEncoding= getEncoding(right);
            
            setDocument(fAncestor, 'A', ancestor);
            
            updateHeader();
            updateControls();
            updateToolItems();
            
            if (!fHasErrors)
                  doDiff();

            fRight.setEditable(cc.isRightEditable() && cp.isRightEditable(input));
            fLeft.setEditable(cc.isLeftEditable() && cp.isLeftEditable(input));
            
            invalidateLines();
            updateVScrollBar();
            refreshBirdsEyeView();
            
            if (!fHasErrors && !emptyInput && !fComposite.isDisposed()) {
                  Diff selectDiff= null;
                  if (FIX_47640) {
                        if (leftRange != null)
                            selectDiff= findDiff('L', leftRange);
                        else if (rightRange != null)
                            selectDiff= findDiff('R', rightRange);
                  }
                  if (selectDiff != null)
                        setCurrentDiff(selectDiff, true);
                  else
                        selectFirstDiff();
            }
      }

      private Diff findDiff(char c, Position range) {
            
            MergeSourceViewer v;
            int start= range.getOffset();
            int end= start + range.getLength();
            if (c == 'L')
                  v= fLeft;
            else if (c == 'R')
                  v= fRight;
            else
                  return null;
            
            if (fChangeDiffs != null) {
                  Iterator iter= fChangeDiffs.iterator();
                  while (iter.hasNext()) {
                        Diff diff= (Diff) iter.next();
                        if (diff.isDeleted() || diff.fDirection == RangeDifference.NOCHANGE)
                            continue;
                        if (diff.overlaps(v, start, end))
                              return diff;
                  }
            }
            return null;
      }

      private static String getEncoding(Object o) {
            String encoding= null;
            if (o instanceof IEncodedStreamContentAccessor) {
                  try {
                        encoding= ((IEncodedStreamContentAccessor)o).getCharset();
                  } catch (CoreException e) {
                        // ignored
                  }
            }
            if (encoding == null)
                  encoding= ResourcesPlugin.getEncoding();
            return encoding;
      }
      
      private void updateDiffBackground(Diff diff) {
            
            if (! fHighlightRanges)
                  return;
            
            if (diff == null || diff.fIsToken)
                  return;
                  
            if (fShowCurrentOnly && !isCurrentDiff(diff))
                  return;
                                    
            Color c= getColor(null, getFillColor(diff));
            if (c == null)
                  return;
                  
            if (isThreeWay())
                  fAncestor.setLineBackground(diff.fAncestorPos, c);
            fLeft.setLineBackground(diff.fLeftPos, c);
            fRight.setLineBackground(diff.fRightPos, c);
      }
      
      private void updateAllDiffBackgrounds(Display display) {
            if (fChangeDiffs != null) {
                  boolean threeWay= isThreeWay();
                  Iterator iter= fChangeDiffs.iterator();
                  while (iter.hasNext()) {
                        Diff diff= (Diff) iter.next();
                        Color c= getColor(display, getFillColor(diff));
                        if (threeWay)
                              fAncestor.setLineBackground(diff.fAncestorPos, c);
                        fLeft.setLineBackground(diff.fLeftPos, c);
                        fRight.setLineBackground(diff.fRightPos, c);
                  }
            }
      }
      
      boolean isCurrentDiff(Diff diff) {
            if (diff == null)
                  return false;
            if (diff == fCurrentDiff)
                  return true;
            if (fCurrentDiff != null && fCurrentDiff.fParent == diff)
                  return true;
            return false;
      }
      
      /*
       * 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) {
            
            IDocument doc= e.getDocument();
            
            if (doc == fLeft.getDocument()) {
                  setLeftDirty(true);
            } else if (doc == fRight.getDocument()) {
                  setRightDirty(true);
            }

            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 'A':
                  other= input.getLeft();
                  otherType= 'L';
                  if (other == null) {
                        other= input.getRight();
                        otherType= 'R';
                  }
                  break;
            case 'L':
                  other= input.getRight();
                  otherType= 'R';
                  if (other == null) {
                        other= input.getAncestor();
                        otherType= 'A';
                  }
                  break;
            case 'R':
                  other= input.getLeft();
                  otherType= 'L';
                  if (other == null) {
                        other= input.getAncestor();
                        otherType= 'A';
                  }
                  break;
            }
            
            if (other instanceof IDocumentRange) {
                  IDocumentRange dr= (IDocumentRange) other;
                  Position p= dr.getRange();    
                  Diff diff= findDiff(otherType, p.offset);
                  if (diff != null) {
                        switch (type) {
                        case 'A':
                              if (diff.fAncestorPos != null)
                                    return diff.fAncestorPos.offset;
                              break;
                        case 'L':
                              if (diff.fLeftPos != null)
                                    return diff.fLeftPos.offset;
                              break;
                        case 'R':
                              if (diff.fRightPos != null)
                                    return diff.fRightPos.offset;
                              break;
                        }
                  }
            }
            return 0;
      }
      
      private void setError(char type, String message) {
            IMergeViewerContentProvider cp= getMergeContentProvider();
            if (cp instanceof MergeViewerContentProvider) {
                  MergeViewerContentProvider mcp= (MergeViewerContentProvider) cp;
                  switch (type) {
                  case 'A':
                        mcp.setAncestorError(message);
                        break;
                  case 'L':
                        mcp.setLeftError(message);
                        break;
                  case 'R':
                        mcp.setRightError(message);
                        break;
                  }
            }
            fHasErrors= true;
      }
      
      /*
       * Returns true if a new Document could be installed.
       */
      private boolean setDocument(MergeSourceViewer tp, char type, Object o) {
            
            if (tp == null)
                  return false;
                        
            IDocument newDoc= null;
            Position range= null;

            if (o instanceof IDocumentRange) {
                  newDoc= ((IDocumentRange)o).getDocument();
                  range= ((IDocumentRange)o).getRange();

            } else if (o instanceof IDocument) {
                  newDoc= (IDocument) o;
                  
            } else if (o instanceof IStreamContentAccessor) {
                  
                  newDoc= DocumentManager.get(o);
                  if (newDoc == null) {
                        IStreamContentAccessor sca= (IStreamContentAccessor) o;
                        String s= null;
            
                        try {
                              s= Utilities.readString(sca);
                        } catch (CoreException ex) {
                              setError(type, ex.getMessage());
                        }
      
                        newDoc= new Document(s != null ? s : ""); //$NON-NLS-1$
                        DocumentManager.put(o, newDoc);
                        IDocumentPartitioner partitioner= getDocumentPartitioner();
                        if (partitioner != null) {
                              newDoc.setDocumentPartitioner(partitioner);
                              partitioner.connect(newDoc);
                        }
                  }
            } else if (o == null) { // deletion on one side
                  
                  ITypedElement parent= getParent(type);    // we try to find an insertion position within the deletion's parent
                  
                  if (parent instanceof IDocumentRange) {
                        newDoc= ((IDocumentRange)parent).getDocument();
                        newDoc.addPositionCategory(IDocumentRange.RANGE_CATEGORY);
                        Object input= getInput();
                        range= getNewRange(type, input);
                        if (range == null) {
                              int pos= 0;
                              if (input instanceof ICompareInput)
                                    pos= findInsertionPosition(type, (ICompareInput)input);
                              range= new Position(pos, 0);
                              try {
                                    newDoc.addPosition(IDocumentRange.RANGE_CATEGORY, range);
                              } catch (BadPositionCategoryException ex) {
                                    // silently ignored
                                    if (DEBUG) System.out.println("BadPositionCategoryException: " + ex);   //$NON-NLS-1$
                              } catch (BadLocationException ex) {
                                    // silently ignored
                                    if (DEBUG) System.out.println("BadLocationException: " + ex);     //$NON-NLS-1$
                              }
                              addNewRange(type, input, range);
                        }
                  } else if (parent instanceof IDocument) {
                        newDoc= ((IDocumentRange)o).getDocument();
                  }
            }

            boolean enabled= true;
            if (newDoc == null) {
                  //System.out.println("setDocument: create new Document");
                  newDoc= new Document(""); //$NON-NLS-1$
                  enabled= false;
            }
            
            IDocument oldDoc= tp.getDocument();
            
            if (newDoc != oldDoc) {
                  
                  // got a new document
                  
                  unsetDocument(tp);
                  
                  if (newDoc != null) {
                        newDoc.addPositionCategory(IDocumentRange.RANGE_CATEGORY);
                        if (fPositionUpdater == null)
                              fPositionUpdater= new ChildPositionUpdater(IDocumentRange.RANGE_CATEGORY);
                        else
                              newDoc.removePositionUpdater(fPositionUpdater);
                        newDoc.addPositionUpdater(fPositionUpdater);
                  }

                  // install new document
                  if (newDoc != null) {
                                    
                        tp.setRegion(range);
                        if (fSubDoc) {
                              if (range != null) {
                                    IRegion r= normalizeDocumentRegion(newDoc, toRegion(range));
                                    tp.setDocument(newDoc, r.getOffset(), r.getLength());
                              } else
                                    tp.setDocument(newDoc);
                        } else
                              tp.setDocument(newDoc);
                                                
                        tp.rememberDocument(newDoc);
                        newDoc.addDocumentListener(fDocumentListener);
                        //LeakTester.add(newDoc);
                  }
                  
            } else {    // same document but different range
                  
                  tp.setRegion(range);
                  if (fSubDoc) {
                        if (range != null) {
                              IRegion r= normalizeDocumentRegion(newDoc, toRegion(range));
                              tp.setVisibleRegion(r.getOffset(), r.getLength());
                        } else
                              tp.resetVisibleRegion();
                  } else
                        tp.resetVisibleRegion();
            }
            
            tp.setEnabled(enabled);

            return enabled;
      }
      
      private Position getNewRange(char type, Object input) {
            switch (type) {
            case 'A':
                  return (Position) fNewAncestorRanges.get(input);
            case 'L':
                  return (Position) fNewLeftRanges.get(input);
            case 'R':
                  return (Position) fNewRightRanges.get(input);
            }
            return null;
      }
      
      private void addNewRange(char type, Object input, Position range) {
            switch (type) {
            case 'A':
                  fNewAncestorRanges.put(input, range);
                  break;
            case 'L':
                  fNewLeftRanges.put(input, range);
                  break;
            case 'R':
                  fNewRightRanges.put(input, range);
                  break;
            }
      }

      private void unsetDocument(MergeSourceViewer tp) {
            IDocument oldDoc= tp.getDocument();
            if (oldDoc == null) {
                  oldDoc= tp.getRememberedDocument();
//                if (oldDoc != null)
//                      System.err.println("TextMergeViewer.unsetDocument: would leak");
            }
            if (oldDoc != null) {
                  tp.rememberDocument(null);
                  // de-install old positions
                  if (fPositionUpdater != null)
                        oldDoc.removePositionUpdater(fPositionUpdater);
                  try {
                        oldDoc.removePositionCategory(IDocumentRange.RANGE_CATEGORY);
                  } catch (BadPositionCategoryException ex) {
                        // NeedWork
                  }
                  
                  oldDoc.removeDocumentListener(fDocumentListener);
                  //LeakTester.remove(oldDoc);
            }
      }
      
      /**
       * 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
       */
02083       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 ? fLeftEncoding : fRightEncoding);
                              } 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);
      }
            
02126       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.getTextWidget().setBounds(x, y, width, height);
            } else {
                  if (Utilities.okToUse(fAncestorCanvas))
                        fAncestorCanvas.setVisible(false);
                  if (fAncestor.isControlOkToUse()) {
                        StyledText t= fAncestor.getTextWidget();
                        t.setVisible(false);
                        t.setBounds(0, 0, 0, 0);
                        if (fFocusPart == fAncestor) {
                              fFocusPart= fLeft;
                              fFocusPart.getTextWidget().setFocus();
                        }
                  }
            }
      }

      /*
       * Lays out everything.
       */
02159       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.getTextWidget().setBounds(x, y, leftTextWidth, height);
            x+= leftTextWidth;
            
            if (fCenter == null || fCenter.isDisposed())
                  fCenter= createCenter(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);
                  scrollbarWidth= trim.width + 2*trim.x;
            }
            int rightTextWidth= width2-scrollbarWidth;
            if (fRightCanvas != null)
                  rightTextWidth-= fMarginWidth;
            fRight.getTextWidget().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

      private static int maxWork(IRangeComparator a, IRangeComparator l, IRangeComparator r) {
            int ln= l.getRangeCount();
            int rn= r.getRangeCount();
            if (a != null) {
                  int an= a.getRangeCount();
                  return (2 * Math.max(an, ln)) + (2 * Math.max(an, rn));
            }
            return 2 * Math.max(ln, rn);
      }
      
      /**
       * Perform a two level 2- or 3-way diff.
       * The first level is based on line comparison, the second level on token comparison.
       */
02263       private void doDiff() {
                                    
            fAllDiffs= new ArrayList();
            fChangeDiffs= new ArrayList();
            fCurrentDiff= null;
            
            IDocument aDoc= null;
            IDocument lDoc= fLeft.getDocument();
            IDocument rDoc= fRight.getDocument();
            if (lDoc == null || rDoc == null)
                  return;
                  
            Position aRegion= null;
            Position lRegion= fLeft.getRegion();
            Position rRegion= fRight.getRegion();
                        
            boolean threeWay= isThreeWay();
            
            if (threeWay && !fIgnoreAncestor) {
                  aDoc= fAncestor.getDocument();
                  aRegion= fAncestor.getRegion();
            }
            
            fAncestor.resetLineBackground();
            fLeft.resetLineBackground();
            fRight.resetLineBackground();
            
            boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);           
            
            DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
            DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
            DocLineComparator sancestor= null;
            if (aDoc != null)
                  sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
                  
            if (!fSubDoc && rRegion != null && lRegion != null) {
                  // we have to add a diff for the ignored lines
                  
                  int astart= 0;
                  int as= 0;
                  if (aRegion != null) {
                        astart= aRegion.getOffset();
                        as= Math.max(0, astart-1);
                  }
                  int ys= Math.max(0, lRegion.getOffset()-1);
                  int ms= Math.max(0, rRegion.getOffset()-1);
                  
                  if (as > 0 || ys > 0 || ms > 0) {
                        Diff diff= new Diff(null, RangeDifference.NOCHANGE,
                              aDoc, aRegion, 0, astart,
                              lDoc, lRegion, 0, lRegion.getOffset(),
                              rDoc, rRegion, 0, rRegion.getOffset());
                        fAllDiffs.add(diff);
                  }
            }
            
            final ResourceBundle bundle= getResourceBundle();
                  
            final Object[] result= new Object[1];
            final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
            IRunnableWithProgress runnable= new IRunnableWithProgress() {
                  public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
                        String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
                        monitor.beginTask(progressTitle, maxWork(sa, sl, sr));
                        try {
                              result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr);
                        } catch (OutOfMemoryError ex) {
                              System.gc();
                              throw new InvocationTargetException(ex);
                        }
                        if (monitor.isCanceled())     { // canceled
                              throw new InterruptedException();
                        }
                        monitor.done();
                  }
            };
            IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
            
            RangeDifference[] e= null;
            try {
                  progressService.run(true, true, runnable);
                  e= (RangeDifference[]) result[0];
            } catch (InvocationTargetException ex) {
                  String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
                  String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
                  String msg= MessageFormat.format(format, new Object[] { Integer.toString(progressService.getLongOperationTime()/1000) } );
                  MessageDialog.openError(fComposite.getShell(), title, msg);
                  e= null;
            } catch (InterruptedException ex) {
                  // 
            }
                              
            if (e == null) {
                  // we create a NOCHANGE range for the whole document
                  Diff diff= new Diff(null, RangeDifference.NOCHANGE,
                        aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0,
                        lDoc, lRegion, 0, lDoc.getLength(),
                        rDoc, rRegion, 0, rDoc.getLength());      
                        
                  fAllDiffs.add(diff);
            } else {
                  for (int i= 0; i < e.length; i++) {
                        String a= null, s= null, d= null;
                        RangeDifference es= e[i];
                        
                        int kind= es.kind();
                        
                        int ancestorStart= 0;
                        int ancestorEnd= 0;
                        if (sancestor != null) {
                              ancestorStart= sancestor.getTokenStart(es.ancestorStart());
                              ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength());
                        }
                        
                        int leftStart= sleft.getTokenStart(es.leftStart());
                        int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength());
                        
                        int rightStart= sright.getTokenStart(es.rightStart());
                        int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength());
                        
                        Diff diff= new Diff(null, kind,
                              aDoc, aRegion, ancestorStart, ancestorEnd,
                              lDoc, lRegion, leftStart, leftEnd,
                              rDoc, rRegion, rightStart, rightEnd);     
                        
                        fAllDiffs.add(diff);    // remember all range diffs for scrolling
            
                        if (ignoreWhiteSpace) {
                              if (sancestor != null)
                                    a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
                              s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
                              d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
                        
                              if ((a == null || a.trim().length() == 0) && s.trim().length() == 0 && d.trim().length() == 0) {
                                    diff.fIsWhitespace= true;
                                    continue;
                              }
                        }
            
                        if (useChange(kind)) {
                              fChangeDiffs.add(diff); // here we remember only the real diffs
                              updateDiffBackground(diff);
            
                              if (s == null)
                                    s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
                              if (d == null)
                                    d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
                              
                              if (s.length() > 0 && d.length() > 0) {
                                    if (a == null && sancestor != null)
                                          a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
                                    if (USE_MERGING_TOKEN_DIFF)
                                          mergingTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s);
                                    else
                                          simpleTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s);
                              }
                        }
                  }
            }
            
            if (!fSubDoc && rRegion != null && lRegion != null) {
                  // we have to add a diff for the ignored lines
                  
                  int aEnd= 0;
                  int aLen= 0;
                  if (aRegion != null && aDoc != null) {
                        aEnd= aRegion.getOffset()+aRegion.getLength();
                        aLen= aDoc.getLength();
                  }
                  Diff diff= new Diff(null, RangeDifference.NOCHANGE,
                        aDoc, aRegion, aEnd, aLen,
                        lDoc, lRegion, lRegion.getOffset()+lRegion.getLength(), lDoc.getLength(),
                        rDoc, rRegion, rRegion.getOffset()+rRegion.getLength(), rDoc.getLength());
                  fAllDiffs.add(diff);
            }           
      }
      
      private Diff findDiff(char type, int pos) {
                                                
            IDocument aDoc= null;
            IDocument lDoc= fLeft.getDocument();
            IDocument rDoc= fRight.getDocument();
            if (lDoc == null || rDoc == null)
                  return null;
                  
            Position aRegion= null;
            Position lRegion= null;
            Position rRegion= null;
                        
            boolean threeWay= isThreeWay();
            
            if (threeWay && !fIgnoreAncestor)
                  aDoc= fAncestor.getDocument();

            boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);           
            
            DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
            DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
            DocLineComparator sancestor= null;
            if (aDoc != null)
                  sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
                  
            final ResourceBundle bundle= getResourceBundle();
                  
            final Object[] result= new Object[1];
            final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
            IRunnableWithProgress runnable= new IRunnableWithProgress() {
                  public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
                        String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
                        monitor.beginTask(progressTitle, maxWork(sa, sl, sr));
                        try {
                              result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr);
                        } catch (OutOfMemoryError ex) {
                              System.gc();
                              throw new InvocationTargetException(ex);
                        }
                        if (monitor.isCanceled())     { // canceled
                              throw new InterruptedException();
                        }
                        monitor.done();
                  }
            };
            IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
            
            RangeDifference[] e= null;
            try {
                  progressService.run(true, true, runnable);
                  e= (RangeDifference[]) result[0];
            } catch (InvocationTargetException ex) {
                  String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
                  String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
                  String msg= MessageFormat.format(format, new Object[] { Integer.toString(progressService.getLongOperationTime()/1000) } );
                  MessageDialog.openError(fComposite.getShell(), title, msg);
                  e= null;
            } catch (InterruptedException ex) {
                  // 
            }
                              
            if (e != null) {
                  for (int i= 0; i < e.length; i++) {
                        RangeDifference es= e[i];
                        
                        int kind= es.kind();
                        
                        int ancestorStart= 0;
                        int ancestorEnd= 0;
                        if (sancestor != null) {
                              ancestorStart= sancestor.getTokenStart(es.ancestorStart());
                              ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength());
                        }
                        
                        int leftStart= sleft.getTokenStart(es.leftStart());
                        int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength());
                        
                        int rightStart= sright.getTokenStart(es.rightStart());
                        int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength());
                        
                        Diff diff= new Diff(null, kind,
                              aDoc, aRegion, ancestorStart, ancestorEnd,
                              lDoc, lRegion, leftStart, leftEnd,
                              rDoc, rRegion, rightStart, rightEnd);     

                        if (diff.isInRange(type, pos))
                              return diff;
                  }
            }
            
            return null;
      }
      
      /*
       * Returns true if kind of change should be shown.
       */
      private boolean useChange(int kind) {
            if (kind == RangeDifference.NOCHANGE)
                  return false;
            if (kind == RangeDifference.ANCESTOR)
                  return fShowPseudoConflicts;
            return true;
      }
      
      private int getTokenEnd(ITokenComparator tc, int start, int count) {
            if (count <= 0)
                  return tc.getTokenStart(start);
            int index= start + count - 1;
            return tc.getTokenStart(index) + tc.getTokenLength(index);
      }
      
      private static int getTokenEnd2(ITokenComparator tc, int start, int length) {
            return tc.getTokenStart(start + length);
      }

      /*
       * Returns the content of lines in the specified range as a String.
       * This includes the line separators.
       *
       * @param doc the document from which to extract the characters
       * @param start index of first line
       * @param length number of lines
       * @return the contents of the specified line range as a String
       */
      private String extract2(IDocument doc, ITokenComparator tc, int start, int length) {
            int count= tc.getRangeCount();
            if (length > 0 && count > 0) {
                  
//                
//                int startPos= tc.getTokenStart(start);
//                int endPos= startPos;
//                
//                if (length > 1)
//                      endPos= tc.getTokenStart(start + (length-1));
//                endPos+= tc.getTokenLength(start + (length-1));
//                      

                  int startPos= tc.getTokenStart(start);
                  int endPos;
                  
                  if (length == 1) {
                        endPos= startPos + tc.getTokenLength(start);
                  } else {
                        endPos= tc.getTokenStart(start + length);
                  }

                  try {
                        return doc.get(startPos, endPos - startPos);
                  } catch (BadLocationException e) {
                        // silently ignored
                  }

            }
            return ""; //$NON-NLS-1$
      }

      /*
       * Performs a token based 3-way diff on the character range specified by the given baseDiff.
       */
      private void simpleTokenDiff(final Diff baseDiff,
                        IDocument ancestorDoc, String a,
                        IDocument rightDoc, String d,
                        IDocument leftDoc, String s) {

            int ancestorStart= 0;
            ITokenComparator sa= null;
            if (ancestorDoc != null) {
                  ancestorStart= baseDiff.fAncestorPos.getOffset();
                  sa= createTokenComparator(a);
            }
            
            int rightStart= baseDiff.fRightPos.getOffset(); 
            ITokenComparator sm= createTokenComparator(d);
            
            int leftStart= baseDiff.fLeftPos.getOffset();
            ITokenComparator sy= createTokenComparator(s);
            
            RangeDifference[] e= RangeDifferencer.findRanges(sa, sy, sm);
            for (int i= 0; i < e.length; i++) {
                  RangeDifference es= e[i];
                  int kind= es.kind();
                  if (kind != RangeDifference.NOCHANGE) {
                        
                        int ancestorStart2= ancestorStart;
                        int ancestorEnd2= ancestorStart;
                        if (ancestorDoc != null) {
                              ancestorStart2 += sa.getTokenStart(es.ancestorStart());
                              ancestorEnd2 += getTokenEnd(sa, es.ancestorStart(), es.ancestorLength());
                        }
                        
                        int leftStart2= leftStart + sy.getTokenStart(es.leftStart());
                        int leftEnd2= leftStart + getTokenEnd(sy, es.leftStart(), es.leftLength());
                        
                        int rightStart2= rightStart + sm.getTokenStart(es.rightStart());
                        int rightEnd2= rightStart + getTokenEnd(sm, es.rightStart(), es.rightLength());
                        
                        Diff diff= new Diff(baseDiff, kind,
                                    ancestorDoc, null, ancestorStart2, ancestorEnd2,
                                    leftDoc, null, leftStart2, leftEnd2,
                                    rightDoc, null, rightStart2, rightEnd2);
                        
                        // ensure that token diff is smaller than basediff
                        int leftS= baseDiff.fLeftPos.offset;
                        int leftE= baseDiff.fLeftPos.offset+baseDiff.fLeftPos.length;
                        int rightS= baseDiff.fRightPos.offset;
                        int rightE= baseDiff.fRightPos.offset+baseDiff.fRightPos.length;
                        if (leftS != leftStart2 || leftE != leftEnd2 ||
                                          rightS != rightStart2 || rightE != rightEnd2) {
                              diff.fIsToken= true;
                              // add to base Diff
                              baseDiff.add(diff);
                        }
                  }
            }
      }
      
      /*
       * Performs a "smart" token based 3-way diff on the character range specified by the given baseDiff.
       * It is "smart" because it tries to minimize the number of token diffs by merging them.
       */
      private void mergingTokenDiff(Diff baseDiff, 
                        IDocument ancestorDoc, String a,
                        IDocument rightDoc, String d,
                        IDocument leftDoc, String s) {
            ITokenComparator sa= null;
            int ancestorStart= 0;
            if (ancestorDoc != null) {
                  sa= createTokenComparator(a);
                  ancestorStart= baseDiff.fAncestorPos.getOffset();
            }
            
            int rightStart= baseDiff.fRightPos.getOffset(); 
            ITokenComparator sm= createTokenComparator(d);
            
            int leftStart= baseDiff.fLeftPos.getOffset();
            ITokenComparator sy= createTokenComparator(s);
            
            RangeDifference[] r= RangeDifferencer.findRanges(sa, sy, sm);
            for (int i= 0; i < r.length; i++) {
                  RangeDifference  es= r[i];
                  // determine range of diffs in one line
                  int start= i;
                  int leftLine= -1;
                  int rightLine= -1;
                  try {
                        leftLine= leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart()));
                        rightLine= rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart()));
                  } catch (BadLocationException e) {
                        // silently ignored
                  }
                  i++;
                  for (; i < r.length; i++) {
                        es= r[i];
                        try {
                              if (leftLine != leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart())))
                                    break;
                              if (rightLine != rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart())))
                                    break;
                        } catch (BadLocationException e) {
                              // silently ignored
                        }
                  }
                  int end= i;
                  
                  // find first diff from left
                  RangeDifference first= null;
                  for (int ii= start; ii < end; ii++) {
                        es= r[ii];
                        if (useChange(es.kind())) {
                              first= es;
                              break;
                        }
                  }
                  
                  // find first diff from mine
                  RangeDifference last= null;
                  for (int ii= end-1; ii >= start; ii--) {
                        es= r[ii];
                        if (useChange(es.kind())) {
                              last= es;
                              break;
                        }
                  }
                  
                  if (first != null && last != null) {
                        
                        int ancestorStart2= 0;
                        int ancestorEnd2= 0;
                        if (ancestorDoc != null) {
                              ancestorStart2= ancestorStart+sa.getTokenStart(first.ancestorStart());
                              ancestorEnd2= ancestorStart+getTokenEnd(sa, last.ancestorStart(), last.ancestorLength());
                        }
                        
                        int leftStart2= leftStart+sy.getTokenStart(first.leftStart());
                        int leftEnd2= leftStart+getTokenEnd(sy, last.leftStart(), last.leftLength());
                        
                        int rightStart2= rightStart+sm.getTokenStart(first.rightStart());
                        int rightEnd2= rightStart+getTokenEnd(sm, last.rightStart(), last.rightLength());
                        Diff diff= new Diff(baseDiff, first.kind(),
                                          ancestorDoc, null, ancestorStart2, ancestorEnd2+1,
                                          leftDoc, null, leftStart2, leftEnd2+1,
                                          rightDoc, null, rightStart2, rightEnd2+1);      
                        diff.fIsToken= true;
                        baseDiff.add(diff);
                  }
            }
      }
      
      //---- update UI stuff
      
      private void updateControls() {
            
            boolean leftToRight= false;
            boolean rightToLeft= false;
            
            updateStatus(fCurrentDiff);
            updateResolveStatus();

            if (fCurrentDiff != null) {
                  IMergeViewerContentProvider cp= getMergeContentProvider();
                  if (cp != null) {
                        rightToLeft= cp.isLeftEditable(getInput());
                        leftToRight= cp.isRightEditable(getInput());
                  }                 
            }
            
            if (fDirectionLabel != null) {
                  if (fHighlightRanges && fCurrentDiff != null && isThreeWay() && !fIgnoreAncestor) {
                        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= false;
            if (fCurrentDiff == null && fChangeDiffs != null && fChangeDiffs.size() > 0)
                  enableNavigation= true;
            else if (fChangeDiffs != null && fChangeDiffs.size() > 1)
                  enableNavigation= true;
            else if (fCurrentDiff != null && fCurrentDiff.fDiffs != null)
                  enableNavigation= true;
            else if (fCurrentDiff != null && fCurrentDiff.fIsToken)
                  enableNavigation= true;
            
            if (fNextItem != null) {
                  IAction a= fNextItem.getAction();
                  a.setEnabled(enableNavigation);
            }
            if (fPreviousItem != null) {
                  IAction a= fPreviousItem.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 (fChangeDiffs != null) {
                        Iterator e= fChangeDiffs.iterator();
                        while (e.hasNext()) {
                              Diff d= (Diff) e.next();
                              if (d.isIncomingOrConflicting() /* && useChange(d.fDirection) && !d.fIsWhitespace */) {
                                    incomingOrConflicting++;
                                    if (!d.fResolved) {
                                          if (d.fDirection == 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) {
            
            if (! fShowMoreInfo)
                  return;
            
            IActionBars bars= Utilities.findActionBars(fComposite);
            if (bars == null)
                  return;
            IStatusLineManager slm= bars.getStatusLineManager();
            if (slm == null)
                  return;
                              
            String diffDescription;
            
            if (diff == null) {
                  diffDescription= CompareMessages.TextMergeViewer_diffDescription_noDiff_format;     
            } else {
                  
                  if (diff.fIsToken)            // we don't show special info for token diffs
                        diff= diff.fParent;
            
                  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.fLeftPos),       // 2: left start line
                              getDiffRange(fRight, diff.fRightPos)      // 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
                  }
            );
      
            slm.setMessage(s);
      }

      private void clearStatus() {
            
            IActionBars bars= Utilities.findActionBars(fComposite);
            if (bars == null)
                  return;
            IStatusLineManager slm= bars.getStatusLineManager();
            if (slm == null)
                  return;
                                    
            slm.setMessage(null);
      }
      
      private String getDiffType(Diff diff) {
            String s= "";     //$NON-NLS-1$
            switch(diff.fDirection) {
            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 (fChangeDiffs != null) {
                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff d= (Diff) e.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$
      }

02982       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.
       */
02993       protected void createToolItems(ToolBarManager tbm) {

            IWorkbenchPartSite ps= Utilities.findSite(fComposite);
            fKeyBindingService= ps != null ? ps.getKeyBindingService() : null;
            
            final String ignoreAncestorActionKey= "action.IgnoreAncestor.";   //$NON-NLS-1$
            Action ignoreAncestorAction= new Action() {
                  public void run() {
                        setIgnoreAncestor(! fIgnoreAncestor);
                        Utilities.initToggleAction(this, getResourceBundle(), ignoreAncestorActionKey, fIgnoreAncestor);
                  }
            };
            ignoreAncestorAction.setChecked(fIgnoreAncestor);
            Utilities.initAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey);
            Utilities.initToggleAction(ignoreAncestorAction, getResourceBundle(), ignoreAncestorActionKey, fIgnoreAncestor);
            
            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() {
                        navigate(true, true, true);
                  }
            };
            Utilities.initAction(a, getResourceBundle(), "action.NextDiff."); //$NON-NLS-1$
            fNextItem= new ActionContributionItem(a);
            tbm.appendToGroup("navigation", fNextItem); //$NON-NLS-1$
            Utilities.registerAction(fKeyBindingService, a, "org.eclipse.compare.selectNextChange");  //$NON-NLS-1$
            
            a= new Action() {
                  public void run() {
                        navigate(false, true, true);
                  }
            };
            Utilities.initAction(a, getResourceBundle(), "action.PrevDiff."); //$NON-NLS-1$
            fPreviousItem= new ActionContributionItem(a);
            tbm.appendToGroup("navigation", fPreviousItem); //$NON-NLS-1$
            Utilities.registerAction(fKeyBindingService, 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$
                  Utilities.registerAction(fKeyBindingService, 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$
                  Utilities.registerAction(fKeyBindingService, a, "org.eclipse.compare.copyRightToLeft");   //$NON-NLS-1$
            }
      }
      
      /* package */ void propertyChange(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);
                  
                  // clear stuff
                  fCurrentDiff= null;
                  fChangeDiffs= null;
                  fAllDiffs= null;
                              
                  doDiff();
                              
                  updateControls();
                  invalidateLines();
                  updateVScrollBar();
                  refreshBirdsEyeView();
                  
                  selectFirstDiff();
                  
//          } 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.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();
                  
            } else if (key.equals(ComparePreferencePage.SYNCHRONIZE_SCROLLING)) {
                  
                  boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
                  if (b != fSynchronizedScrolling)
                        toggleSynchMode();
            
            } else if (key.equals(ComparePreferencePage.SHOW_MORE_INFO)) {
                  
                  boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_MORE_INFO);
                  if (b != fShowMoreInfo) {
                        fShowMoreInfo= b;
                        if (fShowMoreInfo)
                              updateStatus(fCurrentDiff);
                        else
                              clearStatus();
                  }
            
            } else
                  super.propertyChange(event);
      }
      
      private void setIgnoreAncestor(boolean ignore) {
            if (ignore != fIgnoreAncestor) {
                  fIgnoreAncestor= ignore;
                  setAncestorVisibility(false, !fIgnoreAncestor);
            
                  // clear stuff
                  fCurrentDiff= null;
                  fChangeDiffs= null;
                  fAllDiffs= null;
                              
                  doDiff();
                              
                  invalidateLines();
                  updateVScrollBar();
                  refreshBirdsEyeView();
                  
                  selectFirstDiff();
            }
      }
      
      private void selectFirstDiff() {
            
            if (fLeft == null || fRight == null) {
                  return;
            }
            if (fLeft.getDocument() == null || fRight.getDocument() == null) {
                  return;
            }
            
            Diff firstDiff= null;
            if (CompareNavigator.getDirection(fComposite))
                  firstDiff= findNext(fRight, fChangeDiffs, -1, -1, false);
            else
                  firstDiff= findPrev(fRight, fChangeDiffs, 9999999, 9999999, false);
            setCurrentDiff(firstDiff, true);
      }
      
      private void toggleSynchMode() {
            fSynchronizedScrolling= ! fSynchronizedScrolling;
            
            scrollVertical(0, 0, 0, null);
            
            // throw away central control (Sash or Canvas)
            Control center= getCenter();
            if (center != null && !center.isDisposed())
                  center.dispose();
            
            fLeft.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);
            fRight.getTextWidget().getVerticalBar().setVisible(!fSynchronizedScrolling);

            fComposite.layout(true);
      }
                              
03186       protected void updateToolItems() {
                              
            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= getCenter();
                  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(getCenter()))
                  getCenter().redraw();
                  
            if (fRight != null && fRight.isControlOkToUse())
                  fRight.getTextWidget().redraw();
                  
            if (Utilities.okToUse(fRightCanvas))
                  fRightCanvas.redraw();
      }
      
      private boolean showResolveUI() {
            if (!fUseResolveUI || !isThreeWay() || fIgnoreAncestor)
                  return false;
            CompareConfiguration cc= getCompareConfiguration();
            if (cc == null)
                  return false;
            // 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 lineHeight= fLeft.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 (fChangeDiffs != null) {
                  int lshift= fLeft.getVerticalScrollOffset();
                  int rshift= fRight.getVerticalScrollOffset();
                              
                  Point region= new Point(0, 0);
            
                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff diff= (Diff) e.next();
                        if (diff.isDeleted())
                              continue;
                        
                        if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                              continue;

                        fLeft.getLineRange(diff.fLeftPos, region);
                        int ly= (region.x * lineHeight) + lshift;
                        int lh= region.y * lineHeight;
      
                        fRight.getLineRange(diff.fRightPos, 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;
      
                        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(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(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(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 (fChangeDiffs != null) {
                  int shift= tp.getVerticalScrollOffset() + (2-LW);
                        
                  Point region= new Point(0, 0);
                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff diff= (Diff) e.next();
                        if (diff.isDeleted())
                              continue;
                        
                        if (fShowCurrentOnly2 && !isCurrentDiff(diff))
                              continue;

                        tp.getLineRange(diff.getPosition(tp), 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(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 (fChangeDiffs == null)
                  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);
                        
            Iterator e= fChangeDiffs.iterator();      
            while (e.hasNext()) {
                  Diff diff= (Diff) e.next();
                  if (diff.isDeleted())
                        continue;
                  
                  if (fShowCurrentOnly && !isCurrentDiff(diff))
                        continue;

                  tp.getLineRange(diff.getPosition(tp), 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.fParent == diff;
            
            RGB selected_fill= getBackground(null);

            if (isThreeWay() && !fIgnoreAncestor) {
                  switch (diff.fDirection) {
                  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.fParent == diff;
            
            if (isThreeWay() && !fIgnoreAncestor) {
                  switch (diff.fDirection) {
                  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
      
      /*
       * Returns true if end (or beginning) of document reached.
       */
      private boolean navigate(boolean down, boolean wrap, boolean deep) {

            Diff diff= null;
            
            for (;;) {
                  
                  if (fChangeDiffs != null) {
                        MergeSourceViewer part= fFocusPart;
                        if (part == null)
                              part= fRight;
                        
                        if (part != null) {
                              Point s= part.getSelectedRange();
                              if (down)
                                    diff= findNext(part, fChangeDiffs, s.x, s.x+s.y, deep);
                              else
                                    diff= findPrev(part, fChangeDiffs, s.x, s.x+s.y, deep);                             
                        }           
                  }
            
                  if (diff == null) {     // at end or beginning
                        if (wrap) {
                              if (!fEndOfDocReached) {
                                    fEndOfDocReached= true;
                                    if (! endOfDocumentReached(down))
                                          return true;
                              }
                              fEndOfDocReached= false;
                              if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
                                    if (down)
                                          diff= (Diff) fChangeDiffs.get(0);
                                    else
                                          diff= (Diff) fChangeDiffs.get(fChangeDiffs.size()-1);
                              }
                        } else {
                              fEndOfDocReached= false;      
                              return true;
                        }
                  }
                  
                  setCurrentDiff(diff, true);
                  
                  if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
                                                      && !getAncestorEnabled())
                        continue;
                        
                  break;
            }

            return false;
      }
      
      private boolean endOfDocumentReached(boolean down) {
            Control c= getControl();
            if (Utilities.okToUse(c)) {
                  
                  c.getDisplay().beep();
                  
                  if (down)
                        return MessageDialog.openQuestion(c.getShell(),
                              CompareMessages.TextMergeViewer_atEnd_title,    
                              CompareMessages.TextMergeViewer_atEnd_message); 
                  return MessageDialog.openQuestion(c.getShell(),
                              CompareMessages.TextMergeViewer_atBeginning_title,    
                              CompareMessages.TextMergeViewer_atBeginning_message); 
            }
            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) {
            if (fChangeDiffs != null) {
                  Iterator e= fChangeDiffs.iterator();
                  while (e.hasNext()) {
                        Diff diff= (Diff) e.next();
                        if (diff.overlaps(tp, rangeStart, rangeEnd))
                              return diff;
                  }
            }
            return null;
      }
      
      private static Diff findNext(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
            for (int i= 0; i < v.size(); i++) {
                  Diff diff= (Diff) v.get(i);
                  Position p= diff.getPosition(tp);
                  if (p != null) {
                        int startOffset= p.getOffset();
                        if (end < startOffset)  // <=
                              return diff;
                        if (deep && diff.fDiffs != null) {
                              Diff d= null;
                              int endOffset= startOffset + p.getLength();
                              if (start == startOffset && (end == endOffset || end == endOffset-1)) {
                                    d= findNext(tp, diff.fDiffs, start-1, start-1, deep);
                              } else if (end < endOffset) {
                                    d= findNext(tp, diff.fDiffs, start, end, deep);
                              }
                              if (d != null)
                                    return d;
                        }
                  }
            }
            return null;
      }
      
      private static Diff findPrev(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
            for (int i= v.size()-1; i >= 0; i--) {
                  Diff diff= (Diff) v.get(i);
                  Position p= diff.getPosition(tp);
                  if (p != null) {
                        int startOffset= p.getOffset();
                        int endOffset= startOffset + p.getLength();
                        if (start > endOffset)
                              return diff;
                        if (deep && diff.fDiffs != null) {
                              Diff d= null;
                              if (start == startOffset && end == endOffset) {
                                    d= findPrev(tp, diff.fDiffs, end, end, deep);
                              } else if (start >= startOffset) {
                                    d= findPrev(tp, diff.fDiffs, start, end, deep);
                              }
                              if (d != null)
                                    return d;
                        }
                  }
            }
            return null;
      }
            
      /*
       * 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) {

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

            if (fCenterButton != null && !fCenterButton.isDisposed())
                  fCenterButton.setVisible(false);
                                    
            fEndOfDocReached= 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 (isThreeWay() && !fIgnoreAncestor)
                        fAncestor.setSelection(d.fAncestorPos);
                  fLeft.setSelection(d.fLeftPos);
                  fRight.setSelection(d.fRightPos);
                  
                  // now switch diffs
                  fCurrentDiff= d;
                  revealDiff(d, d.fIsToken);
            } else {
                  fCurrentDiff= d;
            }
            
            Diff d1= oldDiff != null ? oldDiff.fParent : null;
            Diff d2= fCurrentDiff != null ? fCurrentDiff.fParent : 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.fLeftPos, region).x;
                  int rs= fRight.getLineRange(d.fRightPos, region).x;
                  
                  if (isThreeWay() && !fIgnoreAncestor) {
                        int as= fAncestor.getLineRange(d.fAncestorPos, 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(fLeft, fLeft.getTopIndex());
                        allButThis= fLeft;
                  } else if (rightIsVisible) {
                        avpos= lvpos= rvpos= realToVirtualPosition(fRight, fRight.getTopIndex());
                        allButThis= fRight;
                  } else if (ancestorIsVisible) {
                        avpos= lvpos= rvpos= realToVirtualPosition(fAncestor, fAncestor.getTopIndex());
                        allButThis= fAncestor;
                  } else {
                        if (fAllDiffs != null) {
                              int vpos= 0;
                              Iterator e= fAllDiffs.iterator();
                              for (int i= 0; e.hasNext(); i++) {
                                    Diff diff= (Diff) e.next();
                                    if (diff == d)
                                          break;
                                    if (fSynchronizedScrolling) {
                                          vpos+= diff.getMaxDiffHeight(fShowAncestor);
                                    } 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.fIsToken) {
                  // we only scroll horizontally for token diffs
                  reveal(fAncestor, d.fAncestorPos);
                  reveal(fLeft, d.fLeftPos);
                  reveal(fRight, d.fRightPos);
            } 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 (fChangeDiffs != null && isThreeWay() && !fIgnoreAncestor) {
                  IRewriteTarget target= leftToRight ? fRight.getRewriteTarget() : fLeft.getRewriteTarget();
                  boolean compoundChangeStarted= false;
                  Iterator e= fChangeDiffs.iterator();
                  try {
                        while (e.hasNext()) {
                              Diff diff= (Diff) e.next();
                              switch (diff.fDirection) {
                              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.
       */
03958       protected void copy(boolean leftToRight) {
            
            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);
            }
            doDiff();
            invalidateLines();
            updateVScrollBar();
            selectFirstDiff();
            refreshBirdsEyeView();
      }

      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, true);
                  } 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()) {

                  Position fromPos= null;
                  Position toPos= null;
                  IDocument fromDoc= null;
                  IDocument toDoc= null;

                  if (leftToRight) {
                        fRight.setEnabled(true);
                        fromPos= diff.fLeftPos;
                        toPos= diff.fRightPos;
                        fromDoc= fLeft.getDocument();
                        toDoc= fRight.getDocument();
                  } else {
                        fLeft.setEnabled(true);
                        fromPos= diff.fRightPos;
                        toPos= diff.fLeftPos;
                        fromDoc= fRight.getDocument();
                        toDoc= fLeft.getDocument();
                  }
                  
                  if (fromDoc != null) {
                        
                        int fromStart= fromPos.getOffset();
                        int fromLen= fromPos.getLength();
                        
                        int toStart= toPos.getOffset();
                        int toLen= toPos.getLength();

                        try {
                              String s= null;
                                                                  
                              switch (diff.fDirection) {
                              case RangeDifference.RIGHT:
                              case RangeDifference.LEFT:
                                    s= fromDoc.get(fromStart, fromLen);
                                    break;
                              case RangeDifference.ANCESTOR:
                                    break;
                              case RangeDifference.CONFLICT:
                                    if (APPEND_CONFLICT) {
                                          s= toDoc.get(toStart, toLen);
                                          s+= fromDoc.get(fromStart, fromLen);
                                    } else
                                          s= fromDoc.get(fromStart, fromLen);
                                    break;
                              }
                              if (s != null) {
                                    toDoc.replace(toStart, toLen, s);                                 
                                    toPos.setOffset(toStart);
                                    toPos.setLength(s.length());
                              }     
                        
                        } catch (BadLocationException e) {
                              // silently ignored
                        }
                  }
            
                  diff.setResolved(true);
                  updateResolveStatus();
                  
                  return true;
            }
            return false;
      }

      //---- scrolling

      /*
       * Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs.
       */
      private int getVirtualHeight() {
            int h= 1;
            if (fAllDiffs != null) {
                  Iterator e= fAllDiffs.iterator();
                  for (int i= 0; e.hasNext(); i++) {
                        Diff diff= (Diff) e.next();
                        h+= diff.getMaxDiffHeight(fShowAncestor);
                  }
            }
            return h;
      }
      
      /*
       * Calculates height (in lines) of right view by adding the height of the right diffs.
       */
      private int getRightHeight() {
            int h= 1;
            if (fAllDiffs != null) {
                  Iterator e= fAllDiffs.iterator();
                  for (int i= 0; e.hasNext(); i++) {
                        Diff diff= (Diff) e.next();
                        h+= diff.getRightHeight();
                  }
            }
            return h;
      }
      
      /*
       * 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(MergeSourceViewer w, int vpos) {

            if (! fSynchronizedScrolling || fAllDiffs == null)
                  return vpos;
                        
            int viewPos= 0;         // real view position
            int virtualPos= 0;      // virtual position
            Point region= new Point(0, 0);
            
            Iterator e= fAllDiffs.iterator();
            while (e.hasNext()) {
                  Diff diff= (Diff) e.next();
                  Position pos= diff.getPosition(w);
                  w.getLineRange(pos, region);
                  int realHeight= region.y;
                  int virtualHeight= diff.getMaxDiffHeight(fShowAncestor);
                  if (vpos <= viewPos + realHeight) { // OK, found!
                        vpos-= viewPos;   // make relative to this slot
                        // now scale position within this slot to virtual slot
                        if (realHeight <= 0)
                              vpos= 0;
                        else
                              vpos= (vpos*virtualHeight)/realHeight;
                        return virtualPos+vpos;
                  }
                  viewPos+= realHeight;
                  virtualPos+= virtualHeight;
            }
            return virtualPos;
      }
            
      private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) {
                                    
            int s= 0;
            
            if (fSynchronizedScrolling) {
                  s= 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(fAncestor, avpos+s)-s;
                        fAncestor.vscroll(y);
                  }
            }

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

            if (allBut != fRight) {
                  if (fSynchronizedScrolling || allBut == null) {
                        int y= virtualToRealPosition(fRight, rvpos+s)-s;
                        fRight.vscroll(y);
                  }
            }
            
            fInScrolling= false;
            
            if (isThreeWay() && fAncestorCanvas != null)
                  fAncestorCanvas.repaint();
            
            if (fLeftCanvas != null)
                  fLeftCanvas.repaint();
            
            Control center= getCenter();
            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(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, getVirtualHeight() - getViewportHeight()));
                  fVScrollBar.setSelection(value);
                  //refreshBirdEyeView();
            }
      }

      /**
       */
      private void updateVScrollBar() {
            
            if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) {
                  int virtualHeight= 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(MergeSourceViewer part, int v) {
                  
            if (! fSynchronizedScrolling || fAllDiffs == null)
                  return v;
                              
            int virtualPos= 0;
            int viewPos= 0;
            Point region= new Point(0, 0);
            
            Iterator e= fAllDiffs.iterator();
            while (e.hasNext()) {
                  Diff diff= (Diff) e.next();
                  Position pos= diff.getPosition(part);
                  int viewHeight= part.getLineRange(pos, region).y;
                  int virtualHeight= diff.getMaxDiffHeight(fShowAncestor);
                  if (v < (virtualPos + virtualHeight)) {
                        v-= virtualPos;         // make relative to this slot
                        if (viewHeight <= 0) {
                              v= 0;
                        } else {
                              v= (v*viewHeight)/virtualHeight;
                        }
                        return viewPos+v;
                  }
                  virtualPos+= virtualHeight;
                  viewPos+= viewHeight;
            }
            return viewPos;
      }
}

Generated by  Doxygen 1.6.0   Back to index