blob: 6d0fee4f7c240560d9cfc4bd2ef74df0010df565 [file] [log] [blame]
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
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.io.InputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.lang.InterruptedException;
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.FontData;
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.text.*;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.compare.*;
import org.eclipse.compare.internal.MergeSourceViewer;
import org.eclipse.compare.internal.BufferedCanvas;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.TokenComparator;
import org.eclipse.compare.internal.ChangePropertyAction;
import org.eclipse.compare.internal.CompareEditor;
import org.eclipse.compare.internal.DocLineComparator;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.MergeViewerAction;
import org.eclipse.compare.internal.INavigatable;
import org.eclipse.compare.internal.CompareNavigator;
import org.eclipse.compare.internal.TimeoutContext;
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
*/
public class TextMergeViewer extends ContentMergeViewer {
private static final boolean DEBUG= false;
private static final String[] GLOBAL_ACTIONS= {
IWorkbenchActionConstants.UNDO,
IWorkbenchActionConstants.REDO,
IWorkbenchActionConstants.CUT,
IWorkbenchActionConstants.COPY,
IWorkbenchActionConstants.PASTE,
IWorkbenchActionConstants.DELETE,
IWorkbenchActionConstants.SELECT_ALL,
IWorkbenchActionConstants.SAVE
};
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 SYNC_SCROLLING= "SYNC_SCROLLING"; //$NON-NLS-1$
private static final String BUNDLE_NAME= "org.eclipse.compare.contentmergeviewer.TextMergeViewerResources"; //$NON-NLS-1$
// constants
/** Width of left and right vertical bar */
private static final int MARGIN_WIDTH= 6;
/** Width of center bar */
private static final int CENTER_WIDTH= 34;
/** Width of birds eye view */
private static final int BIRDS_EYE_VIEW_WIDTH= 10;
/** Width of birds eye view */
private static final int BIRDS_EYE_VIEW_INSET= 1;
/** line width of change borders */
private static final int LW= 1;
/** Provide more merge controls in CompareViewerPane toolbar */
private static final boolean USE_MORE_CONTROLS= true;
/** Selects between smartTokenDiff and mergingTokenDiff */
private static final boolean USE_MERGING_TOKEN_DIFF= false;
/** if DEAD_STEP is true navigation with the next/previous buttons needs an extra step
when wrapping around the beginning or end */
private static final boolean DEAD_STEP= false;
/** When calculating differences show Progress after this timeout (in milliseconds) */
private static final int TIMEOUT= 2000;
// 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 to use
private static final RGB INCOMING= new RGB(100, 100, 200);
private static final RGB INCOMING_FILL= new RGB(230, 230, 240);
private static final RGB SELECTED_INCOMING= new RGB(0, 0, 255);
private static final RGB SELECTED_INCOMING_FILL= new RGB(255, 255, 255);
private static final RGB CONFLICT= new RGB(200, 100, 100);
private static final RGB CONFLICT_FILL= new RGB(240, 230, 230);
private static final RGB SELECTED_CONFLICT= new RGB(255, 0, 0);
private static final RGB SELECTED_CONFLICT_FILL= new RGB(255, 255, 255);
private static final RGB OUTGOING= new RGB(100, 100, 100);
private static final RGB OUTGOING_FILL= new RGB(230, 230, 230);
private static final RGB SELECTED_OUTGOING= new RGB(0, 0, 0);
private static final RGB SELECTED_OUTGOING_FILL= new RGB(255, 255, 255);
// private static final RGB INCOMING= new RGB(230, 230, 240);
// private static final RGB INCOMING_FILL= new RGB(250, 250, 255);
// private static final RGB SELECTED_INCOMING= new RGB(0, 0, 255);
// private static final RGB SELECTED_INCOMING_FILL= new RGB(255, 255, 255);
//
// private static final RGB CONFLICT= new RGB(240, 230, 230);
// private static final RGB CONFLICT_FILL= new RGB(255, 250, 250);
// private static final RGB SELECTED_CONFLICT= new RGB(255, 0, 0);
// private static final RGB SELECTED_CONFLICT_FILL= new RGB(255, 255, 255);
//
// private static final RGB OUTGOING= new RGB(230, 230, 230);
// private static final RGB OUTGOING_FILL= new RGB(250, 250, 250);
// private static final RGB SELECTED_OUTGOING= new RGB(0, 0, 0);
// private static final RGB SELECTED_OUTGOING_FILL= new RGB(255, 255, 255);
private IDocumentListener fDocumentListener;
private IPreferenceStore fPreferenceStore;
private IPropertyChangeListener fPreferenceChangeListener;
/** All diffs for calculating scrolling position (includes line ranges without changes) */
private ArrayList fAllDiffs;
/** Subset of above: just real differences. */
private ArrayList fChangeDiffs;
/** The current diff */
private Diff fCurrentDiff;
private HashMap fNewAncestorRanges= new HashMap();
private HashMap fNewLeftRanges= new HashMap();
private HashMap fNewRightRanges= new HashMap();
private Object fAncestorInput;
private Object fLeftInput;
private Object fRightInput;
private MergeSourceViewer fAncestor;
private MergeSourceViewer fLeft;
private MergeSourceViewer fRight;
private int fLeftLineCount;
private int fRightLineCount;
private boolean fLeftContentsChanged;
private boolean fRightContentsChanged;
private boolean fInScrolling;
private int fPts[]= new int[8]; // scratch area for polygon drawing
private boolean fIgnoreAncestor= false;
private ActionContributionItem fIgnoreAncestorItem;
private boolean fHiglightRanges;
private boolean fShowPseudoConflicts= false;
private ActionContributionItem fNextItem; // goto next difference
private ActionContributionItem fPreviousItem; // goto previous difference
private ActionContributionItem fCopyDiffLeftToRightItem;
private ActionContributionItem fCopyDiffRightToLeftItem;
private boolean fSynchronizedScrolling= true;
private boolean fShowMoreInfo= false;
private MergeSourceViewer fFocusPart;
private boolean fSubDoc= true;
private IPositionUpdater fPositionUpdater;
private boolean fIsMotif;
// SWT widgets
private BufferedCanvas fAncestorCanvas;
private BufferedCanvas fLeftCanvas;
private BufferedCanvas fRightCanvas;
private Canvas fScrollCanvas;
private ScrollBar fVScrollBar;
private Canvas fBirdsEyeCanvas;
// SWT resources to be disposed
private Map fColors;
private Font fFont;
private Color fBackgroundColor;
private Cursor fBirdsEyeCursor;
/**
* 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.getString("TextMergeViewer.changeType.addition"); //$NON-NLS-1$
if (leftEmpty && !rightEmpty)
return CompareMessages.getString("TextMergeViewer.changeType.deletion"); //$NON-NLS-1$
} else {
if (leftEmpty && !rightEmpty)
return CompareMessages.getString("TextMergeViewer.changeType.addition"); //$NON-NLS-1$
if (!leftEmpty && rightEmpty)
return CompareMessages.getString("TextMergeViewer.changeType.deletion"); //$NON-NLS-1$
}
return CompareMessages.getString("TextMergeViewer.changeType.change"); //$NON-NLS-1$
}
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) {
//System.out.println("Diff.createPosition: " + start + " " + l);
}
try {
doc.addPosition(IDocumentRange.RANGE_CATEGORY, p);
} catch (BadPositionCategoryException ex) {
}
return p;
} catch (BadLocationException ee) {
//System.out.println("Diff.createPosition: " + start + " " + end);
}
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;
}
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 contains(MergeSourceViewer w, int start, int end) {
Position h= getPosition(w);
if (h != null) {
int offset= h.getOffset();
if (start >= offset) {
int endPos= offset+h.getLength();
if (end < endPos)
return true;
if (endPos == w.getDocument().getLength())
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
*/
public TextMergeViewer(Composite parent, CompareConfiguration configuration) {
this(parent, SWT.NULL, configuration);
}
/**
* Creates a text merge viewer under the given parent control.
*
* @param parent the parent control
* @param style SWT style bits for top level composite of this viewer
* @param configuration the configuration object
*/
public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
String platform= SWT.getPlatform();
fIsMotif= "motif".equals(platform); //$NON-NLS-1$
if (fIsMotif)
fMarginWidth= 0;
fPreferenceStore= configuration.getPreferenceStore();
if (fPreferenceStore != null) {
fPreferenceChangeListener= new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
TextMergeViewer.this.propertyChange(event);
}
};
fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
Display display= parent.getDisplay();
updateFont(fPreferenceStore, display);
updateBackgroundColor(fPreferenceStore, 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);
}
fDocumentListener= new IDocumentListener() {
public void documentAboutToBeChanged(DocumentEvent e) {
}
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);
}
private void updateFont(IPreferenceStore ps, Display display) {
Font oldFont= fFont;
FontData fontData= null;
if (ps.contains(ComparePreferencePage.TEXT_FONT)
&& !ps.isDefault(ComparePreferencePage.TEXT_FONT))
fontData= PreferenceConverter.getFontData(ps, ComparePreferencePage.TEXT_FONT);
else
fontData= PreferenceConverter.getDefaultFontData(ps, ComparePreferencePage.TEXT_FONT);
if (fontData != null) {
fFont= new Font(display, fontData);
if (fAncestor != null)
fAncestor.setFont(fFont);
if (fLeft != null)
fLeft.setFont(fFont);
if (fRight != null)
fRight.setFont(fFont);
if (oldFont != null)
oldFont.dispose();
}
}
private void updateBackgroundColor(IPreferenceStore ps, Display display) {
Color color= null;
if (ps.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT))
color= null;
else
color= createColor(ps, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, display);
if (fAncestor != null)
fAncestor.setBackgroundColor(color);
if (fLeft != null)
fLeft.setBackgroundColor(color);
if (fRight != null)
fRight.setBackgroundColor(color);
if (fBackgroundColor != null)
fBackgroundColor.dispose();
fBackgroundColor= color;
}
/**
* Creates a color from the information stored in the given preference store.
* Returns <code>null</code> if there is no such information available.
*/
private static Color createColor(IPreferenceStore store, String key, Display display) {
RGB rgb= null;
if (store.contains(key)) {
if (store.isDefault(key))
rgb= PreferenceConverter.getDefaultColor(store, key);
else
rgb= PreferenceConverter.getColor(store, key);
if (rgb != null)
return new Color(display, rgb);
}
return null;
}
/**
* 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
*/
protected void configureTextViewer(TextViewer textViewer) {
}
/**
* 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.
*
* @return a ITokenComparator which is used for a second level token compare.
*/
protected ITokenComparator createTokenComparator(String s) {
return new TokenComparator(s);
}
/**
* 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>
*/
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.
*/
protected void handleDispose(DisposeEvent event) {
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) {
if (fPreferenceStore != null)
fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
fPreferenceChangeListener= null;
}
fLeftCanvas= null;
fRightCanvas= null;
fVScrollBar= null;
fBirdsEyeCanvas= 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();
}
}
if (fFont != null) {
fFont.dispose();
fFont= null;
}
if (fBackgroundColor != null) {
fBackgroundColor.dispose();
fBackgroundColor= 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.
*/
protected void createControls(Composite composite) {
// 1st row
if (fMarginWidth > 0) {
fAncestorCanvas= new BufferedCanvas(composite, SWT.NONE) {
public void doPaint(GC gc) {
paintSides(gc, fAncestor, fAncestorCanvas, false);
}
};
}
fAncestor= createPart(composite);
fAncestor.setEditable(false);
// 2nd row
if (fMarginWidth > 0) {
fLeftCanvas= new BufferedCanvas(composite, SWT.NONE) {
public void doPaint(GC gc) {
paintSides(gc, fLeft, fLeftCanvas, 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);
if (fMarginWidth > 0) {
fRightCanvas= new BufferedCanvas(composite, SWT.NONE) {
public void doPaint(GC gc) {
paintSides(gc, fRight, fRightCanvas, fSynchronizedScrolling);
}
};
}
fScrollCanvas= new Canvas(composite, SWT.V_SCROLL);
Rectangle trim= fScrollCanvas.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);
}
}
);
fBirdsEyeCanvas= new BufferedCanvas(composite, SWT.NONE) {
public void doPaint(GC gc) {
paintBirdsEyeView(this, gc);
}
};
fBirdsEyeCanvas.addMouseListener(
new MouseAdapter() {
public void mouseDown(MouseEvent e) {
Diff diff= handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y);
if (diff != null && diff.fDirection != Differencer.NO_CHANGE) {
//fCurrentDiff= null;
setCurrentDiff(diff, 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 != Differencer.NO_CHANGE)
cursor= fBirdsEyeCursor;
if (fLastCursor != cursor) {
fBirdsEyeCanvas.setCursor(cursor);
fLastCursor= cursor;
}
}
}
);
}
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;
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(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(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) {
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
}
/**
* 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);
}
};
CompareNavigator.hookNavigation(canvas);
new Resizer(canvas, HORIZONTAL);
return canvas;
}
return super.createCenter(parent);
}
/**
* 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);
CompareNavigator.handleNavigationKeys(e);
}
}
);
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);
}
}
);
if (fFont != null)
te.setFont(fFont);
if (fBackgroundColor != null)
te.setBackground(fBackgroundColor);
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 (i == 7)
i= 7;
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;
}
boolean sameDoc(char type, Object newInput, Object oldInput) {
IDocument newDoc= getDocument2(type, newInput);
IDocument oldDoc= getDocument2(type, oldInput);
if (newDoc == oldDoc)
return true;
if (newDoc == null || oldDoc == null)
return true;
return false;
}
/**
* Overridden to prevent save confirmation if new input is sub document of current input.
*/
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.
*/
protected void updateContent(Object ancestor, Object left, Object right) {
boolean emptyInput= (ancestor == null && left == null && right == null);
int n= 0;
if (left != null)
n++;
if (right != null)
n++;
fHiglightRanges= n > 1;
// clear stuff
fCurrentDiff= null;
fChangeDiffs= null;
fAllDiffs= null;
fLeftContentsChanged= false;
fRightContentsChanged= false;
CompareConfiguration cc= getCompareConfiguration();
IMergeViewerContentProvider cp= getMergeContentProvider();
boolean rightEditable= cc.isRightEditable() && cp.isRightEditable(getInput());
boolean leftEditable= cc.isLeftEditable() && cp.isLeftEditable(getInput());
fRight.setEditable(rightEditable);
fLeft.setEditable(leftEditable);
// set new documents
fLeftInput= left;
setDocument(fLeft, 'L', left);
fLeftLineCount= fLeft.getLineCount();
fRightInput= right;
setDocument(fRight, 'R', right);
fRightLineCount= fRight.getLineCount();
fAncestorInput= ancestor;
setDocument(fAncestor, 'A', ancestor);
doDiff();
invalidateLines();
updateVScrollBar();
refreshBirdsEyeView();
if (!emptyInput && !fComposite.isDisposed()) {
// delay so that StyledText widget gets a chance to resize itself
// (otherwise selectFirstDiff would not know its visible area)
fComposite.getDisplay().asyncExec(
new Runnable() {
public void run() {
selectFirstDiff();
}
}
);
}
}
private void updateDiffBackground(Diff diff) {
if (! fHiglightRanges)
return;
if (diff == null || diff.fIsToken)
return;
if (fShowCurrentOnly && !isCurrentDiff(diff))
return;
Color c= getColor(getFillColor(diff));
if (c == null)
return;
if (isThreeWay())
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 whenver 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()) {
fLeftContentsChanged= true;
setLeftDirty(true);
} else if (doc == fRight.getDocument()) {
setRightDirty(true);
fRightContentsChanged= true;
}
updateLines(doc);
}
private static ITypedElement getLeg(ICompareInput input, char type) {
switch (type) {
case 'A':
return input.getAncestor();
case 'L':
return input.getLeft();
case 'R':
return input.getRight();
}
return null;
}
private int findInsertionPosition(char type) {
Object in= getInput();
if (in instanceof ICompareInput) {
ITypedElement other= null;
ICompareInput input= (ICompareInput) in;
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;
}
/**
* 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.getContents());
} catch (CoreException ex) {
}
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) {
ITypedElement parent= getParent(type);
if (parent instanceof IDocumentRange) {
newDoc= ((IDocumentRange)parent).getDocument();
newDoc.addPositionCategory(IDocumentRange.RANGE_CATEGORY);
Object input= getInput();
range= getNewRange(type, input);
if (range == null) {
range= new Position(findInsertionPosition(type), 0);
try {
newDoc.addPosition(IDocumentRange.RANGE_CATEGORY, range);
} catch (BadPositionCategoryException ex) {
if (DEBUG) System.out.println("BadPositionCategoryException: " + ex); //$NON-NLS-1$
} catch (BadLocationException ex) {
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);
newDoc.addDocumentListener(fDocumentListener);
}
} 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) {
// deinstall old positions
if (fPositionUpdater != null)
oldDoc.removePositionUpdater(fPositionUpdater);
try {
oldDoc.removePositionCategory(IDocumentRange.RANGE_CATEGORY);
} catch (BadPositionCategoryException ex) {
}
oldDoc.removeDocumentListener(fDocumentListener);
}
}
/**
* Returns the contents of the underlying document as an array of bytes.
*
* @param left if <code>true</code> the contents of the left side is returned; otherwise the right side
* @return the contents of the left or right document or null
*/
protected byte[] getContents(boolean left) {
MergeSourceViewer v= left ? fLeft : fRight;
if (v != null) {
IDocument d= v.getDocument();
if (d != null) {
String contents= d.get();
if (contents != null)
return contents.getBytes();
}
}
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);
}
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.
*/
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;
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)
scrollbarWidth= fScrollCanvas.computeTrim(0, 0, 0, 0).width;
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) {
y+= scrollbarHeight;
fBirdsEyeCanvas.setBounds(x+scrollbarWidth, y, BIRDS_EYE_VIEW_WIDTH, height-(3*scrollbarHeight));
}
// 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.
*/
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()) { // cancelled
throw new InterruptedException();
}
monitor.done();
}
};
RangeDifference[] e= null;
try {
TimeoutContext.run(true, TIMEOUT, getControl().getShell(), 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(TIMEOUT/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()) { // cancelled
throw new InterruptedException();
}
monitor.done();
}
};
RangeDifference[] e= null;
try {
TimeoutContext.run(true, TIMEOUT, getControl().getShell(), 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(TIMEOUT/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) {
}
}
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);
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) {
}
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) {
}
}
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);
if (fCurrentDiff != null) {
IMergeViewerContentProvider cp= getMergeContentProvider();
if (cp != null) {
rightToLeft= cp.isLeftEditable(getInput());
leftToRight= cp.isRightEditable(getInput());
}
}
if (fDirectionLabel != null) {
if (fHiglightRanges && 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 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.getString("TextMergeViewer.diffDescription.noDiff.format"); //$NON-NLS-1$
} else {
if (diff.fIsToken) // we don't show special info for token diffs
diff= diff.fParent;
String format= CompareMessages.getString("TextMergeViewer.diffDescription.diff.format"); //$NON-NLS-1$
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.getString("TextMergeViewer.statusLine.format"); //$NON-NLS-1$
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.getString("TextMergeViewer.direction.outgoing"); //$NON-NLS-1$
break;
case RangeDifference.RIGHT:
s= CompareMessages.getString("TextMergeViewer.direction.incoming"); //$NON-NLS-1$
break;
case RangeDifference.CONFLICT:
s= CompareMessages.getString("TextMergeViewer.direction.conflicting"); //$NON-NLS-1$
break;
}
String format= CompareMessages.getString("TextMergeViewer.diffType.format"); //$NON-NLS-1$
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.getString("TextMergeViewer.beforeLine.format"); //$NON-NLS-1$
else
format= CompareMessages.getString("TextMergeViewer.range.format"); //$NON-NLS-1$
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.getString("TextMergeViewer.cursorPosition.format"); //$NON-NLS-1$
return MessageFormat.format(format,
new String[] { Integer.toString(line + 1), Integer.toString(column + 1) } );
} catch (BadLocationException x) {
}
}
}
return ""; //$NON-NLS-1$
}
protected void updateHeader() {
super.updateHeader();
updateControls();
}
/**
* Creates the two items for copying a difference range from one side to the other
* and adds them to the given toolbar manager.
*/
protected void createToolItems(ToolBarManager tbm) {
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$
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$
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);
tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$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);
tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$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.TEXT_FONT)) {
if (fPreferenceStore != null) {
updateFont(fPreferenceStore, fComposite.getDisplay());
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 if (key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)
|| key.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)) {
if (fPreferenceStore != null) {
updateBackgroundColor(fPreferenceStore, fComposite.getDisplay());
}
} 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);
}
protected void updateToolItems() {
if (fIgnoreAncestorItem != null)
fIgnoreAncestorItem.setVisible(isThreeWay());
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.isControlOkToUse())
fAncestor.getTextWidget().redraw();
}
if (Utilities.okToUse(fLeftCanvas))
fLeftCanvas.redraw();
if (fLeft.isControlOkToUse())
fLeft.getTextWidget().redraw();
if (Utilities.okToUse(getCenter()))
getCenter().redraw();
if (fRight.isControlOkToUse())
fRight.getTextWidget().redraw();
if (Utilities.okToUse(fRightCanvas))
fRightCanvas.redraw();
}
private void paintCenter(Canvas canvas, GC g) {
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(canvas.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
g.fillRectangle(0, 0, 1, size.y);
g.fillRectangle(w-1, 0, 1, size.y);
}
if (! fHiglightRanges)
return;
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;
g.setBackground(getColor(getFillColor(diff)));
g.fillPolygon(fPts);
g.setLineWidth(LW);
g.setForeground(getColor(getStrokeColor(diff)));
g.drawLine(fPts[0], fPts[1], fPts[2], fPts[3]);
g.drawLine(fPts[6], fPts[7], fPts[4], fPts[5]);
}
}
}
private void paintSides(GC g, MergeSourceViewer tp, Canvas canvas, boolean right) {
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(canvas.getDisplay().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 (! fHiglightRanges)
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(getFillColor(diff)));
if (right)
g.fillRectangle(x, y, w2, h);
else
g.fillRectangle(x+w2, y, w2, h);
g.setBackground(getColor(getStrokeColor(diff)));
if (right) {
g.fillRectangle(x, y-1, w2+1, LW);
g.fillRectangle(x+w2, y, LW, h);
g.fillRectangle(x, y+h-1, w2, LW);
} else {
g.fillRectangle(x+w2, y-1, w2, LW);
g.fillRectangle(x+w2, y, LW, h);
g.fillRectangle(x+w2, y+h-1, w2, LW);
}
}
}
}
private void paint(PaintEvent event, MergeSourceViewer tp) {
if (! fHiglightRanges)
return;
if (fChangeDiffs == null)
return;
Control canvas= (Control) event.widget;
GC g= event.gc;
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(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;
if (isThreeWay() && !fIgnoreAncestor) {
switch (diff.fDirection) {
case RangeDifference.RIGHT:
if (fLeftIsLocal)
return selected ? SELECTED_INCOMING_FILL : INCOMING_FILL;
return selected ? SELECTED_OUTGOING_FILL : OUTGOING_FILL;
case RangeDifference.ANCESTOR:
return selected ? SELECTED_CONFLICT_FILL : CONFLICT_FILL;
case RangeDifference.LEFT:
if (fLeftIsLocal)
return selected ? SELECTED_OUTGOING_FILL : OUTGOING_FILL;
return selected ? SELECTED_INCOMING_FILL : INCOMING_FILL;
case RangeDifference.CONFLICT:
return selected ? SELECTED_CONFLICT_FILL : CONFLICT_FILL;
}
return null;
}
return selected ? SELECTED_OUTGOING_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(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(fComposite.getDisplay(), rgb);
fColors.put(rgb, c);
}
return c;
}
//---- Navigating and resolving Diffs
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) {
if (wrap) {
Control c= getControl();
if (Utilities.okToUse(c))
c.getDisplay().beep();
if (DEAD_STEP)
return true;
if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
if (down)
diff= (Diff) fChangeDiffs.get(0);
else
diff= (Diff) fChangeDiffs.get(fChangeDiffs.size()-1);
}
} else
return true;
}
setCurrentDiff(diff, true);
if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
&& !getAncestorEnabled())
continue;
break;
}
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.contains(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;
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);
}
}
//--------------------------------------------------------------------------------
protected void copy(boolean leftToRight) {
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);
fRightContentsChanged= false;
} 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);
fLeftContentsChanged= false;
}
doDiff();
invalidateLines();
updateVScrollBar();
selectFirstDiff();
refreshBirdsEyeView();
}
private void copyDiffLeftToRight() {
copy(fCurrentDiff, true, false, false);
}
private void copyDiffRightToLeft() {
copy(fCurrentDiff, false, false, false);
}
private void copy(Diff diff, boolean leftToRight, boolean both, boolean gotoNext) {
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:
s= fromDoc.get(fromStart, fromLen);
if (both)
s+= toDoc.get(toStart, toLen);
break;
}
if (s != null) {
toDoc.replace(toStart, toLen, s);
toPos.setOffset(toStart);
toPos.setLength(s.length());
}
} catch (BadLocationException e) {
}
}
diff.setResolved(true);
if (gotoNext) {
navigate(true, true, true);
} else {
revealDiff(diff, true);
updateControls();
}
}
}
//---- 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;
}
}