Bug 213780 - Compare With direction should be configurable

add a preference to swap the left and right side of the merge viewer.
This fix just swaps the left and right input for display. The model
values for left and right are not changed.

Change-Id: I4aa47e2e0389a0f1e8745e89b964296eb0b67678
Signed-off-by: Conrad Groth <info@conrad-groth.de>
diff --git a/bundles/org.eclipse.compare/META-INF/MANIFEST.MF b/bundles/org.eclipse.compare/META-INF/MANIFEST.MF
index ae2e278..b5b0c15 100644
--- a/bundles/org.eclipse.compare/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.compare/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.compare; singleton:=true
-Bundle-Version: 3.6.100.qualifier
+Bundle-Version: 3.7.0.qualifier
 Bundle-Activator: org.eclipse.compare.internal.CompareUIPlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java
index 5620ae2..7a5ef8b 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/CompareConfiguration.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -8,6 +8,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Alex Blewitt <alex.blewitt@gmail.com> - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344
+ *     Conrad Groth - Bug 213780 - Compare With direction should be configurable
  *******************************************************************************/
 package org.eclipse.compare;
 
@@ -42,7 +43,7 @@
  * A <code>CompareConfiguration</code> object
  * controls various UI aspects of compare/merge viewers like
  * title labels and images, or whether a side of a merge viewer is editable.
- * In addition to these fixed properties <code>ICompareConfiguration</code> provides
+ * In addition to these fixed properties <code>CompareConfiguration</code> provides
  * API for an open ended set of properties. Different viewers which share the same
  * configuration can communicate via this mechanism. E.g. if a compare editor
  * has a button for controlling whether compare viewers ignore white space,
@@ -70,38 +71,46 @@
 	 * @since 3.0
 	 */
 	public static final String USE_OUTLINE_VIEW= "USE_OUTLINE_VIEW"; //$NON-NLS-1$
-	
-	private static ImageDescriptor[] fgImages= new ImageDescriptor[16];
-	private static boolean fLeftIsLocal= true;
+	/**
+	 * Name of the mirrored property, i.e. if left input is shown on the right side and vice versa.
+	 * @since 3.7
+	 */
+	public static final String MIRRORED = "MIRRORED"; //$NON-NLS-1$
+
+	private static ImageDescriptor[] fgImages= new ImageDescriptor[32];
 
 	static {
-		if (fLeftIsLocal) {
-			fgImages[Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_inadd_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outadd_ov.gif"); //$NON-NLS-1$
+		// Not swapped (a.k.a. left is local)
+		fgImages[Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_inadd_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outadd_ov.gif"); //$NON-NLS-1$
 
-			fgImages[Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_indel_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outdel_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_indel_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/r_outdel_ov.gif"); //$NON-NLS-1$
 
-			fgImages[Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_inchg_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_outchg_ov.gif"); //$NON-NLS-1$
-		} else {
-			fgImages[Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/inadd_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/outadd_ov.gif"); //$NON-NLS-1$
-
-			fgImages[Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/indel_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/outdel_ov.gif"); //$NON-NLS-1$
-
-			fgImages[Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/inchg_ov.gif"); //$NON-NLS-1$
-			fgImages[Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/outchg_ov.gif"); //$NON-NLS-1$
-		}
+		fgImages[Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_inchg_ov.gif"); //$NON-NLS-1$
+		fgImages[Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/r_outchg_ov.gif"); //$NON-NLS-1$
 
 		fgImages[Differencer.CONFLICTING + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/confadd_ov.gif"); //$NON-NLS-1$
 		fgImages[Differencer.CONFLICTING + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/confdel_ov.gif"); //$NON-NLS-1$
 		fgImages[Differencer.CONFLICTING + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/confchg_ov.gif"); //$NON-NLS-1$
+	
+		// Mirrored (a.k.a. right is local)
+		fgImages[16 + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/add_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.LEFT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/inadd_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.RIGHT + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/outadd_ov.gif"); //$NON-NLS-1$
+
+		fgImages[16 + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/del_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.LEFT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/indel_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.RIGHT + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/outdel_ov.gif"); //$NON-NLS-1$
+
+		fgImages[16 + Differencer.LEFT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/inchg_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.RIGHT + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/outchg_ov.gif"); //$NON-NLS-1$
+
+		fgImages[16 + Differencer.CONFLICTING + Differencer.ADDITION]= CompareUIPlugin.getImageDescriptor("ovr16/confadd_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.CONFLICTING + Differencer.DELETION]= CompareUIPlugin.getImageDescriptor("ovr16/confdel_ov.gif"); //$NON-NLS-1$
+		fgImages[16 + Differencer.CONFLICTING + Differencer.CHANGE]= CompareUIPlugin.getImageDescriptor("ovr16/confchg_ov.gif"); //$NON-NLS-1$
 	}
 
 	private IPreferenceStore fPreferenceStore;
@@ -271,25 +280,25 @@
 	 * suitable default labels, and no images.
 	 * The given preference store is used to connect this configuration
 	 * with the Compare preference page properties <code>ComparePreferencePage.INITIALLY_SHOW_ANCESTOR_PANE</code>,
-	 * and <code>CompareConfiguration.IGNORE_WHITESPACE</code>.
+	 * <code>CompareConfiguration.IGNORE_WHITESPACE</code> and {@link #MIRRORED}
 	 * 
 	 * @param prefStore the preference store which this configuration holds onto.
 	 * @since 2.0
 	 */
 	public CompareConfiguration(IPreferenceStore prefStore) {
-		
-		setProperty("LEFT_IS_LOCAL", Boolean.valueOf(fLeftIsLocal)); //$NON-NLS-1$
-		
 		fPreferenceStore= prefStore;
 		if (fPreferenceStore != null) {
 			boolean b= fPreferenceStore.getBoolean(ComparePreferencePage.INITIALLY_SHOW_ANCESTOR_PANE);
 			setProperty(ICompareUIConstants.PROP_ANCESTOR_VISIBLE, Boolean.valueOf(b));
-			
+
 			b= fPreferenceStore.getBoolean(ComparePreferencePage.IGNORE_WHITESPACE);
 			setProperty(CompareConfiguration.IGNORE_WHITESPACE, Boolean.valueOf(b));
+
+			b= fPreferenceStore.getBoolean(ComparePreferencePage.SWAPPED);
+			setProperty(CompareConfiguration.MIRRORED, Boolean.valueOf(b));
 		}
-	}	
-	
+	}
+
 	/**
 	 * Creates a new configuration with editable left and right sides,
 	 * suitable default labels, and no images.
@@ -323,7 +332,7 @@
 	public Image getImage(int kind) {
 		if (fDisposed)
 			return null;
-		ImageDescriptor id= fgImages[kind & 15];
+		ImageDescriptor id= getImageDescriptor(kind);
 		ResourceManager rm = getResourceManager();
 		return rm.createImage(id);
 	}
@@ -351,11 +360,25 @@
 	public Image getImage(Image base, int kind) {
 		if (fDisposed)
 			return null;
-		kind &= 15;
-		ImageDescriptor id = new DiffImageDescriptor(base, fgImages[kind], ICompareUIConstants.COMPARE_IMAGE_WIDTH, !fLeftIsLocal);
+		ImageDescriptor id = new DiffImageDescriptor(base, getImageDescriptor(kind), ICompareUIConstants.COMPARE_IMAGE_WIDTH, false);
 		ResourceManager rm = getResourceManager();
 		return rm.createImage(id);
 	}
+
+	/**
+	 * <b>Only the views are mirrored. All model values for left and right are not changed!</b>
+	 * 
+	 * @return true if the left and right side of the viewer are mirrored. Default is false.
+	 * @since 3.7
+	 */
+	public boolean isMirrored() {
+		Object property = getProperty(MIRRORED);
+		return property instanceof Boolean && (Boolean) property;
+	}
+
+	private ImageDescriptor getImageDescriptor(int kind) {
+		return fgImages[(kind & 15) + (isMirrored() ? 16 : 0)];
+	}
 	
 	/**
 	 * Dispose of this compare configuration.
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java
index 55b4d5b..cde614c 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/ContentMergeViewer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -9,10 +9,12 @@
  *     IBM Corporation - initial API and implementation
  *     Alex Blewitt <alex.blewitt@gmail.com> - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344
  *     Stefan Xenos <sxenos@gmail.com> (Google) - bug 448968 - Add diagnostic logging
+ *     Conrad Groth - Bug 213780 - Compare With direction should be configurable
  *******************************************************************************/
 
 package org.eclipse.compare.contentmergeviewer;
 
+import java.io.IOException;
 import java.util.ResourceBundle;
 
 import org.eclipse.compare.CompareConfiguration;
@@ -26,11 +28,13 @@
 import org.eclipse.compare.internal.CompareEditor;
 import org.eclipse.compare.internal.CompareHandlerService;
 import org.eclipse.compare.internal.CompareMessages;
+import org.eclipse.compare.internal.ComparePreferencePage;
 import org.eclipse.compare.internal.CompareUIPlugin;
 import org.eclipse.compare.internal.ICompareUIConstants;
 import org.eclipse.compare.internal.IFlushable2;
 import org.eclipse.compare.internal.ISavingSaveable;
 import org.eclipse.compare.internal.MergeViewerContentProvider;
+import org.eclipse.compare.internal.MirroredMergeViewerContentProvider;
 import org.eclipse.compare.internal.Policy;
 import org.eclipse.compare.internal.Utilities;
 import org.eclipse.compare.internal.ViewerSwitchingCancelled;
@@ -49,6 +53,8 @@
 import org.eclipse.jface.action.ToolBarManager;
 import org.eclipse.jface.dialogs.IDialogConstants;
 import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.IPersistentPreferenceStore;
+import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.ContentViewer;
@@ -321,14 +327,17 @@
 	private boolean fAncestorVisible;	// whether the ancestor pane is visible
 	private ActionContributionItem fAncestorItem;
 	
-	private Action fCopyLeftToRightAction;	// copy from left to right
-	private Action fCopyRightToLeftAction;	// copy from right to left
+	private ActionContributionItem copyLeftToRightItem;	// copy from left to right
+	private ActionContributionItem copyRightToLeftItem;	// copy from right to left
 
 	private boolean fIsLeftDirty;
 	private boolean fIsRightDirty;
 
 	private CompareHandlerService fHandlerService;
 
+	private final MergeViewerContentProvider fDefaultContentProvider;
+	private Action fSwitchLeftAndRight;
+
 	// SWT widgets
 	/* package */ Composite fComposite;
 	private CLabel fAncestorLabel;
@@ -382,31 +391,16 @@
 		
 		fAncestorVisible= Utilities.getBoolean(cc, ICompareUIConstants.PROP_ANCESTOR_VISIBLE, fAncestorVisible);
 		fConfirmSave= Utilities.getBoolean(cc, CompareEditor.CONFIRM_SAVE_PROPERTY, fConfirmSave);
-
-		setContentProvider(new MergeViewerContentProvider(cc));
 		
-		fCompareInputChangeListener= new ICompareInputChangeListener() {
-			@Override
-			public void compareInputChanged(ICompareInput input) {
-				if (input == getInput()) {
-					handleCompareInputChange();
-				}
-			}
-		};
+		fCompareInputChangeListener = (input) -> { if (input == getInput()) handleCompareInputChange(); };
 		
 		// Make sure the compare configuration is not null
-		if (cc == null) {
-			fCompareConfiguration = new CompareConfiguration();
-		} else {
-			fCompareConfiguration= cc;
-			fPropertyChangeListener= new IPropertyChangeListener() {
-				@Override
-				public void propertyChange(PropertyChangeEvent event) {
-					ContentMergeViewer.this.handlePropertyChangeEvent(event);
-				}
-			};
-		}
+		fCompareConfiguration = cc != null ? cc : new CompareConfiguration();
+		fPropertyChangeListener = (event) -> handlePropertyChangeEvent(event);
 		fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener);
+
+		fDefaultContentProvider = new MergeViewerContentProvider(fCompareConfiguration);
+		updateContentProvider();
 		
 		fIsLeftDirty = false;
 		fIsRightDirty = false;
@@ -523,10 +517,9 @@
 	}
 	
 	/**
-	 * Returns the compare configuration of this viewer,
-	 * or <code>null</code> if this viewer does not yet have a configuration.
+	 * Returns the compare configuration of this viewer.
 	 *
-	 * @return the compare configuration, or <code>null</code> if none
+	 * @return the compare configuration, never <code>null</code>
 	 */
 	protected CompareConfiguration getCompareConfiguration() {
 		return fCompareConfiguration;
@@ -544,6 +537,12 @@
 		super.setContentProvider(contentProvider);
 	}
 
+	private void updateContentProvider() {
+		setContentProvider(getCompareConfiguration().isMirrored()
+				? new MirroredMergeViewerContentProvider(getCompareConfiguration(), fDefaultContentProvider)
+				: fDefaultContentProvider);
+	}
+
 	/* package */ IMergeViewerContentProvider getMergeContentProvider() {
 		return (IMergeViewerContentProvider) getContentProvider();
 	}
@@ -597,6 +596,13 @@
 			setAncestorVisibility(false, !Utilities.getBoolean(getCompareConfiguration(), ICompareUIConstants.PROP_IGNORE_ANCESTOR, false));
 			return;
 		}
+
+		if (key.equals(ComparePreferencePage.SWAPPED)) {
+			getCompareConfiguration().setProperty(CompareConfiguration.MIRRORED, event.getNewValue());
+			updateContentProvider();
+			updateToolItems();
+			return;
+		}
 	}
 	
 	void updateCursor(Control c, int dir) {
@@ -903,34 +909,33 @@
 			tbm.add(new Separator("modes"));	//$NON-NLS-1$
 			tbm.add(new Separator("merge"));	//$NON-NLS-1$
 			tbm.add(new Separator("navigation"));	//$NON-NLS-1$
-			
-			CompareConfiguration cc= getCompareConfiguration();
-		
-			if (cc.isRightEditable()) {
-				fCopyLeftToRightAction=
-					new Action() {
-						@Override
-						public void run() {
-							copy(true);
+
+			copyLeftToRightItem= createCopyAction(true);
+			Utilities.initAction(copyLeftToRightItem.getAction(), getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$
+			tbm.appendToGroup("merge", copyLeftToRightItem); //$NON-NLS-1$
+			fHandlerService.registerAction(copyLeftToRightItem.getAction(), "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$
+
+			copyRightToLeftItem= createCopyAction(false);
+			Utilities.initAction(copyRightToLeftItem.getAction(), getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$
+			tbm.appendToGroup("merge", copyRightToLeftItem); //$NON-NLS-1$
+			fHandlerService.registerAction(copyRightToLeftItem.getAction(), "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$
+
+			fSwitchLeftAndRight = new Action() {
+				@Override
+				public void run() {
+					IPreferenceStore preferences = getCompareConfiguration().getPreferenceStore();
+					preferences.setValue(ComparePreferencePage.SWAPPED, !getCompareConfiguration().isMirrored());
+					if (preferences instanceof IPersistentPreferenceStore) {
+						try {
+							((IPersistentPreferenceStore) preferences).save();
+						} catch (IOException e) {
+							CompareUIPlugin.log(e);
 						}
-					};
-				Utilities.initAction(fCopyLeftToRightAction, getResourceBundle(), "action.CopyLeftToRight."); //$NON-NLS-1$
-				tbm.appendToGroup("merge", fCopyLeftToRightAction); //$NON-NLS-1$
-				fHandlerService.registerAction(fCopyLeftToRightAction, "org.eclipse.compare.copyAllLeftToRight"); //$NON-NLS-1$
-			}
-			
-			if (cc.isLeftEditable()) {
-				fCopyRightToLeftAction=
-					new Action() {
-						@Override
-						public void run() {
-							copy(false);
-						}
-					};
-				Utilities.initAction(fCopyRightToLeftAction, getResourceBundle(), "action.CopyRightToLeft."); //$NON-NLS-1$
-				tbm.appendToGroup("merge", fCopyRightToLeftAction); //$NON-NLS-1$
-				fHandlerService.registerAction(fCopyRightToLeftAction, "org.eclipse.compare.copyAllRightToLeft"); //$NON-NLS-1$
-			}
+					}
+				}
+			};
+			Utilities.initAction(fSwitchLeftAndRight, getResourceBundle(), "action.SwitchLeftAndRight."); //$NON-NLS-1$
+			tbm.appendToGroup("modes", fSwitchLeftAndRight); //$NON-NLS-1$
 			
 			final ChangePropertyAction a= new ChangePropertyAction(fBundle, getCompareConfiguration(), "action.EnableAncestor.", ICompareUIConstants.PROP_ANCESTOR_VISIBLE); //$NON-NLS-1$
 			a.setChecked(fAncestorVisible);
@@ -938,14 +943,23 @@
 			fAncestorItem.setVisible(false);
 			tbm.appendToGroup("modes", fAncestorItem); //$NON-NLS-1$
 			tbm.getControl().addDisposeListener(a);
-			
+
 			createToolItems(tbm);
 			updateToolItems();
 			
 			tbm.update(true);
 		}
 	}
-	
+
+	private ActionContributionItem createCopyAction(boolean leftToRight) {
+		return new ActionContributionItem(new Action() {
+			@Override
+			public void run() {
+				copy(leftToRight);
+			}
+		});
+	}
+
 	/**
 	 * Callback that is invoked when the control of this merge viewer is given focus.
 	 * This method should return <code>true</code> if a particular widget was given focus
@@ -1089,24 +1103,20 @@
 		
 		Object input= getInput();
 		
-		if (fCopyLeftToRightAction != null) {
-			boolean enable= content.isRightEditable(input);
-//			if (enable && input instanceof ICompareInput) {
-//				ITypedElement e= ((ICompareInput) input).getLeft();
-//				if (e == null)
-//					enable= false;
-//			}
-			fCopyLeftToRightAction.setEnabled(enable);
+		if (copyLeftToRightItem != null) {
+			boolean rightEditable = content.isRightEditable(input);
+			copyLeftToRightItem.setVisible(rightEditable);
+			copyLeftToRightItem.getAction().setEnabled(rightEditable);
 		}
 		
-		if (fCopyRightToLeftAction != null) {
-			boolean enable= content.isLeftEditable(input);
-//			if (enable && input instanceof ICompareInput) {
-//				ITypedElement e= ((ICompareInput) input).getRight();
-//				if (e == null)
-//					enable= false;
-//			}
-			fCopyRightToLeftAction.setEnabled(enable);
+		if (copyRightToLeftItem != null) {
+			boolean leftEditable = content.isLeftEditable(input);
+			copyRightToLeftItem.setVisible(leftEditable);
+			copyRightToLeftItem.getAction().setEnabled(leftEditable);
+		}
+
+		if (fSwitchLeftAndRight != null) {
+			fSwitchLeftAndRight.setChecked(getCompareConfiguration().isMirrored());
 		}
 	}
 	
@@ -1417,4 +1427,23 @@
 		return false;
 	} 
 
+	/**
+	 * If the inputs are mirrored, this asks the right model value.
+	 * 
+	 * @return true if the left viewer is editable.
+	 * @since 3.7
+	 */
+	protected boolean isLeftEditable() {
+		return fCompareConfiguration.isMirrored() ? fCompareConfiguration.isRightEditable() : fCompareConfiguration.isLeftEditable();
+	}
+
+	/**
+	 * If the inputs are mirrored, this asks the right model value.
+	 * 
+	 * @return true if the left viewer is editable.
+	 * @since 3.7
+	 */
+	protected boolean isRightEditable() {
+		return fCompareConfiguration.isMirrored() ? fCompareConfiguration.isLeftEditable() : fCompareConfiguration.isRightEditable();
+	}
 }
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
index 1096ed5..21b5faf 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -316,8 +316,6 @@
 	/** line width of change borders */
 	private static final int LW= 1;
 		
-	// determines whether a change between left and right is considered incoming or outgoing
-	private boolean fLeftIsLocal;
 	private boolean fShowCurrentOnly= false;
 	private boolean fShowCurrentOnly2= false;
 	private int fMarginWidth= MARGIN_WIDTH;
@@ -375,7 +373,6 @@
 	
 	private boolean fUseSplines= true;
 	private boolean fUseSingleLine= true;
-	private boolean fUseResolveUI= true;
 	private boolean fHighlightTokenChanges = false;
 
 	private String fSymbolicFontName;
@@ -774,15 +771,19 @@
 
 			// install new document
 			tp.setRegion(range);
-			if (this.fViewer.fSubDoc) {
-				if (range != null) {
+			SourceViewer sourceViewer = tp.getSourceViewer();
+			sourceViewer.setRedraw(false);
+			try {
+				if (this.fViewer.fSubDoc && range != null) {
 					IRegion r= this.fViewer.normalizeDocumentRegion(document, TextMergeViewer.toRegion(range));
-					tp.getSourceViewer().setDocument(document, r.getOffset(), r.getLength());
-				} else
-					tp.getSourceViewer().setDocument(document);
-			} else
-				tp.getSourceViewer().setDocument(document);
-							
+					sourceViewer.setDocument(document, r.getOffset(), r.getLength());
+				} else {
+					sourceViewer.setDocument(document);
+				}
+			} finally {
+				sourceViewer.setRedraw(true);
+			}
+
 			tp.rememberDocument(document);
 		}
 
@@ -1320,17 +1321,12 @@
 			if (isThreeWay() && !isIgnoreAncestor()) {
 				switch (diff.getKind()) {
 				case RangeDifference.RIGHT:
-					if (fLeftIsLocal)
-						return INCOMING_TEXT_FILL;
-					return OUTGOING_TEXT_FILL;
+					return getCompareConfiguration().isMirrored() ? OUTGOING_TEXT_FILL : INCOMING_TEXT_FILL;
 				case RangeDifference.ANCESTOR:
-					return CONFLICT_TEXT_FILL;
-				case RangeDifference.LEFT:
-					if (fLeftIsLocal)
-						return OUTGOING_TEXT_FILL;
-					return INCOMING_TEXT_FILL;
 				case RangeDifference.CONFLICT:
 					return CONFLICT_TEXT_FILL;
+				case RangeDifference.LEFT:
+					return getCompareConfiguration().isMirrored() ? INCOMING_TEXT_FILL : OUTGOING_TEXT_FILL;
 				default:
 					return null;
 				}
@@ -1607,7 +1603,6 @@
 		if (fPreferenceStore != null) {
 			fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
 			
-			fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
 			fSynchronizedScrolling= fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING);
 			fShowPseudoConflicts= fPreferenceStore.getBoolean(ComparePreferencePage.SHOW_PSEUDO_CONFLICTS);
 			//fUseSplines= fPreferenceStore.getBoolean(ComparePreferencePage.USE_SPLINES);
@@ -1663,7 +1658,7 @@
 	}
 
 	private ChainedPreferenceStore createChainedPreferenceStore() {
-    	ArrayList<IPreferenceStore> stores= new ArrayList<IPreferenceStore>(2);
+    	List<IPreferenceStore> stores= new ArrayList<>(2);
 		stores.add(getCompareConfiguration().getPreferenceStore());
 		stores.add(EditorsUI.getPreferenceStore());
 		return new ChainedPreferenceStore(stores.toArray(new IPreferenceStore[stores.size()]));
@@ -2456,46 +2451,42 @@
 					paintCenter(this, gc);
 				}
 			};
-			if (fUseResolveUI) {
-				new HoverResizer(canvas, HORIZONTAL);
+			new HoverResizer(canvas, HORIZONTAL);
 
-				if (fNormalCursor == null) fNormalCursor= new Cursor(canvas.getDisplay(), SWT.CURSOR_ARROW);
-				int style= fIsMac ? SWT.FLAT : SWT.PUSH;
+			if (fNormalCursor == null) fNormalCursor= new Cursor(canvas.getDisplay(), SWT.CURSOR_ARROW);
+			int style= fIsMac ? SWT.FLAT : SWT.PUSH;
 
-				fLeftToRightButton= new Button(canvas, style);
-				fLeftToRightButton.setCursor(fNormalCursor);
-				fLeftToRightButton.setText(COPY_LEFT_TO_RIGHT_INDICATOR);
-				fLeftToRightButton.setToolTipText(
-						Utilities.getString(getResourceBundle(), "action.CopyDiffLeftToRight.tooltip")); //$NON-NLS-1$
-				fLeftToRightButton.pack();
-				fLeftToRightButton.setVisible(false);
-				fLeftToRightButton.addSelectionListener(
+			fLeftToRightButton= new Button(canvas, style);
+			fLeftToRightButton.setCursor(fNormalCursor);
+			fLeftToRightButton.setText(COPY_LEFT_TO_RIGHT_INDICATOR);
+			fLeftToRightButton.setToolTipText(
+					Utilities.getString(getResourceBundle(), "action.CopyDiffLeftToRight.tooltip")); //$NON-NLS-1$
+			fLeftToRightButton.pack();
+			fLeftToRightButton.setVisible(false);
+			fLeftToRightButton.addSelectionListener(
+				new SelectionAdapter() {
+					@Override
+					public void widgetSelected(SelectionEvent e) {
+						handleCenterButtonSelection(true);
+					}
+				}
+			);
+
+			fRightToLeftButton= new Button(canvas, style);
+			fRightToLeftButton.setCursor(fNormalCursor);
+			fRightToLeftButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR);
+			fRightToLeftButton.setToolTipText(
+					Utilities.getString(getResourceBundle(), "action.CopyDiffRightToLeft.tooltip")); //$NON-NLS-1$
+			fRightToLeftButton.pack();
+			fRightToLeftButton.setVisible(false);
+			fRightToLeftButton.addSelectionListener(
 					new SelectionAdapter() {
 						@Override
 						public void widgetSelected(SelectionEvent e) {
-							handleCenterButtonSelection(true);
+							handleCenterButtonSelection(false);
 						}
 					}
-				);
-
-				fRightToLeftButton= new Button(canvas, style);
-				fRightToLeftButton.setCursor(fNormalCursor);
-				fRightToLeftButton.setText(COPY_RIGHT_TO_LEFT_INDICATOR);
-				fRightToLeftButton.setToolTipText(
-						Utilities.getString(getResourceBundle(), "action.CopyDiffRightToLeft.tooltip")); //$NON-NLS-1$
-				fRightToLeftButton.pack();
-				fRightToLeftButton.setVisible(false);
-				fRightToLeftButton.addSelectionListener(
-						new SelectionAdapter() {
-							@Override
-							public void widgetSelected(SelectionEvent e) {
-								handleCenterButtonSelection(false);
-							}
-						}
-				);
-			} else {
-				new Resizer(canvas, HORIZONTAL);
-			}
+			);
 			
 			return canvas;
 		}
@@ -2514,8 +2505,6 @@
 	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) {
 				fButtonDiff= diff;
@@ -2987,24 +2976,18 @@
 				        	    rightRange= ((IDocumentRange)right).getRange();
 					    
 					    ancestor= ci.getAncestor();
-					    left= ci.getLeft();
-					    right= ci.getRight();
+					    left= getCompareConfiguration().isMirrored() ? ci.getRight() : ci.getLeft();
+					    right= getCompareConfiguration().isMirrored() ? ci.getLeft() : ci.getRight();
 				    }
 				}
 			}
 		}
 
-		int n= 0;
-		if (left != null)
-			n++;
-		if (right != null)
-			n++;
-		fHighlightRanges= n > 1;
+		fHighlightRanges= left != null && right != null;
 		
 		resetDiffs();
 		fHasErrors= false; // start with no errors
 		
-		CompareConfiguration cc= getCompareConfiguration();
 		IMergeViewerContentProvider cp= getMergeContentProvider();
 		
 		if (cp instanceof MergeViewerContentProvider) {
@@ -3042,26 +3025,20 @@
 
 		if (!isConfigured) {
 			configureSourceViewer(fAncestor.getSourceViewer(), false, null);
-			configureSourceViewer(fLeft.getSourceViewer(), cc.isLeftEditable() && cp.isLeftEditable(input), fLeftContributor);
-			configureSourceViewer(fRight.getSourceViewer(), cc.isRightEditable() && cp.isRightEditable(input), fRightContributor);
+			configureSourceViewer(fLeft.getSourceViewer(), isLeftEditable() && cp.isLeftEditable(input), fLeftContributor);
+			configureSourceViewer(fRight.getSourceViewer(), isRightEditable() && cp.isRightEditable(input), fRightContributor);
 			isConfigured = true; // configure once
 		} 
 
 		// set new documents
-		fLeftContributor.setDocument(fLeft, cc.isLeftEditable() && cp.isLeftEditable(input));
+		fLeftContributor.setDocument(fLeft, isLeftEditable() && cp.isLeftEditable(input));
 		fLeftLineCount= fLeft.getLineCount();
 		
-		fRightContributor.setDocument(fRight, cc.isRightEditable() && cp.isRightEditable(input));
+		fRightContributor.setDocument(fRight, isRightEditable() && cp.isRightEditable(input));
 		fRightLineCount= fRight.getLineCount();
 		
 		fAncestorContributor.setDocument(fAncestor, false);
 
-		//if the input is part of a patch hunk, toggle synchronized scrolling
-		/*if (isPatchHunk()){
-			setSyncScrolling(false);
-		} else {
-			setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING));
-		}*/
 		setSyncScrolling(fPreferenceStore.getBoolean(ComparePreferencePage.SYNCHRONIZE_SCROLLING));
 		
 		update(false);
@@ -3616,9 +3593,9 @@
 		}
 		
 		if (fCopyDiffLeftToRightItem != null)
-			((Action)fCopyDiffLeftToRightItem.getAction()).setEnabled(leftToRight);
+			fCopyDiffLeftToRightItem.getAction().setEnabled(leftToRight);
 		if (fCopyDiffRightToLeftItem != null)
-			((Action)fCopyDiffRightToLeftItem.getAction()).setEnabled(rightToLeft);
+			fCopyDiffRightToLeftItem.getAction().setEnabled(rightToLeft);
 
 		if (fNextDiff != null) {
 			IAction a = fNextDiff.getAction();
@@ -3660,15 +3637,12 @@
 		
 		if (showResolveUI()) {
 			// we only show red or green if there is at least one incoming or conflicting change
-			int incomingOrConflicting= 0;
 			int unresolvedIncoming= 0;
 			int unresolvedConflicting= 0;
 
 			if (fMerger.hasChanges()) {
 				for (Iterator<?> iterator = fMerger.changesIterator(); iterator	.hasNext();) {
 					Diff d = (Diff) iterator.next();
-					if (d.isIncomingOrConflicting() /* && useChange(d.fDirection) && !d.fIsWhitespace */) {
-						incomingOrConflicting++;
 						if (!d.isResolved()) {
 							if (d.getKind() == RangeDifference.CONFLICT) {
 								unresolvedConflicting++;
@@ -3676,18 +3650,15 @@
 							}
 							unresolvedIncoming++;
 						}
-					}
 				}
 			}
 		
-			if (incomingOrConflicting > 0) {
-				if (unresolvedConflicting > 0)
-					rgb= SELECTED_CONFLICT;
-				else if (unresolvedIncoming > 0)
-					rgb= SELECTED_INCOMING;
-				else
-					rgb= RESOLVED;
-			}
+			if (unresolvedConflicting > 0)
+				rgb= SELECTED_CONFLICT;
+			else if (unresolvedIncoming > 0)
+				rgb= SELECTED_INCOMING;
+			else
+				rgb= RESOLVED;
 		}
 		
 		if (fHeaderPainter.setColor(rgb))
@@ -3728,10 +3699,14 @@
 		String s= ""; 	//$NON-NLS-1$
 		switch(diff.getKind()) {
 		case RangeDifference.LEFT:
-			s= CompareMessages.TextMergeViewer_direction_outgoing;
+			s= getCompareConfiguration().isMirrored() ?
+					CompareMessages.TextMergeViewer_direction_incoming :
+					CompareMessages.TextMergeViewer_direction_outgoing;
 			break;
 		case RangeDifference.RIGHT:
-			s= CompareMessages.TextMergeViewer_direction_incoming;
+			s= getCompareConfiguration().isMirrored() ?
+					CompareMessages.TextMergeViewer_direction_outgoing :
+					CompareMessages.TextMergeViewer_direction_incoming;
 			break;
 		case RangeDifference.CONFLICT:
 			s= CompareMessages.TextMergeViewer_direction_conflicting;
@@ -3896,35 +3871,29 @@
 		tbm.appendToGroup("navigation", fPreviousChange); //$NON-NLS-1$
 		fHandlerService.registerAction(a, "org.eclipse.compare.selectPreviousChange");	//$NON-NLS-1$
 
-		CompareConfiguration cc= getCompareConfiguration();
+		a= new Action() {
+			@Override
+			public void run() {
+				copyDiffLeftToRight();
+			}
+		};
+		Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
+		fCopyDiffLeftToRightItem= new ActionContributionItem(a);
+		fCopyDiffLeftToRightItem.setVisible(isRightEditable());
+		tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
+		fHandlerService.registerAction(a, "org.eclipse.compare.copyLeftToRight");	//$NON-NLS-1$
 		
-		if (cc.isRightEditable()) {
-			a= new Action() {
-				@Override
-				public void run() {
-					copyDiffLeftToRight();
-				}
-			};
-			Utilities.initAction(a, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
-			fCopyDiffLeftToRightItem= new ActionContributionItem(a);
-			fCopyDiffLeftToRightItem.setVisible(true);
-			tbm.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
-			fHandlerService.registerAction(a, "org.eclipse.compare.copyLeftToRight");	//$NON-NLS-1$
-		}
-		
-		if (cc.isLeftEditable()) {
-			a= new Action() {
-				@Override
-				public void run() {
-					copyDiffRightToLeft();
-				}
-			};
-			Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
-			fCopyDiffRightToLeftItem= new ActionContributionItem(a);
-			fCopyDiffRightToLeftItem.setVisible(true);
-			tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
-			fHandlerService.registerAction(a, "org.eclipse.compare.copyRightToLeft");	//$NON-NLS-1$
-		}
+		a= new Action() {
+			@Override
+			public void run() {
+				copyDiffRightToLeft();
+			}
+		};
+		Utilities.initAction(a, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
+		fCopyDiffRightToLeftItem= new ActionContributionItem(a);
+		fCopyDiffRightToLeftItem.setVisible(isLeftEditable());
+		tbm.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
+		fHandlerService.registerAction(a, "org.eclipse.compare.copyRightToLeft");	//$NON-NLS-1$
 		
 		fIgnoreWhitespace= ChangePropertyAction.createIgnoreWhiteSpaceAction(getResourceBundle(), getCompareConfiguration());
 		fIgnoreWhitespace.setActionDefinitionId(ICompareUIConstants.COMMAND_IGNORE_WHITESPACE);
@@ -4213,6 +4182,13 @@
 					
 	@Override
 	protected void updateToolItems() {
+		if (fCopyDiffLeftToRightItem != null) {
+			fCopyDiffLeftToRightItem.setVisible(isRightEditable());
+		}
+		if (fCopyDiffRightToLeftItem != null) {
+			fCopyDiffRightToLeftItem.setVisible(isLeftEditable());
+		}
+
 		//only update toolbar items if diffs need to be calculated (which
 		//dictates whether a toolbar gets added at all)
 		if (!isPatchHunk()){
@@ -4229,9 +4205,8 @@
 				if (a != null)
 					a.setEnabled(a.isEnabled() && !fHasErrors);
 			}
-			
-			super.updateToolItems();
 		}
+		super.updateToolItems();
 	}
 	
 	//---- painting lines
@@ -4295,18 +4270,14 @@
 	}
 	
 	private boolean showResolveUI() {
-		if (!fUseResolveUI || !isThreeWay() || isIgnoreAncestor())
+		if (!isThreeWay() || isIgnoreAncestor())
 			return false;
 		return isAnySideEditable();
 	}
 
 	private boolean isAnySideEditable() {
-		CompareConfiguration cc= getCompareConfiguration();
 		// we only enable the new resolve UI if exactly one side is editable
-		boolean l= cc.isLeftEditable();
-		boolean r= cc.isRightEditable();
-		//return (l && !r) || (r && !l);
-		return l || r;
+		return isLeftEditable() || isRightEditable();
 	}
 	
 	private void paintCenter(Canvas canvas, GC g) {
@@ -4339,8 +4310,6 @@
 		if (! fHighlightRanges)
 			return;
 
-		boolean isAnySideEditable= isAnySideEditable();
-
 		if (fMerger.hasChanges()) {
 			int lshift= fLeft.getVerticalScrollOffset();
 			int rshift= fRight.getVerticalScrollOffset();
@@ -4423,7 +4392,7 @@
 					}
 				}
 				
-				if (fUseSingleLine && isAnySideEditable && diff.isUnresolvedIncomingOrConflicting()) {
+				if (fUseSingleLine && isAnySideEditable()) {
 					// draw resolve state
 					int cx= (w-RESOLVE_SIZE)/2;
 					int cy= ((ly+lh/2) + (ry+rh/2) - RESOLVE_SIZE)/2;
@@ -4577,17 +4546,16 @@
 		if (isThreeWay() && !isIgnoreAncestor()) {
 			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
-				if (fLeftIsLocal)
+				if (!getCompareConfiguration().isMirrored())
 					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;
+			case RangeDifference.LEFT:
+				if (!getCompareConfiguration().isMirrored())
+					return selected ? selected_fill : OUTGOING_FILL;
+				return selected ? selected_fill : INCOMING_FILL;
 			default:
 				return null;
 			}
@@ -4601,17 +4569,16 @@
 		if (isThreeWay() && !isIgnoreAncestor()) {
 			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
-				if (fLeftIsLocal)
+				if (!getCompareConfiguration().isMirrored())
 					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;
+			case RangeDifference.LEFT:
+				if (!getCompareConfiguration().isMirrored())
+					return selected ? SELECTED_OUTGOING : OUTGOING;
+				return selected ? SELECTED_INCOMING : INCOMING;
 			default:
 				return null;
 			}
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties
index 9fef099..281d9e5 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewerResources.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2011 IBM Corporation and others.
+# Copyright (c) 2000, 2016 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
@@ -73,6 +73,9 @@
 action.IgnoreAncestor.description.checked=Three-Way Compare
 action.IgnoreAncestor.image=twowaycompare_co.gif
 
+action.SwitchLeftAndRight.label=Swap Left and Right View
+action.SwitchLeftAndRight.tooltip=Swap Left and Right View
+action.SwitchLeftAndRight.image=switch.png
 
 #####################################################
 # Context menu actions
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java
index 57fe155..54becc3 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/BinaryCompareViewer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -41,10 +41,10 @@
 	private static final int EOF = -1;
 	private ICompareInput fInput;
 	private ResourceBundle fBundle;
-	private boolean fLeftIsLocal;
 
 	private Composite fComposite;
 	private Label fMessage;
+	private CompareConfiguration compareConfiguration;
 	
 	public BinaryCompareViewer(Composite parent, final CompareConfiguration cc) {
 		
@@ -60,19 +60,20 @@
 		fMessage= new Label(fComposite, SWT.WRAP);
 		fComposite.setData(CompareUI.COMPARE_VIEWER_TITLE, Utilities.getString(fBundle, "title")); //$NON-NLS-1$
 		
-		fLeftIsLocal= Utilities.getBoolean(cc, "LEFT_IS_LOCAL", false); //$NON-NLS-1$
-		
-		if (cc != null && cc.getContainer() instanceof CompareEditorInput) {
+		compareConfiguration = cc != null ? cc : new CompareConfiguration();
+
+		if (compareConfiguration.getContainer() instanceof CompareEditorInput) {
 			Label compareAsTextLabel = new Label(fComposite, SWT.WRAP);
-			compareAsTextLabel
-					.setText(Utilities.getString(fBundle, "compareAsText")); //$NON-NLS-1$
+			compareAsTextLabel.setText(Utilities.getString(fBundle, "compareAsText")); //$NON-NLS-1$
 		}
 	}
 
+	@Override
 	public Control getControl() {
 		return fComposite;
 	}
 
+	@Override
 	public void setInput(Object input) {
 		if (fComposite != null && input instanceof ICompareInput) {
 			fInput= (ICompareInput) input;
@@ -102,20 +103,13 @@
 				} else if (left == null && right == null) {
 					message= Utilities.getString(fBundle, "deleteConflictMessage"); //$NON-NLS-1$
 				} else if (left == null) {
-					if (fLeftIsLocal)
-						message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$
-					else
-						message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$
+					message= Utilities.getString(fBundle, compareConfiguration.isMirrored() ?
+							"addedMessage" : "deletedMessage"); //$NON-NLS-1$ //$NON-NLS-2$
 				} else if (right == null) {
-					if (fLeftIsLocal)
-						message= Utilities.getString(fBundle, "addedMessage"); //$NON-NLS-1$
-					else
-						message= Utilities.getString(fBundle, "deletedMessage"); //$NON-NLS-1$
+					message= Utilities.getString(fBundle, compareConfiguration.isMirrored() ?
+							"deletedMessage" : "addedMessage"); //$NON-NLS-1$ //$NON-NLS-2$
 				}
-			} catch (CoreException ex) {
-				message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$
-				CompareUIPlugin.log(ex);
-			} catch (IOException ex) {
+			} catch (CoreException | IOException ex) {
 				message = Utilities.getString(fBundle, "errorMessage"); //$NON-NLS-1$
 				CompareUIPlugin.log(ex);
 			} finally {
@@ -128,6 +122,7 @@
 		}
 	}
 
+	@Override
 	public Object getInput() {
 		return fInput;
 	}
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java
index 8fe2bee..cd262b4 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ComparePreferencePage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 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
@@ -8,13 +8,16 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Alex Blewitt <alex.blewitt@gmail.com> - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344
+ *     Conrad Groth - Bug 213780 - Compare With direction should be configurable
  *******************************************************************************/
 package org.eclipse.compare.internal;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import org.eclipse.compare.CompareConfiguration;
@@ -25,6 +28,7 @@
 import org.eclipse.compare.structuremergeviewer.DiffNode;
 import org.eclipse.compare.structuremergeviewer.Differencer;
 import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.preference.FieldEditor;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.preference.PreferencePage;
 import org.eclipse.jface.preference.RadioGroupFieldEditor;
@@ -98,13 +102,14 @@
 	public static final String PATH_FILTER= PREFIX + "PathFilter"; //$NON-NLS-1$
 	public static final String ADDED_LINES_REGEX= PREFIX + "AddedLinesRegex"; //$NON-NLS-1$
 	public static final String REMOVED_LINES_REGEX= PREFIX + "RemovedLinesRegex"; //$NON-NLS-1$
+	public static final String SWAPPED = PREFIX + "Swapped"; //$NON-NLS-1$
 	
-	
+
 	private TextMergeViewer fPreviewViewer;
 	private IPropertyChangeListener fPreferenceChangeListener;
 	private CompareConfiguration fCompareConfiguration;
 	private OverlayPreferenceStore fOverlayStore;
-	private Map fCheckBoxes= new HashMap();
+	private Map<Button, String> fCheckBoxes = new HashMap<>();
 	private Text fFilters;
 	private Text addedLinesRegex;
 	private Text removedLinesRegex;
@@ -129,8 +134,9 @@
 		new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, PATH_FILTER),
 		new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION),
 		new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL),
+		new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, SWAPPED),
 	};
-	private RadioGroupFieldEditor editor;
+	private List<FieldEditor> editors = new ArrayList<>();
 	private TabItem fTextCompareTab;
 	private Button fDisableCappingCheckBox;
 	
@@ -153,6 +159,7 @@
 		store.setDefault(PATH_FILTER, ""); //$NON-NLS-1$
 		store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, ICompareUIConstants.PREF_VALUE_PROMPT);
 		store.setDefault(ICompareUIConstants.PREF_NAVIGATION_END_ACTION_LOCAL, ICompareUIConstants.PREF_VALUE_LOOP);
+		store.setDefault(SWAPPED, false);
 	}
 
 	public ComparePreferencePage() {
@@ -190,7 +197,7 @@
 		fOverlayStore.setValue(ADDED_LINES_REGEX, addedLinesRegex.getText());
 		fOverlayStore.setValue(REMOVED_LINES_REGEX, removedLinesRegex.getText());
 
-		editor.store();
+		editors.forEach(editor -> editor.store());
 		fOverlayStore.propagate();
 
 		ComparePlugin.getDefault().setCappingDisabled(
@@ -206,7 +213,7 @@
 		
 		fOverlayStore.loadDefaults();
 		initializeFields();
-		
+
 		super.performDefaults();
 	}
 	
@@ -356,10 +363,13 @@
 		addCheckBox(composite, "ComparePreferencePage.highlightTokenChanges.label", HIGHLIGHT_TOKEN_CHANGES, 0);	//$NON-NLS-1$
 		//addCheckBox(composite, "ComparePreferencePage.useResolveUI.label", USE_RESOLVE_UI, 0);	//$NON-NLS-1$
 		fDisableCappingCheckBox = addCheckBox(composite, "ComparePreferencePage.disableCapping.label", CAPPING_DISABLED, 0);	//$NON-NLS-1$
+		addCheckBox(composite, "ComparePreferencePage.swapped.label", SWAPPED, 0);	//$NON-NLS-1$
 		
 		Composite radioGroup = new Composite(composite, SWT.NULL);
 		radioGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-		editor = new RadioGroupFieldEditor(ICompareUIConstants.PREF_NAVIGATION_END_ACTION, CompareMessages.ComparePreferencePage_0, 1,
+		RadioGroupFieldEditor editor = new RadioGroupFieldEditor(
+				ICompareUIConstants.PREF_NAVIGATION_END_ACTION,
+				CompareMessages.ComparePreferencePage_0, 1,
 				new String[][] {
 					new String[] { CompareMessages.ComparePreferencePage_1, ICompareUIConstants.PREF_VALUE_PROMPT },
 				    new String[] { CompareMessages.ComparePreferencePage_2, ICompareUIConstants.PREF_VALUE_LOOP },
@@ -369,6 +379,7 @@
 		radioGroup, true);
 		editor.setPreferenceStore(fOverlayStore);
 		editor.fillIntoGrid(radioGroup, 1);
+		editors.add(editor);
 		
 		// a spacer
 		Label separator= new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
@@ -427,10 +438,10 @@
 			
 	private void initializeFields() {
 		
-		Iterator e= fCheckBoxes.keySet().iterator();
+		Iterator<Button> e = fCheckBoxes.keySet().iterator();
 		while (e.hasNext()) {
-			Button b= (Button) e.next();
-			String key= (String) fCheckBoxes.get(b);
+			Button b = e.next();
+			String key= fCheckBoxes.get(b);
 			b.setSelection(fOverlayStore.getBoolean(key));
 		}
 		
@@ -441,7 +452,7 @@
 		if (removedLinesRegex != null)
 			removedLinesRegex.setText(fOverlayStore.getString(REMOVED_LINES_REGEX));
 		
-		editor.load();
+		editors.forEach(editor -> editor.load());
 	}
 
 	// overlay stuff
@@ -455,14 +466,13 @@
 		
 		GridData gd= new GridData(GridData.FILL_HORIZONTAL);
 		gd.horizontalIndent= indentation;
-		gd.horizontalSpan= 2;
 		checkBox.setLayoutData(gd);
 		
 		if (fCheckBoxListener == null) {
 			fCheckBoxListener= new SelectionAdapter() {
 				public void widgetSelected(SelectionEvent e) {
 					Button button= (Button) e.widget;
-					fOverlayStore.setValue((String) fCheckBoxes.get(button), button.getSelection());
+					fOverlayStore.setValue(fCheckBoxes.get(button), button.getSelection());
 				}
 			};
 		}
@@ -472,7 +482,7 @@
 		
 		return checkBox;
 	}
-	
+
 	private String loadPreviewContentFromFile(String key) {
 		
 		String preview= Utilities.getString(key);
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties
index 3e733b4..197855c 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/ImageMergeViewerResources.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2011 IBM Corporation and others.
+# Copyright (c) 2000, 2016 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
@@ -30,3 +30,7 @@
 action.EnableAncestor.label= Enable Ancestor Pane
 action.EnableAncestor.tooltip= Control Visibility of Ancestor Pane
 action.EnableAncestor.image= elcl16/ancestorpane_co.gif
+
+action.SwitchLeftAndRight.label=Swap Left and Right View
+action.SwitchLeftAndRight.tooltip=Swap Left and Right View
+action.SwitchLeftAndRight.image=switch.png
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MirroredMergeViewerContentProvider.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MirroredMergeViewerContentProvider.java
new file mode 100644
index 0000000..b3b7558
--- /dev/null
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/MirroredMergeViewerContentProvider.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Conrad Groth 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:
+ *     Conrad Groth - Bug 213780 - Compare With direction should be configurable
+ *******************************************************************************/
+package org.eclipse.compare.internal;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * This class just swaps the left and right input for display.
+ * The model values for left and right are not changed.
+ * We must extend from the class and not the interface, because some implementations expect the class.
+ */
+public class MirroredMergeViewerContentProvider extends MergeViewerContentProvider {
+	private MergeViewerContentProvider delegate;
+
+	public MirroredMergeViewerContentProvider(CompareConfiguration cc, MergeViewerContentProvider delegate) {
+		super(cc);
+		this.delegate = delegate;
+	}
+
+	@Override
+	public String getLeftLabel(Object input) {
+		return delegate.getRightLabel(input);
+	}
+
+	@Override
+	public Image getLeftImage(Object input) {
+		return delegate.getRightImage(input);
+	}
+
+	@Override
+	public Object getLeftContent(Object input) {
+		return delegate.getRightContent(input);
+	}
+
+	@Override
+	public boolean isLeftEditable(Object input) {
+		return delegate.isRightEditable(input);
+	}
+
+	@Override
+	public void saveLeftContent(Object input, byte[] bytes) {
+		delegate.saveRightContent(input, bytes);
+	}
+
+	@Override
+	public String getRightLabel(Object input) {
+		return delegate.getLeftLabel(input);
+	}
+
+	@Override
+	public Image getRightImage(Object input) {
+		return delegate.getLeftImage(input);
+	}
+
+	@Override
+	public Object getRightContent(Object input) {
+		return delegate.getLeftContent(input);
+	}
+
+	@Override
+	public boolean isRightEditable(Object input) {
+		return delegate.isLeftEditable(input);
+	}
+
+	@Override
+	public void saveRightContent(Object input, byte[] bytes) {
+		delegate.saveLeftContent(input, bytes);
+	}
+
+	@Override
+	public String getAncestorLabel(Object input) {
+		return delegate.getAncestorLabel(input);
+	}
+
+	@Override
+	public Image getAncestorImage(Object input) {
+		return delegate.getAncestorImage(input);
+	}
+
+	@Override
+	public Object getAncestorContent(Object input) {
+		return delegate.getAncestorContent(input);
+	}
+
+	@Override
+	public boolean showAncestor(Object input) {
+		return delegate.showAncestor(input);
+	}
+
+	@Override
+	public void setAncestorError(String errorMessage) {
+		delegate.setAncestorError(errorMessage);
+	}
+
+	@Override
+	public void setLeftError(String errorMessage) {
+		delegate.setLeftError(errorMessage);
+	}
+
+	@Override
+	public void setRightError(String errorMessage) {
+		delegate.setRightError(errorMessage);
+	}
+}
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
index 3f7938e..43738a7 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
@@ -74,8 +74,6 @@
 	/** Subset of above: just real differences. */
 	private ArrayList fChangeDiffs;
 	
-	private final boolean fLeftIsLocal;
-
 	private IDocumentMergerInput fInput;
 	
 	/**
@@ -120,7 +118,7 @@
 		int fDirection;
 		boolean fIsToken= false;
 		/** child token diffs */
-		ArrayList fDiffs;
+		List<Diff> fDiffs;
 		boolean fIsWhitespace= false;
 
 		/*
@@ -177,10 +175,10 @@
 			int code= Differencer.CHANGE;
 			switch (fDirection) {
 			case RangeDifference.RIGHT:
-				code+= Differencer.LEFT;
+				code+= getCompareConfiguration().isMirrored() ? Differencer.RIGHT : Differencer.LEFT;
 				break;
 			case RangeDifference.LEFT:
-				code+= Differencer.RIGHT;
+				code+= getCompareConfiguration().isMirrored() ? Differencer.LEFT : Differencer.RIGHT ;
 				break;
 			case RangeDifference.ANCESTOR:
 			case RangeDifference.CONFLICT:
@@ -226,7 +224,7 @@
 
 		void add(Diff d) {
 			if (fDiffs == null)
-				fDiffs= new ArrayList();
+				fDiffs= new ArrayList<>();
 			fDiffs.add(d);
 		}
 		
@@ -255,48 +253,6 @@
 			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;
-//		}
-		
-		public 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();
-//		}
-		
-		public boolean isUnresolvedIncomingOrConflicting() {
-			if (fResolved)
-				return false;
-			return isIncomingOrConflicting();
-		}
-				
 		Position getPosition(int contributor) {
 			if (contributor == MergeViewerContentProvider.LEFT_CONTRIBUTOR)
 				return fLeftPos;
@@ -392,7 +348,6 @@
 	
 	public DocumentMerger(IDocumentMergerInput input) {
 		this.fInput = input;
-		fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
 	}
 	
 	/**
@@ -1238,7 +1193,7 @@
 	public boolean isFirstChildDiff(char contributor, int childStart, Diff diff) {
 		if (!diff.hasChildren())
 			return false;
-		Diff d = (Diff)diff.fDiffs.get(0);
+		Diff d = diff.fDiffs.get(0);
 		Position p= d.getPosition(contributor);
 		return (p.getOffset() >= childStart);
 	}
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java
index 44d9c90..d5df14b 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/DiffTreeViewer.java
@@ -40,15 +40,15 @@
 public class DiffTreeViewer extends TreeViewer {
 	
 	class DiffViewerContentProvider implements ITreeContentProvider {
-			
+
 		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
 			// empty implementation
 		}
-	
+
 		public boolean isDeleted(Object element) {
 			return false;
 		}
-			
+
 		public void dispose() {
 			inputChanged(DiffTreeViewer.this, getInput(), null);
 		}
@@ -71,6 +71,7 @@
 			return new Object[0];
 		}
 		
+		@Override
 		public Object[] getElements(Object element) {
 			return getChildren(element);
 		}				
@@ -81,7 +82,7 @@
 	 * is true.
 	 */
 	class DiffViewerLabelProvider extends LabelProvider {
-		
+		@Override
 		public String getText(Object element) {
 			
 			if (element instanceof IDiffElement)
@@ -90,26 +91,42 @@
 			return Utilities.getString(fBundle, "defaultLabel"); //$NON-NLS-1$
 		}
 	
+		@Override
 		public Image getImage(Object element) {
 			if (element instanceof IDiffElement) {
 				IDiffElement input= (IDiffElement) element;
 				
 				int kind= input.getKind();
-				if (fLeftIsLocal) {
-					switch (kind & Differencer.DIRECTION_MASK) {
+				// Flip the direction and the change type, because all images are the other way round,
+				// i.e. for comparison from left to right.
+				switch (kind & Differencer.DIRECTION_MASK) {
 					case Differencer.LEFT:
 						kind= (kind &~ Differencer.LEFT) | Differencer.RIGHT;
 						break;
 					case Differencer.RIGHT:
 						kind= (kind &~ Differencer.RIGHT) | Differencer.LEFT;
 						break;
-					}
 				}
-				
+				switch (kind & Differencer.CHANGE_TYPE_MASK) {
+					case Differencer.ADDITION:
+						kind= (kind &~ Differencer.ADDITION) | Differencer.DELETION;
+						break;
+					case Differencer.DELETION:
+						kind= (kind &~ Differencer.DELETION) | Differencer.ADDITION;
+						break;
+				}
+
 				return fCompareConfiguration.getImage(input.getImage(), kind);
 			}
 			return null;
 		}
+		
+		/**
+		 * Informs the platform, that the images have changed.
+		 */
+		public void fireLabelProviderChanged() {
+			fireLabelProviderChanged(new LabelProviderChangedEvent(this));
+		}
 	}
 
 	static class FilterSame extends ViewerFilter {
@@ -125,11 +142,9 @@
 	
 	private ResourceBundle fBundle;
 	private CompareConfiguration fCompareConfiguration;
-	/* package */ boolean fLeftIsLocal;
 	private IPropertyChangeListener fPropertyChangeListener;
+	private DiffViewerLabelProvider diffViewerLabelProvider = new DiffViewerLabelProvider();
 
-	private Action fCopyLeftToRightAction;
-	private Action fCopyRightToLeftAction;
 	private Action fEmptyMenuAction;
 	private Action fExpandAllAction;
 		
@@ -182,9 +197,6 @@
 			}
 		};
 		tree.setData(INavigatable.NAVIGATOR_PROPERTY, nav);
-		
-		fLeftIsLocal= Utilities.getBoolean(configuration, "LEFT_IS_LOCAL", false); //$NON-NLS-1$
-
 		tree.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle());
 
 		Composite parent= tree.getParent();
@@ -194,25 +206,15 @@
 		// register for notification with the CompareConfiguration 
 		fCompareConfiguration= configuration;
 		if (fCompareConfiguration != null) {
-			fPropertyChangeListener= new IPropertyChangeListener() {
-				public void propertyChange(PropertyChangeEvent event) {
-					DiffTreeViewer.this.propertyChange(event);
-				}
-			};
+			fPropertyChangeListener = event -> propertyChange(event);
 			fCompareConfiguration.addPropertyChangeListener(fPropertyChangeListener);
 		}				
 	
 		setContentProvider(new DiffViewerContentProvider());
-		setLabelProvider(new DiffViewerLabelProvider());
-		
-		addSelectionChangedListener(
-			new ISelectionChangedListener() {
-				public void selectionChanged(SelectionChangedEvent se) {
-					updateActions();
-				}
-			}
-		);
-										
+		setLabelProvider(diffViewerLabelProvider);
+
+		addSelectionChangedListener(event -> updateActions());
+
 		setComparator(new DiffViewerComparator());
 		
 		ToolBarManager tbm= CompareViewerPane.getToolBarManager(parent);
@@ -301,12 +303,16 @@
 	/**
 	 * Tracks property changes of the configuration object.
 	 * Clients may extend to track their own property changes.
+	 * In this case they must call the inherited method.
 	 * @param event property change event that triggered call to this method
 	 */
 	protected void propertyChange(PropertyChangeEvent event) {
-		// empty default implementation
+		if (event.getProperty().equals(CompareConfiguration.MIRRORED)) {
+			diffViewerLabelProvider.fireLabelProviderChanged();
+		}
 	}
 	
+	@Override
 	protected void inputChanged(Object in, Object oldInput) {
 		super.inputChanged(in, oldInput);
 		
@@ -375,38 +381,6 @@
 	 * @param toolbarManager the toolbar manager for which to add the actions
 	 */
 	protected void createToolItems(ToolBarManager toolbarManager) {
-		
-//		fCopyLeftToRightAction= new Action() {
-//			public void run() {
-//				copySelected(true);
-//			}
-//		};
-//		Utilities.initAction(fCopyLeftToRightAction, fBundle, "action.TakeLeft.");
-//		toolbarManager.appendToGroup("merge", fCopyLeftToRightAction);
-
-//		fCopyRightToLeftAction= new Action() {
-//			public void run() {
-//				copySelected(false);
-//			}
-//		};
-//		Utilities.initAction(fCopyRightToLeftAction, fBundle, "action.TakeRight.");
-//		toolbarManager.appendToGroup("merge", fCopyRightToLeftAction);
-		
-//		fNextAction= new Action() {
-//			public void run() {
-//				navigate(true);
-//			}
-//		};
-//		Utilities.initAction(fNextAction, fBundle, "action.NextDiff."); //$NON-NLS-1$
-//		toolbarManager.appendToGroup("navigation", fNextAction); //$NON-NLS-1$
-
-//		fPreviousAction= new Action() {
-//			public void run() {
-//				navigate(false);
-//			}
-//		};
-//		Utilities.initAction(fPreviousAction, fBundle, "action.PrevDiff."); //$NON-NLS-1$
-//		toolbarManager.appendToGroup("navigation", fPreviousAction); //$NON-NLS-1$
 	}
 	
 	/**
@@ -442,13 +416,7 @@
 			}
 		}
 		fExpandAllAction.setEnabled(enable);
-
 		manager.add(fExpandAllAction);
-		
-		if (fCopyLeftToRightAction != null)
-			manager.add(fCopyLeftToRightAction);
-		if (fCopyRightToLeftAction != null)
-			manager.add(fCopyRightToLeftAction);
 	}
 
 	/**
@@ -669,48 +637,10 @@
 		}
 	}
 			
-	private final boolean isEditable(Object element, boolean left) {
-		if (element instanceof ICompareInput) {
-			ICompareInput diff= (ICompareInput) element;
-			Object side= left ? diff.getLeft() : diff.getRight();
-			if (side == null && diff instanceof IDiffElement) {
-				IDiffContainer container= ((IDiffElement)diff).getParent();
-				if (container instanceof ICompareInput) {
-					ICompareInput parent= (ICompareInput) container;
-					side= left ? parent.getLeft() : parent.getRight();
-				}
-			}
-			if (side instanceof IEditableContent)
-				return ((IEditableContent) side).isEditable();
-		}
-		return false;
-	}
-		
 	private void updateActions() {
-		int leftToRight= 0;
-		int rightToLeft= 0;
-		ISelection selection= getSelection();
-		if (selection instanceof IStructuredSelection) {
-			IStructuredSelection ss= (IStructuredSelection) selection;
-			Iterator e= ss.iterator();
-			while (e.hasNext()) {
-				Object element= e.next();
-				if (element instanceof ICompareInput) {
-					if (isEditable(element, false))
-						leftToRight++;
-					if (isEditable(element, true))
-						rightToLeft++;
-					if (leftToRight > 0 && rightToLeft > 0)
-						break;
-				}
-			}
-			if (fExpandAllAction != null)
-				fExpandAllAction.setEnabled(selection.isEmpty());
+		if (fExpandAllAction != null) {
+			fExpandAllAction.setEnabled(getSelection().isEmpty());
 		}
-		if (fCopyLeftToRightAction != null)
-			fCopyLeftToRightAction.setEnabled(leftToRight > 0);
-		if (fCopyRightToLeftAction != null)
-			fCopyRightToLeftAction.setEnabled(rightToLeft > 0);
 	}
 	
 	/*
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java
index b51e562..7df336d 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/structuremergeviewer/StructureDiffViewer.java
@@ -482,8 +482,8 @@
 						}
 						protected Object visit(Object data, int result, Object ancestor, Object left, Object right) {
 							Object o= super.visit(data, result, ancestor, left, right);
-							if (fLeftIsLocal && o instanceof DiffNode)
-								((DiffNode)o).swapSides(fLeftIsLocal);
+							if (!getCompareConfiguration().isMirrored() && o instanceof DiffNode)
+								((DiffNode)o).swapSides(true);
 							return o;
 						}
 					};
@@ -692,11 +692,8 @@
 		if (Display.getCurrent() != null)
 			runnable.run();
 		else
-			getControl().getDisplay().syncExec(new Runnable() {
-				public void run() {
-					if (!getControl().isDisposed())
-						runnable.run();
-				}
+			getControl().getDisplay().syncExec(() -> {
+				if (!getControl().isDisposed()) runnable.run();
 			});
 	}
 }
diff --git a/bundles/org.eclipse.compare/icons/full/dlcl16/switch.png b/bundles/org.eclipse.compare/icons/full/dlcl16/switch.png
new file mode 100644
index 0000000..f3132b9
--- /dev/null
+++ b/bundles/org.eclipse.compare/icons/full/dlcl16/switch.png
Binary files differ
diff --git a/bundles/org.eclipse.compare/icons/full/elcl16/switch.png b/bundles/org.eclipse.compare/icons/full/elcl16/switch.png
new file mode 100644
index 0000000..96cef47
--- /dev/null
+++ b/bundles/org.eclipse.compare/icons/full/elcl16/switch.png
Binary files differ
diff --git a/bundles/org.eclipse.compare/plugin.properties b/bundles/org.eclipse.compare/plugin.properties
index 9335310..d314944 100644
--- a/bundles/org.eclipse.compare/plugin.properties
+++ b/bundles/org.eclipse.compare/plugin.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2000, 2014 IBM Corporation and others.
+# Copyright (c) 2000, 2016 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
@@ -132,6 +132,7 @@
 ComparePreferencePage.useSingleLine.label= Connect &ranges with single line
 ComparePreferencePage.highlightTokenChanges.label=&Highlight individual changes
 ComparePreferencePage.disableCapping.label=Disable &capping when comparing large documents
+ComparePreferencePage.swapped.label=Swap sid&es
 
 ComparePreferencePage.preview.label= Previe&w:
 ComparePreferencePage.ancestor.label= Common Ancestor
diff --git a/bundles/org.eclipse.compare/pom.xml b/bundles/org.eclipse.compare/pom.xml
index 55ad2d4..0867488 100644
--- a/bundles/org.eclipse.compare/pom.xml
+++ b/bundles/org.eclipse.compare/pom.xml
@@ -19,6 +19,6 @@
   </parent>
   <groupId>org.eclipse.compare</groupId>
   <artifactId>org.eclipse.compare</artifactId>
-  <version>3.6.100-SNAPSHOT</version>
+  <version>3.7.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/examples/org.eclipse.compare.examples.xml/src/org/eclipse/compare/examples/xml/XMLStructureViewer.java b/examples/org.eclipse.compare.examples.xml/src/org/eclipse/compare/examples/xml/XMLStructureViewer.java
index af1ae11..cb87573 100644
--- a/examples/org.eclipse.compare.examples.xml/src/org/eclipse/compare/examples/xml/XMLStructureViewer.java
+++ b/examples/org.eclipse.compare.examples.xml/src/org/eclipse/compare/examples/xml/XMLStructureViewer.java
@@ -499,6 +499,7 @@
 				!getXMLStructureCreator().getRemoveWhiteSpace());
 			contentChanged();
 		}
+		super.propertyChange(event);
 	}
 
 	private void performMatching(final XMLNode left, final XMLNode right,