Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Richard2013-05-21 11:55:05 -0400
committerMikaƫl Barbero2013-06-27 16:18:44 -0400
commite249f7f4ef4fbfa75f5f7bcfb407784a2bbe1d7e (patch)
tree82277280e364cdf5920ceed406b973605b3e3467
parent68a39767bef4abb768055e6fc0b33618438fb620 (diff)
downloadorg.eclipse.emf.compare-e249f7f4ef4fbfa75f5f7bcfb407784a2bbe1d7e.tar.gz
org.eclipse.emf.compare-e249f7f4ef4fbfa75f5f7bcfb407784a2bbe1d7e.tar.xz
org.eclipse.emf.compare-e249f7f4ef4fbfa75f5f7bcfb407784a2bbe1d7e.zip
[398361] Enhanced Model Merge UI
- Add new Ruler next to StructureMergeViewer - When select diff in StructureMergeViewer, highlight required and unmergeable diffs, and create annotations in Ruler - Add new merge and navigation actions in StructureMergeViewer toolbar. - Delete old merge and navigation actions in ContentMergeViewer toolbar. Bug: 398361 Change-Id: I3d43fe9c4938404361f835e93631ed744a2c4377
-rw-r--r--plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java29
-rw-r--r--plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_left_ov.gifbin0 -> 868 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_right_ov.gifbin0 -> 865 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java413
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_all_changes.gifbin0 -> 85 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_change.gifbin0 -> 76 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/left_to_right.gifbin0 -> 485 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_left.gifbin0 -> 607 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_right.gifbin0 -> 625 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_left.gifbin0 -> 582 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_right.gifbin0 -> 578 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/next_diff.gifbin0 -> 348 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/prev_diff.gifbin0 -> 573 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_all_changes.gifbin0 -> 81 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_change.gifbin0 -> 73 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/right_to_left.gifbin0 -> 490 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/plugin.properties42
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/plugin.xml421
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java65
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/actions/expand/ExpandAllModelAction.java2
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java122
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java41
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/tree/TreeContentMergeViewer.java44
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/AbstractViewerWrapper.java369
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeRuler.java631
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeViewer.java641
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java838
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerContentProvider.java29
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerCreator.java2
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectAllChanges.java252
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectChange.java181
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedAllTo.java70
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedTo.java84
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptAllChanges.java42
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptChange.java41
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownHandler.java109
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownLeftToRight.java53
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownRightToLeft.java51
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToLeft.java28
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToRight.java28
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToLeft.java29
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToRight.java30
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectAllChanges.java42
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectChange.java41
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectNextDiff.java44
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectPreviousDiff.java44
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/AcceptRejectChangePropertyTester.java50
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/DiffSelectedPropertyTester.java60
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/LeftToRightSidePropertyTester.java48
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/MergedToPropertyTester.java48
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/ModelSaveablePropertyTester.java (renamed from plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/ModelSaveablePropertyTester.java)7
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/RightToLeftSidePropertyTester.java48
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/util/EMFCompareUIHandlerUtil.java194
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/EMFCompareConstants.java4
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/groups/StructureMergeViewerGrouper.java2
-rw-r--r--plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF1
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/DiffMergeDataAdapter.java116
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IDiffMergeData.java73
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/utils/DiffUtil.java128
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java380
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java1506
61 files changed, 5605 insertions, 1918 deletions
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
index 90a86939c..8b5f08da8 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
@@ -27,7 +27,6 @@ import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
@@ -1719,34 +1718,6 @@ public class DiagramContentMergeViewer extends EMFCompareContentMergeViewer {
/**
* {@inheritDoc}
*
- * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#copyDiff(boolean)
- */
- @Override
- protected void copyDiff(boolean leftToRight) {
- /*
- * FIXME change this! For the moment we always do a new setInput() on the content viewer whenever we
- * select a Diagram Difference. This is meant to change so that we use selection synchronization
- * instead. This code will break whenever we implement that change.
- */
- if (fCurrentSelectedDiff != null) {
- final Command command = getEditingDomain().createCopyCommand(
- Collections.singletonList(fCurrentSelectedDiff), leftToRight,
- EMFCompareRCPPlugin.getDefault().getMergerRegistry());
- getEditingDomain().getCommandStack().execute(command);
-
- if (leftToRight) {
- setRightDirty(true);
- } else {
- setLeftDirty(true);
- }
- // refresh();
- }
-
- }
-
- /**
- * {@inheritDoc}
- *
* @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getContents(boolean)
*/
@Override
diff --git a/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_left_ov.gif b/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_left_ov.gif
new file mode 100644
index 000000000..117e63d56
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_left_ov.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_right_ov.gif b/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_right_ov.gif
new file mode 100644
index 000000000..26ebe0fe6
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.edit/icons/full/ovr16/merged_right_ov.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
index 365932911..a73652b16 100644
--- a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
+++ b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
@@ -1,196 +1,217 @@
-/*******************************************************************************
- * Copyright (c) 2012 Obeo.
- * 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:
- * Obeo - initial API and implementation
- *******************************************************************************/
-package org.eclipse.emf.compare.provider.spec;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.emf.common.util.ResourceLocator;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Conflict;
-import org.eclipse.emf.compare.ConflictKind;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceKind;
-import org.eclipse.emf.compare.DifferenceSource;
-import org.eclipse.emf.compare.DifferenceState;
-import org.eclipse.emf.compare.Match;
-import org.eclipse.emf.edit.provider.ComposedImage;
-
-/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
-public class OverlayImageProvider {
-
- private final ResourceLocator fResourceLocator;
-
- /**
- *
- */
- public OverlayImageProvider(ResourceLocator resourceLocator) {
- this.fResourceLocator = resourceLocator;
- }
-
- public Object getComposedImage(Diff diff, Object imageToCompose) {
- String overlay = getImageOverlay(diff);
- return getComposedImage(imageToCompose, overlay);
- }
-
- public Object getComposedImage(Match match, Object imageToCompose) {
- String overlay = getImageOverlay(match);
- return getComposedImage(imageToCompose, overlay);
- }
-
- private Object getComposedImage(Object imageToCompose, String overlay) {
- Collection<Object> images = newArrayList();
- images.add(imageToCompose);
- if (overlay != null) {
- Object image = fResourceLocator.getImage(overlay);
- images.add(image);
- }
- return new ComposedImageExtension(images);
- }
-
- // Nothing here has to be externalized
- @SuppressWarnings("nls")
- private String getImageOverlay(Diff diff) {
- final DifferenceSource source = diff.getSource();
- final Match match = diff.getMatch();
- final Conflict conflict = diff.getConflict();
- final DifferenceKind diffKind = diff.getKind();
- final Comparison comparison = match.getComparison();
- String path = "full/ovr16/";
-
- if (diff.getState() == DifferenceState.MERGED) {
- path += "merged_ov";
- } else if (diff.getState() == DifferenceState.DISCARDED) {
- path += "removed_ov";
- } else if (comparison.isThreeWay()) {
- // "png" needs explicit declaration, "gif" does not
- String extension = "";
- if (conflict != null) {
- extension = ".png";
- if (conflict.getKind() == ConflictKind.PSEUDO) {
- path += "p";
- }
- path += "conf";
- if (source == DifferenceSource.RIGHT) {
- path += "r_";
- }
- } else {
- switch (source) {
- case LEFT:
- path += "r_out";
- break;
- case RIGHT:
- path += "r_in";
- break;
- default:
- // Cannot happen ... for now
- break;
- }
- }
-
- switch (diffKind) {
- case ADD:
- path += "add_ov";
- break;
- case DELETE:
- path += "del_ov";
- break;
- case CHANGE:
- // fallthrough
- case MOVE:
- path += "chg_ov";
- break;
- default:
- // Cannot happen ... for now
- break;
- }
- path += extension;
- } else {
- switch (diffKind) {
- case ADD:
- path += "add_ov";
- break;
- case DELETE:
- path += "del_ov";
- break;
- case CHANGE:
- // fallthrough
- case MOVE:
- path += "chg_ov";
- break;
- default:
- break;
- }
- }
- return path;
- }
-
- // Nothing here has to be externalized
- @SuppressWarnings("nls")
- private String getImageOverlay(Match match) {
- return "full/ovr16/match_ov.png";
- }
-
- private final class ComposedImageExtension extends ComposedImage {
-
- /**
- *
- */
- private static final int X_OFFSET = 10;
-
- /**
- * @param images
- */
- ComposedImageExtension(Collection<?> images) {
- super(images);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.provider.ComposedImage#getDrawPoints(org.eclipse.emf.edit.provider.ComposedImage.Size)
- */
- @Override
- public List<Point> getDrawPoints(Size size) {
- List<ComposedImage.Point> result = super.getDrawPoints(size);
- if (result.size() > 1) {
- result.get(1).x = X_OFFSET;
- result.get(1).y = 2;
- }
- return result;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.provider.ComposedImage#getSize(java.util.Collection)
- */
- @Override
- public Size getSize(Collection<? extends Size> sizes) {
- this.imageSizes = newArrayList(sizes);
- List<Point> drawPoints = getDrawPoints(null);
-
- Size result = new Size();
- for (int i = 0; i < sizes.size(); i++) {
- Size size = this.imageSizes.get(i);
- Point point = drawPoints.get(i);
-
- result.width = Math.max(result.width, size.width + Math.abs(point.x));
- result.height = Math.max(result.height, size.height + Math.abs(point.y));
- }
- return result;
- }
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2012 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.provider.spec;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.util.ResourceLocator;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.ConflictKind;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceKind;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.internal.merge.IDiffMergeData;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.edit.provider.ComposedImage;
+
+/**
+ * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
+ */
+public class OverlayImageProvider {
+
+ private final ResourceLocator fResourceLocator;
+
+ /**
+ *
+ */
+ public OverlayImageProvider(ResourceLocator resourceLocator) {
+ this.fResourceLocator = resourceLocator;
+ }
+
+ public Object getComposedImage(Diff diff, Object imageToCompose) {
+ String overlay = getImageOverlay(diff);
+ return getComposedImage(imageToCompose, overlay);
+ }
+
+ public Object getComposedImage(Match match, Object imageToCompose) {
+ String overlay = getImageOverlay(match);
+ return getComposedImage(imageToCompose, overlay);
+ }
+
+ private Object getComposedImage(Object imageToCompose, String overlay) {
+ Collection<Object> images = newArrayList();
+ images.add(imageToCompose);
+ if (overlay != null) {
+ Object image = fResourceLocator.getImage(overlay);
+ images.add(image);
+ }
+ return new ComposedImageExtension(images);
+ }
+
+ // Nothing here has to be externalized
+ @SuppressWarnings("nls")
+ private String getImageOverlay(Diff diff) {
+ final DifferenceSource source = diff.getSource();
+ final Match match = diff.getMatch();
+ final Conflict conflict = diff.getConflict();
+ final DifferenceKind diffKind = diff.getKind();
+ final Comparison comparison = match.getComparison();
+ String path = "full/ovr16/";
+
+ if (diff.getState() == DifferenceState.MERGED) {
+ Adapter adapter = EcoreUtil.getExistingAdapter(diff, IDiffMergeData.class);
+ if (adapter != null) {
+ IDiffMergeData iMergeData = (IDiffMergeData)adapter;
+ if (iMergeData.isLeftEditable() && !iMergeData.isRightEditable()) {
+ if (iMergeData.hasBeenMergedToLeft()) {
+ path += "removed_ov";
+ } else {
+ path += "merged_ov";
+ }
+ } else if (iMergeData.isLeftEditable() && iMergeData.isRightEditable()) {
+ if (iMergeData.hasBeenMergedToLeft()) {
+ path += "merged_left_ov";
+ } else {
+ path += "merged_right_ov";
+ }
+ }
+ } else {
+ path += "merged_ov";
+ }
+ } else if (diff.getState() == DifferenceState.DISCARDED) {
+ path += "removed_ov";
+ } else if (comparison.isThreeWay()) {
+ // "png" needs explicit declaration, "gif" does not
+ String extension = "";
+ if (conflict != null) {
+ extension = ".png";
+ if (conflict.getKind() == ConflictKind.PSEUDO) {
+ path += "p";
+ }
+ path += "conf";
+ if (source == DifferenceSource.RIGHT) {
+ path += "r_";
+ }
+ } else {
+ switch (source) {
+ case LEFT:
+ path += "r_out";
+ break;
+ case RIGHT:
+ path += "r_in";
+ break;
+ default:
+ // Cannot happen ... for now
+ break;
+ }
+ }
+
+ switch (diffKind) {
+ case ADD:
+ path += "add_ov";
+ break;
+ case DELETE:
+ path += "del_ov";
+ break;
+ case CHANGE:
+ // fallthrough
+ case MOVE:
+ path += "chg_ov";
+ break;
+ default:
+ // Cannot happen ... for now
+ break;
+ }
+ path += extension;
+ } else {
+ switch (diffKind) {
+ case ADD:
+ path += "add_ov";
+ break;
+ case DELETE:
+ path += "del_ov";
+ break;
+ case CHANGE:
+ // fallthrough
+ case MOVE:
+ path += "chg_ov";
+ break;
+ default:
+ break;
+ }
+ }
+ return path;
+ }
+
+ // Nothing here has to be externalized
+ @SuppressWarnings("nls")
+ private String getImageOverlay(Match match) {
+ return "full/ovr16/match_ov.png";
+ }
+
+ private final class ComposedImageExtension extends ComposedImage {
+
+ /**
+ *
+ */
+ private static final int X_OFFSET = 10;
+
+ /**
+ * @param images
+ */
+ ComposedImageExtension(Collection<?> images) {
+ super(images);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.provider.ComposedImage#getDrawPoints(org.eclipse.emf.edit.provider.ComposedImage.Size)
+ */
+ @Override
+ public List<Point> getDrawPoints(Size size) {
+ List<ComposedImage.Point> result = super.getDrawPoints(size);
+ if (result.size() > 1) {
+ result.get(1).x = X_OFFSET;
+ result.get(1).y = 2;
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.provider.ComposedImage#getSize(java.util.Collection)
+ */
+ @Override
+ public Size getSize(Collection<? extends Size> sizes) {
+ this.imageSizes = newArrayList(sizes);
+ List<Point> drawPoints = getDrawPoints(null);
+
+ Size result = new Size();
+ for (int i = 0; i < sizes.size(); i++) {
+ Size size = this.imageSizes.get(i);
+ Point point = drawPoints.get(i);
+
+ result.width = Math.max(result.width, size.width + Math.abs(point.x));
+ result.height = Math.max(result.height, size.height + Math.abs(point.y));
+ }
+ return result;
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_all_changes.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_all_changes.gif
new file mode 100644
index 000000000..8456759aa
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_all_changes.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_change.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_change.gif
new file mode 100644
index 000000000..23c97f09e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/accept_change.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/left_to_right.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/left_to_right.gif
new file mode 100644
index 000000000..29189bbab
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/left_to_right.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_left.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_left.gif
new file mode 100644
index 000000000..e029948cf
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_left.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_right.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_right.gif
new file mode 100644
index 000000000..0659813a1
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_all_to_right.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_left.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_left.gif
new file mode 100644
index 000000000..db2b1e3d5
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_left.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_right.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_right.gif
new file mode 100644
index 000000000..9ebd50a9f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/merge_to_right.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/next_diff.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/next_diff.gif
new file mode 100644
index 000000000..79cda1359
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/next_diff.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/prev_diff.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/prev_diff.gif
new file mode 100644
index 000000000..d5a96ec97
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/prev_diff.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_all_changes.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_all_changes.gif
new file mode 100644
index 000000000..a873b3056
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_all_changes.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_change.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_change.gif
new file mode 100644
index 000000000..1aca259db
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/reject_change.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/right_to_left.gif b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/right_to_left.gif
new file mode 100644
index 000000000..ce33df2a2
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/right_to_left.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/plugin.properties b/plugins/org.eclipse.emf.compare.ide.ui/plugin.properties
index c87ce892d..a9c88e240 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/plugin.properties
+++ b/plugins/org.eclipse.emf.compare.ide.ui/plugin.properties
@@ -14,3 +14,45 @@ providerName = Eclipse Modeling Project
save.model.label = Save Comparison Model
save.model.tooltip = Save Comparison Model
save.model.command.name = EMF Compare Save Comparison Model
+
+next.diff = Next Difference
+next.diff.tooltip = Next Difference
+next.diff.command.name = EMF Compare Next Difference
+
+previous.diff = Previous Difference
+previous.diff.tooltip = Previous Difference
+previous.diff.command.name = EMF Compare Previous Difference
+
+accept.change = Accept Change
+accept.change.tooltip = Accept Change
+accept.change.command.name = EMF Compare Accept Change
+
+accept.all.changes = Accept All Non-Conflicting Changes
+accept.all.changes.tooltip = Accept All Non-Conflicting Changes
+accept.all.changes.command.name = EMF Compare Accept All Non-Conflicting Changes
+
+reject.change = Reject Change
+reject.change.tooltip = Reject Change
+reject.change.command.name = EMF Compare Reject Change
+
+reject.all.changes = Reject All Non-Conflicting Changes
+reject.all.changes.tooltip = Reject All Non-Conflicting Changes
+reject.all.changes.command.name = EMF Compare Reject All Non-Conflicting Changes
+
+merged.to.left = Copy Current Change To Left
+merged.to.left.tooltip = Copy Current Change From Right To Left
+merged.to.left.command.name = EMF Compare Copy Current Change To Left
+
+merged.to.right = Copy Current Change To Right
+merged.to.right.tooltip = Copy Current Change From Left To Right
+merged.to.right.command.name = EMF Compare Copy Current Change To Right
+
+merged.all.to.left = Copy All Non-Conflicting Changes To Left
+merged.all.to.left.tooltip = Copy All Non-Conflicting Changes From Right To Left
+merged.all.to.left.command.name = EMF Compare Copy All Non-Conflicting Changes To Left
+
+merged.all.to.right = Copy All Non-Conflicting Changes To Right
+merged.all.to.right.tooltip = Copy All Non-Conflicting Changes From Left To Right
+merged.all.to.right.command.name = EMF Compare Copy All Non-Conflicting Changes To Right
+
+dropdown.tooltip = Select the way of merge
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
index ddc8431ab..124535c5e 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
@@ -106,13 +106,176 @@
allPopups="false"
locationURI="toolbar:org.eclipse.emf.compare.structuremergeviewer.toolbar">
<command
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown"
+ icon="icons/full/toolb16/left_to_right.gif"
+ id="org.eclipse.emf.compare.ide.ui.setdropdown"
+ style="pulldown"
+ tooltip="%dropdown.tooltip">
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.acceptChange"
+ icon="icons/full/toolb16/accept_change.gif"
+ label="%accept.change"
+ style="push"
+ tooltip="%accept.change.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.acceptAllChanges"
+ icon="icons/full/toolb16/accept_all_changes.gif"
+ label="%accept.all.changes"
+ style="push"
+ tooltip="%accept.all.changes.tooltip">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.mergedToRight"
+ icon="icons/full/toolb16/merge_to_right.gif"
+ label="%merged.to.right"
+ style="push"
+ tooltip="%merged.to.right.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.mergedAllToRight"
+ icon="icons/full/toolb16/merge_all_to_right.gif"
+ label="%merged.all.to.right"
+ style="push"
+ tooltip="%merged.all.to.right.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <separator
+ name="org.eclipse.emf.compare.ide.ui.separatorLeftRight"
+ visible="true">
+ </separator>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.rejectChange"
+ icon="icons/full/toolb16/reject_change.gif"
+ label="%reject.change"
+ style="push"
+ tooltip="%reject.change.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.rejectAllChanges"
+ icon="icons/full/toolb16/reject_all_changes.gif"
+ label="%reject.all.changes"
+ style="push"
+ tooltip="%reject.all.changes.tooltip">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.mergedToLeft"
+ icon="icons/full/toolb16/merge_to_left.gif"
+ label="%merged.to.left"
+ style="push"
+ tooltip="%merged.to.left.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.mergedAllToLeft"
+ icon="icons/full/toolb16/merge_all_to_left.gif"
+ label="%merged.all.to.left"
+ style="push"
+ tooltip="%merged.all.to.left.tooltip">
+ <visibleWhen
+ checkEnabled="false">
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </visibleWhen>
+ </command>
+ <separator
+ name="org.eclipse.emf.compare.ide.ui.separatorNextPrevDiff"
+ visible="true">
+ </separator>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.nextDiff"
+ icon="icons/full/toolb16/next_diff.gif"
+ label="%next.diff"
+ style="push"
+ tooltip="%next.diff.tooltip">
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.previousDiff"
+ icon="icons/full/toolb16/prev_diff.gif"
+ label="%previous.diff"
+ style="push"
+ tooltip="%previous.diff.tooltip">
+ </command>
+ <separator
+ name="org.eclipse.emf.compare.ide.ui.separatorSave1"
+ visible="true">
+ </separator>
+ <command
commandId="org.eclipse.emf.compare.ide.ui.saveComparisonModel"
icon="icons/full/toolb16/saveas_edit.gif"
- label="%save.model.name"
+ label="%save.model.label"
style="push"
tooltip="%save.model.tooltip">
</command>
</menuContribution>
+ <menuContribution
+ allPopups="false"
+ locationURI="menu:org.eclipse.emf.compare.ide.ui.setdropdown">
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown.ltr"
+ icon="icons/full/toolb16/left_to_right.gif"
+ style="push">
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown.rtl"
+ icon="icons/full/toolb16/right_to_left.gif"
+ style="push">
+ </command>
+ </menuContribution>
</extension>
<extension
point="org.eclipse.ui.commands">
@@ -131,6 +294,58 @@
name="Each Other">
</command>
<command
+ id="org.eclipse.emf.compare.ide.ui.dropdown"
+ name="org.eclipse.emf.compare.ide.ui.dropdown">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.dropdown.ltr"
+ name="Left to Right">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.dropdown.rtl"
+ name="Right to Left">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.acceptChange"
+ name="%accept.change.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.acceptAllChanges"
+ name="%accept.all.changes.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.rejectChange"
+ name="%reject.change.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.rejectAllChanges"
+ name="%reject.all.changes.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.mergedToRight"
+ name="%merged.to.right.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.mergedAllToRight"
+ name="%merged.all.to.right.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.mergedToLeft"
+ name="%merged.to.left.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.mergedAllToLeft"
+ name="%merged.all.to.left.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.nextDiff"
+ name="%next.diff.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.ide.ui.previousDiff"
+ name="%previous.diff.command.name">
+ </command>
+ <command
id="org.eclipse.emf.compare.ide.ui.saveComparisonModel"
name="%save.model.command.name">
</command>
@@ -216,6 +431,161 @@
</activeWhen>
</handler>
<handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.AcceptChange"
+ commandId="org.eclipse.emf.compare.ide.ui.acceptChange">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </activeWhen>
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <and>
+ <test
+ property="emfcompare.diffSelected">
+ </test>
+ <test
+ property="emfcompare.leftToRightSide">
+ </test>
+ </and>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.AcceptAllChanges"
+ commandId="org.eclipse.emf.compare.ide.ui.acceptAllChanges">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.RejectChange"
+ commandId="org.eclipse.emf.compare.ide.ui.rejectChange">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </activeWhen>
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <and>
+ <test
+ property="emfcompare.diffSelected">
+ </test>
+ <test
+ property="emfcompare.rightToLeftSide">
+ </test>
+ </and>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.RejectAllChanges"
+ commandId="org.eclipse.emf.compare.ide.ui.rejectAllChanges">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.MergedToRight"
+ commandId="org.eclipse.emf.compare.ide.ui.mergedToRight">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </activeWhen>
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <and>
+ <test
+ property="emfcompare.diffSelected">
+ </test>
+ <test
+ property="emfcompare.leftToRightSide">
+ </test>
+ </and>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.MergedAllToRight"
+ commandId="org.eclipse.emf.compare.ide.ui.mergedAllToRight">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </activeWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.MergedToLeft"
+ commandId="org.eclipse.emf.compare.ide.ui.mergedToLeft">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with>
+ </activeWhen>
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <and>
+ <test
+ property="emfcompare.diffSelected">
+ </test>
+ <test
+ property="emfcompare.rightToLeftSide">
+ </test>
+ </and>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.MergedAllToLeft"
+ commandId="org.eclipse.emf.compare.ide.ui.mergedAllToLeft">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.bothSidesWriteable">
+ </test>
+ </with></activeWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.SelectNextDiff"
+ commandId="org.eclipse.emf.compare.ide.ui.nextDiff">
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.SelectPreviousDiff"
+ commandId="org.eclipse.emf.compare.ide.ui.previousDiff">
+ </handler>
+ <handler
class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.SaveComparisonModel"
commandId="org.eclipse.emf.compare.ide.ui.saveComparisonModel">
<enabledWhen>
@@ -228,6 +598,18 @@
</enabledWhen>
</handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.DropDownHandler"
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown">
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.DropDownLeftToRight"
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown.ltr">
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.DropDownRightToLeft"
+ commandId="org.eclipse.emf.compare.ide.ui.dropdown.rtl">
+ </handler>
</extension>
<extension
id="org.eclipse.emf.compare.model.provider"
@@ -259,12 +641,47 @@
<extension
point="org.eclipse.core.expressions.propertyTesters">
<propertyTester
- class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.ModelSaveablePropertyTester"
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.ModelSaveablePropertyTester"
id="org.eclipse.emf.compare.ide.ui.modelSaveable"
namespace="emfcompare"
properties="isSaveable"
type="java.lang.Object">
</propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.AcceptRejectChangePropertyTester"
+ id="org.eclipse.emf.compare.ide.ui.hasReadOnlySide"
+ namespace="emfcompare"
+ properties="hasReadOnlySide"
+ type="java.lang.Object">
+ </propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.MergedToPropertyTester"
+ id="org.eclipse.emf.compare.ide.ui.bothSidesWriteable"
+ namespace="emfcompare"
+ properties="bothSidesWriteable"
+ type="java.lang.Object">
+ </propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.DiffSelectedPropertyTester"
+ id="org.eclipse.emf.compare.ide.ui.diffSelected"
+ namespace="emfcompare"
+ properties="diffSelected"
+ type="java.lang.Object">
+ </propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.LeftToRightSidePropertyTester"
+ id="org.eclipse.emf.compare.ide.ui.leftToRightSide"
+ namespace="emfcompare"
+ properties="leftToRightSide"
+ type="java.lang.Object">
+ </propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester.RightToLeftSidePropertyTester"
+ id="org.eclipse.emf.compare.ide.ui.rightToLeftSide"
+ namespace="emfcompare"
+ properties="rightToLeftSide"
+ type="java.lang.Object">
+ </propertyTester>
</extension>
<extension
point="org.eclipse.emf.compare.ide.ui.modelResolvers">
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
index 03c80982f..c3df55225 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/EMFCompareIDEUIPlugin.java
@@ -10,6 +10,10 @@
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
@@ -18,6 +22,8 @@ import org.eclipse.emf.compare.ide.ui.internal.logical.IModelResolverRegistry;
import org.eclipse.emf.compare.ide.ui.internal.logical.ModelResolverRegistryImpl;
import org.eclipse.emf.compare.ide.ui.internal.logical.ModelResolverRegistryListener;
import org.eclipse.emf.compare.rcp.extension.AbstractRegistryEventListener;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@@ -42,6 +48,9 @@ public class EMFCompareIDEUIPlugin extends AbstractUIPlugin {
/** Registry of model resolvers. */
private IModelResolverRegistry modelResolverRegistry;
+ /** keep track of resources that should be freed when exiting. */
+ private static Map<String, Image> resourcesMapper = new HashMap<String, Image>();
+
/** Default constructor. */
public EMFCompareIDEUIPlugin() {
// Empty constructor
@@ -90,6 +99,62 @@ public class EMFCompareIDEUIPlugin extends AbstractUIPlugin {
}
/**
+ * <p>
+ * returns a plugin image. The returned image does not need to be explicitly disposed.
+ * </p>
+ *
+ * @param imagePath
+ * : plugin relative path to the image
+ * @return Image : plugin hosted image
+ */
+ public static Image getImage(String imagePath) {
+ Image image = resourcesMapper.get(imagePath);
+ if (image == null) {
+ ImageDescriptor imageDescriptor = imageDescriptorFromPlugin(PLUGIN_ID, imagePath);
+ image = imageDescriptor.createImage();
+ resourcesMapper.put(imagePath, image);
+ }
+ return image;
+ }
+
+ /**
+ * <p>
+ * returns a plugin image descriptor.
+ * </p>
+ *
+ * @param imagePath
+ * : plugin relative path to the image
+ * @return ImageDescriptor : image descriptor.
+ */
+ public static ImageDescriptor getImageDescriptor(String imagePath) {
+ return imageDescriptorFromPlugin(PLUGIN_ID, imagePath);
+ }
+
+ /**
+ * Dispose image with the given id.
+ *
+ * @param id
+ * : dispose system resources associated with the image with the given id.
+ */
+ public static void disposeImage(String id) {
+ Image image = resourcesMapper.remove(id);
+ if (image != null) {
+ image.dispose();
+ }
+ }
+
+ /**
+ * dispose system resources associated with cached images.
+ */
+ public static void disposeCachedImages() {
+ Iterator<Image> iterator = resourcesMapper.values().iterator();
+ while (iterator.hasNext()) {
+ iterator.next().dispose();
+ }
+ resourcesMapper.clear();
+ }
+
+ /**
* Returns the registry containing all known model resolvers.
*
* @return The registry containing all known model resolvers.
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/actions/expand/ExpandAllModelAction.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/actions/expand/ExpandAllModelAction.java
index ea3ba9847..a36941860 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/actions/expand/ExpandAllModelAction.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/actions/expand/ExpandAllModelAction.java
@@ -37,5 +37,7 @@ public class ExpandAllModelAction extends Action {
@Override
public void run() {
treeViewer.expandToLevel(256);
+ // Workaround to force the redraw of the EMFCompareDiffTreeruler
+ treeViewer.setSelection(treeViewer.getSelection());
}
}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java
index 28deda794..0ab47750e 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java
@@ -23,11 +23,8 @@ import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.compare.CompareConfiguration;
-import org.eclipse.compare.CompareNavigator;
-import org.eclipse.compare.ICompareNavigator;
import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.compare.internal.CompareHandlerService;
-import org.eclipse.compare.internal.Utilities;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStackListener;
@@ -51,8 +48,6 @@ import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.item.IMergeViewerItem
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl.CascadingDifferencesFilter;
import org.eclipse.emf.compare.utils.EMFComparePredicates;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
@@ -91,10 +86,6 @@ public abstract class EMFCompareContentMergeViewer extends ContentMergeViewer im
private IMergeViewer fRight;
- private ActionContributionItem fCopyDiffLeftToRightItem;
-
- private ActionContributionItem fCopyDiffRightToLeftItem;
-
private final AtomicBoolean fSyncingSelections = new AtomicBoolean(false);
private EMFCompareColor fColors;
@@ -292,66 +283,6 @@ public abstract class EMFCompareContentMergeViewer extends ContentMergeViewer im
"toolbar:org.eclipse.emf.compare.contentmergeviewer.toolbar");
}
- // Copy actions
- CompareConfiguration cc = getCompareConfiguration();
-
- if (cc.isRightEditable()) {
- Action copyLeftToRight = new Action() {
- @Override
- public void run() {
- copyDiff(true);
- // Select next diff
- navigate(true);
- }
- };
- Utilities.initAction(copyLeftToRight, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
- copyLeftToRight.setEnabled(false);
- fCopyDiffLeftToRightItem = new ActionContributionItem(copyLeftToRight);
- fCopyDiffLeftToRightItem.setVisible(true);
- toolBarManager.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
- getHandlerService().registerAction(copyLeftToRight, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$
- }
-
- if (cc.isLeftEditable()) {
- Action copyRightToLeft = new Action() {
- @Override
- public void run() {
- copyDiff(false);
- // Select next diff
- navigate(true);
- }
- };
- Utilities.initAction(copyRightToLeft, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
- copyRightToLeft.setEnabled(false);
- fCopyDiffRightToLeftItem = new ActionContributionItem(copyRightToLeft);
- fCopyDiffRightToLeftItem.setVisible(true);
- toolBarManager.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
- getHandlerService().registerAction(copyRightToLeft, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$
- }
-
- // Navigation
- final Action nextDiff = new Action() {
- @Override
- public void run() {
- navigate(true);
- }
- };
- Utilities.initAction(nextDiff, getResourceBundle(), "action.NextDiff.");
- ActionContributionItem contributionNextDiff = new ActionContributionItem(nextDiff);
- contributionNextDiff.setVisible(true);
- toolBarManager.appendToGroup("navigation", contributionNextDiff);
-
- final Action previousDiff = new Action() {
- @Override
- public void run() {
- navigate(false);
- }
- };
- Utilities.initAction(previousDiff, getResourceBundle(), "action.PrevDiff.");
- ActionContributionItem contributionPreviousDiff = new ActionContributionItem(previousDiff);
- contributionPreviousDiff.setVisible(true);
- toolBarManager.appendToGroup("navigation", contributionPreviousDiff);
-
undoAction = new UndoAction(getEditingDomain());
redoAction = new RedoAction(getEditingDomain());
@@ -416,29 +347,6 @@ public abstract class EMFCompareContentMergeViewer extends ContentMergeViewer im
}
/**
- * Called by the framework to navigate to the next (or previous) difference. This will open the content
- * viewer for the next (or previous) diff displayed in the structure viewer.
- *
- * @param next
- * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if
- * we should go to the previous instead.
- */
- protected void navigate(boolean next) {
- final Control control = getControl();
- if (control != null && !control.isDisposed()) {
- final ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
- if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) {
- navigator.selectChange(next);
- }
- }
- }
-
- /**
- *
- */
- protected abstract void copyDiff(boolean leftToRight);
-
- /**
* {@inheritDoc}
*
* @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleResizeAncestor(int, int, int, int)
@@ -568,36 +476,6 @@ public abstract class EMFCompareContentMergeViewer extends ContentMergeViewer im
}
/**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#updateToolItems()
- */
- @Override
- protected void updateToolItems() {
- super.updateToolItems();
-
- manageCopyActionsActivation();
- }
-
- protected void manageCopyActionsActivation() {
- Diff diff = getDiffFrom(getRightMergeViewer());
- if (diff == null) {
- diff = getDiffFrom(getLeftMergeViewer());
- }
- boolean enableCopy = false;
- if (diff != null) {
- enableCopy = diff.getState() == DifferenceState.UNRESOLVED;
- }
-
- if (fCopyDiffLeftToRightItem != null) {
- fCopyDiffLeftToRightItem.getAction().setEnabled(enableCopy);
- }
- if (fCopyDiffRightToLeftItem != null) {
- fCopyDiffRightToLeftItem.getAction().setEnabled(enableCopy);
- }
- }
-
- /**
* Checks the element selected in the given viewer in order to determine whether it can be adapted into a
* Diff.
*
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java
index 68a7c512a..45ef92e57 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java
@@ -10,14 +10,9 @@
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.table;
-import static com.google.common.collect.Iterables.addAll;
-
-import java.util.ArrayList;
-import java.util.List;
import java.util.ResourceBundle;
import org.eclipse.compare.CompareConfiguration;
-import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
@@ -35,9 +30,7 @@ import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.viewers.ArrayContentProvider;
-import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
@@ -109,40 +102,6 @@ public class TableContentMergeViewer extends EMFCompareContentMergeViewer {
/**
* {@inheritDoc}
*
- * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#copyDiff(boolean)
- */
- @Override
- protected void copyDiff(boolean leftToRight) {
- final Diff diffToCopy = getDiffToCopy(getRightMergeViewer());
- if (diffToCopy != null) {
- List<Diff> diffsToCopy = new ArrayList<Diff>();
- diffsToCopy.add(diffToCopy);
- if (isSubDiffFilterActive()) {
- addAll(diffsToCopy, ComparisonUtil.getSubDiffs(leftToRight).apply(diffToCopy));
- }
- Command copyCommand = getEditingDomain().createCopyCommand(diffsToCopy, leftToRight,
- EMFCompareRCPPlugin.getDefault().getMergerRegistry());
-
- getEditingDomain().getCommandStack().execute(copyCommand);
- refresh();
- }
- }
-
- private Diff getDiffToCopy(AbstractMergeViewer abstractMergeViewer) {
- Diff diffToCopy = null;
- ISelection selection = abstractMergeViewer.getSelection();
- if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
- Object firstElement = ((IStructuredSelection)selection).getFirstElement();
- if (firstElement instanceof IMergeViewerItem) {
- diffToCopy = ((IMergeViewerItem)firstElement).getDiff();
- }
- }
- return diffToCopy;
- }
-
- /**
- * {@inheritDoc}
- *
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getLeftMergeViewer()
*/
@Override
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/tree/TreeContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/tree/TreeContentMergeViewer.java
index d3f6cd792..db0483d60 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/tree/TreeContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/tree/TreeContentMergeViewer.java
@@ -11,20 +11,17 @@
package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree;
import static com.google.common.base.Predicates.equalTo;
-import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.compare.CompareConfiguration;
-import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.Match;
@@ -50,9 +47,7 @@ import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
-import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
@@ -167,45 +162,6 @@ public class TreeContentMergeViewer extends EMFCompareContentMergeViewer {
/**
* {@inheritDoc}
*
- * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#copyDiff(boolean)
- */
- @Override
- protected void copyDiff(boolean leftToRight) {
- final Diff diffToCopy;
- if (leftToRight) {
- diffToCopy = getDiffToCopy(getLeftMergeViewer());
- } else {
- diffToCopy = getDiffToCopy(getRightMergeViewer());
- }
- if (diffToCopy != null) {
- List<Diff> diffsToCopy = new ArrayList<Diff>();
- diffsToCopy.add(diffToCopy);
- if (isSubDiffFilterActive()) {
- addAll(diffsToCopy, ComparisonUtil.getSubDiffs(leftToRight).apply(diffToCopy));
- }
- Command copyCommand = getEditingDomain().createCopyCommand(diffsToCopy, leftToRight,
- EMFCompareRCPPlugin.getDefault().getMergerRegistry());
-
- getEditingDomain().getCommandStack().execute(copyCommand);
- refresh();
- }
- }
-
- private Diff getDiffToCopy(AbstractMergeViewer abstractMergeViewer) {
- Diff diffToCopy = null;
- ISelection selection = abstractMergeViewer.getSelection();
- if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
- Object firstElement = ((IStructuredSelection)selection).getFirstElement();
- if (firstElement instanceof IMergeViewerItem) {
- diffToCopy = ((IMergeViewerItem)firstElement).getDiff();
- }
- }
- return diffToCopy;
- }
-
- /**
- * {@inheritDoc}
- *
* @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getContents(boolean)
*/
@Override
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/AbstractViewerWrapper.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/AbstractViewerWrapper.java
new file mode 100644
index 000000000..41b62f00b
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/AbstractViewerWrapper.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
+
+import java.util.List;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.IOpenListener;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredViewer;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Widget;
+
+/**
+ * A control that wrapped a StructuredViewer and another control.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public abstract class AbstractViewerWrapper extends StructuredViewer {
+
+ /** The StructuredViewer associated with this wrapper. */
+ private StructuredViewer fViewer;
+
+ /** A composite control that will contains all sub-control of this wrapper. */
+ private final Control fControl;
+
+ /** The selection changed listener. */
+ private ISelectionChangedListener fWrappedViewerListener;
+
+ /** The compare configuration object. */
+ private final CompareConfiguration fConfiguration;
+
+ /**
+ * Constructor.
+ *
+ * @param parent
+ * the SWT parent control under which to create the viewer's SWT control.
+ * @param config
+ * a compare configuration the newly created viewer might want to use.
+ */
+ public AbstractViewerWrapper(Composite parent, CompareConfiguration config) {
+ fConfiguration = config;
+ fControl = createControl(parent, config);
+ hookControl(fControl);
+ fWrappedViewerListener = new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ fireSelectionChanged(new SelectionChangedEvent(AbstractViewerWrapper.this, event
+ .getSelection()));
+ }
+ };
+ fViewer.addSelectionChangedListener(fWrappedViewerListener);
+
+ setLabelProvider(fViewer.getLabelProvider());
+ setContentProvider(fViewer.getContentProvider());
+ }
+
+ /**
+ * Should call {@link #setViewer(org.eclipse.jface.viewers.Viewer)}.
+ *
+ * @param parent
+ * the SWT control under which to create the viewer.
+ * @param config
+ * the compare configuration object.
+ * @return a composite control that will contains all sub-control of this wrapper.
+ */
+ protected abstract Control createControl(Composite parent, CompareConfiguration config);
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#addOpenListener(IOpenListener)
+ */
+ @Override
+ public void addOpenListener(IOpenListener listener) {
+ fViewer.addOpenListener(listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#removeOpenListener(IOpenListener)
+ */
+ @Override
+ public void removeOpenListener(IOpenListener listener) {
+ fViewer.removeOpenListener(listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#update(Object, String[])
+ */
+ @Override
+ public void update(Object element, String[] properties) {
+ fViewer.update(element, properties);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.Viewer#getControl()
+ */
+ @Override
+ public Control getControl() {
+ return fControl;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.ContentViewer#getInput()
+ */
+ @Override
+ public Object getInput() {
+ return super.getInput();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#getSelection()
+ */
+ @Override
+ public ISelection getSelection() {
+ return fViewer.getSelection();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#refresh()
+ */
+ @Override
+ public void refresh() {
+ fViewer.refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#refresh(boolean)
+ */
+ @Override
+ public void refresh(boolean updateLabels) {
+ fViewer.refresh(updateLabels);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#setSelection(ISelection, boolean)
+ */
+ @Override
+ public void setSelection(ISelection selection, boolean reveal) {
+ fViewer.setSelection(selection, reveal);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#reveal(Object)
+ */
+ @Override
+ public void reveal(Object element) {
+ fViewer.reveal(element);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.ContentViewer#getContentProvider()
+ */
+ @Override
+ public IContentProvider getContentProvider() {
+ return super.getContentProvider();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.ContentViewer#setContentProvider(IContentProvider)
+ */
+ @Override
+ public void setContentProvider(IContentProvider provider) {
+ super.setContentProvider(provider);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.ContentViewer#getLabelProvider()
+ */
+ @Override
+ public IBaseLabelProvider getLabelProvider() {
+ return super.getLabelProvider();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.ContentViewer#setLabelProvider(IBaseLabelProvider)
+ */
+ @Override
+ public void setLabelProvider(IBaseLabelProvider labelProvider) {
+ super.setLabelProvider(labelProvider);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#doFindInputItem(Object)
+ */
+ @Override
+ protected Widget doFindInputItem(Object element) {
+ /*
+ * Nothing to do here. The method doFindInputItem(Object element) is only called by
+ * StructuredViewer#update(Object element, String[] properties), which is override in this class.
+ */
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#doFindItem(Object)
+ */
+ @Override
+ protected Widget doFindItem(Object element) {
+ /*
+ * Nothing to do here. The method doFindItem(Object element) is only called by
+ * StructuredViewer#update(Object element, String[] properties), which is override in this class.
+ */
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#doUpdateItem(Widget, Object, boolean)
+ */
+ @Override
+ protected void doUpdateItem(Widget item, Object element, boolean fullMap) {
+ /*
+ * Nothing to do here. The method doUpdateItem(Widget item, Object element, boolean fullMap) is only
+ * called by StructuredViewer#update(Object element, String[] properties), which is override in this
+ * class.
+ */
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget()
+ */
+ @Override
+ protected List getSelectionFromWidget() {
+ /*
+ * Nothing to do here. The method getSelectionFromWidget() is only called by
+ * StructuredViewer#getSelection(), which is override in this class.
+ */
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(Object)
+ */
+ @Override
+ protected void internalRefresh(Object element) {
+ /*
+ * Nothing to do here. The method internalRefresh(Object element) is only called by
+ * StructuredViewer#internalRefresh(Object element, boolean updateLabels), which is override in
+ * AbstractTreeViewer.
+ */
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(List, boolean)
+ */
+ @Override
+ protected void setSelectionToWidget(List l, boolean reveal) {
+ /*
+ * Nothing to do here. The method setSelectionToWidget(List l, boolean reveal) is only called by
+ * StructuredViewer#setSelectionToWidget(ISelection selection, boolean reveal), which is override in
+ * AbstractTreeViewer.
+ */
+ }
+
+ /**
+ * Adds event listener hooks to the given control.
+ * <p>
+ * All subclasses must call this method when their control is first established.
+ * </p>
+ * <p>
+ * The <code>ContentViewer</code> implementation of this method hooks dispose events for the given
+ * control. Subclasses may override if they need to add other control hooks; however,
+ * <code>super.hookControl</code> must be invoked.
+ * </p>
+ *
+ * @param control
+ * the control
+ */
+ @Override
+ protected void hookControl(Control control) {
+ control.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent event) {
+ handleDispose(event);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#handleDispose(DisposeEvent)
+ */
+ @Override
+ protected void handleDispose(DisposeEvent event) {
+ fViewer.removeSelectionChangedListener(fWrappedViewerListener);
+ }
+
+ /**
+ * Returns the {@link StructuredViewer} associated with this wrapper.
+ *
+ * @return a StructuredViewer.
+ */
+ protected StructuredViewer getViewer() {
+ return fViewer;
+ }
+
+ /**
+ * Set the {@link StructuredViewer} of this wrapper.
+ *
+ * @param fViewer
+ * a StructuredViewer.
+ */
+ protected void setViewer(StructuredViewer viewer) {
+ fViewer = viewer;
+ }
+
+ /**
+ * Get the compare configuration object.
+ *
+ * @return the compare configuration object.
+ */
+ public CompareConfiguration getCompareConfiguration() {
+ return fConfiguration;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeRuler.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeRuler.java
new file mode 100644
index 000000000..7cefbdb91
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeRuler.java
@@ -0,0 +1,631 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * A specific canvas that must be presented next to a TreeViewer. It shows consequences of a Diff (required
+ * and unmergeable differences), as in the TreeViewer, but in a compact format (small colored rectangles) and
+ * with links to respectives Treeitems.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class EMFCompareDiffTreeRuler extends Canvas {
+
+ /** The vertical offset for an annotation. */
+ private static final int Y_OFFSET = 6;
+
+ /** The height of an annotation. */
+ private static final int ANNOTATION_HEIGHT = 5;
+
+ /** The TreeViewer associated with this Treeruler. */
+ private final TreeViewer fTreeViewer;
+
+ /** The color a required diff. */
+ private final Color requiredDiffFillColor;
+
+ /** The color of an unmergeable diff. **/
+ private final Color unmergeableDiffFillColor;
+
+ /** The border color a required diff. */
+ private final Color requiredDiffBorderColor;
+
+ /** The border color an unmergeable diff. */
+ private final Color unmergeableDiffBorderColor;
+
+ /** The width of this tree ruler. */
+ private final int fWidth;
+
+ /** The list of required Diff that need to be shown in the TreeRuler. */
+ private Set<Diff> requires;
+
+ /** The list of unmergeables Diff that need to be shown in the TreeRuler. */
+ private Set<Diff> unmergeables;
+
+ /** A map that links a diff with tree items. */
+ private Multimap<Diff, TreeItem> diffItems;
+
+ /** A map that links a rectangle with a tree item. */
+ private Map<Rectangle, TreeItem> annotationsData;
+
+ /** The paint listener. */
+ private PaintListener paintListener;
+
+ /** The mouse click listener. */
+ private MouseListener mouseClickListener;
+
+ /** The mouse move listener. */
+ private MouseMoveListener mouseMoveListener;
+
+ /** The mouse track listener. */
+ private MouseTrackListener mouseTrackListener;
+
+ /** The last cursor used. */
+ private Cursor lastCursor;
+
+ /** The configuration for this control. */
+ private CompareConfiguration fConfiguration;
+
+ /** The selected diff in the Treeviewer associated with this Treeruler. */
+ private Diff selectedDiff;
+
+ /**
+ * Constructor.
+ *
+ * @param parent
+ * the control's parent.
+ * @param style
+ * the style of the control to construct.
+ * @param width
+ * the control's width.
+ * @param treeViewer
+ * the TreeViewer associated with this control.
+ * @param config
+ * the configuration for this control.
+ */
+ EMFCompareDiffTreeRuler(Composite parent, int style, int width, TreeViewer treeViewer,
+ CompareConfiguration config) {
+ super(parent, style);
+ fWidth = width;
+ fTreeViewer = treeViewer;
+ fConfiguration = config;
+
+ requiredDiffFillColor = JFaceResources.getColorRegistry().get(
+ EMFCompareDiffTreeViewer.REQUIRED_DIFF_COLOR);
+ requiredDiffBorderColor = JFaceResources.getColorRegistry().get(
+ EMFCompareDiffTreeViewer.REQUIRED_DIFF_BORDER_COLOR);
+ unmergeableDiffFillColor = JFaceResources.getColorRegistry().get(
+ EMFCompareDiffTreeViewer.UNMERGEABLE_DIFF_COLOR);
+ unmergeableDiffBorderColor = JFaceResources.getColorRegistry().get(
+ EMFCompareDiffTreeViewer.UNMERGEABLE_DIFF_BORDER_COLOR);
+
+ requires = Sets.newHashSet();
+ unmergeables = Sets.newHashSet();
+ diffItems = HashMultimap.create();
+ annotationsData = Maps.newHashMap();
+
+ paintListener = new PaintListener() {
+ public void paintControl(PaintEvent e) {
+ handlePaintEvent(e);
+ }
+ };
+ addPaintListener(paintListener);
+
+ mouseClickListener = new MouseListener() {
+
+ public void mouseUp(MouseEvent e) {
+ handleMouseClickEvent(e);
+ }
+
+ public void mouseDown(MouseEvent e) {
+ // Do nothing.
+ }
+
+ public void mouseDoubleClick(MouseEvent e) {
+ // Do nothing.
+ }
+ };
+ addMouseListener(mouseClickListener);
+
+ mouseMoveListener = new MouseMoveListener() {
+
+ public void mouseMove(MouseEvent e) {
+ handleMouveMoveEvent(e);
+ }
+ };
+ addMouseMoveListener(mouseMoveListener);
+
+ mouseTrackListener = new MouseTrackListener() {
+
+ public void mouseHover(MouseEvent e) {
+ handleMouseHoverEvent(e);
+ }
+
+ public void mouseExit(MouseEvent e) {
+ // Do nothing.
+ }
+
+ public void mouseEnter(MouseEvent e) {
+ // Do nothing.
+ }
+ };
+ addMouseTrackListener(mouseTrackListener);
+ }
+
+ /**
+ * Compute consequences (required and unmergeable differences) when selection changed occurs.
+ *
+ * @param event
+ * the SelectionChangedEvent event.
+ */
+ public void selectionChanged(SelectionChangedEvent event) {
+ clearAllData();
+ ISelection selection = event.getSelection();
+ if (selection instanceof IStructuredSelection) {
+ Object element = ((IStructuredSelection)selection).getFirstElement();
+ if (element instanceof Adapter) {
+ Object target = ((Adapter)element).getTarget();
+ if (target instanceof Diff) {
+ selectedDiff = (Diff)target;
+ computeConsequences();
+ }
+ }
+ }
+ }
+
+ /**
+ * Compute consequences (required and unmergeable differences).
+ */
+ public void computeConsequences() {
+ clearAllData();
+ if (selectedDiff != null) {
+ Boolean leftToRight = (Boolean)fConfiguration.getProperty(EMFCompareConstants.MERGE_WAY);
+ boolean ltr = false;
+ if (leftToRight == null || leftToRight.booleanValue()) {
+ ltr = true;
+ }
+ boolean leftEditable = fConfiguration.isLeftEditable();
+ boolean rightEditable = fConfiguration.isRightEditable();
+ boolean bothSidesEditable = leftEditable && rightEditable;
+ if ((ltr && (leftEditable || bothSidesEditable)) || (!ltr && (rightEditable && !leftEditable))) {
+ requires = DiffUtil.getRequires(selectedDiff, true, selectedDiff.getSource());
+ unmergeables = DiffUtil.getUnmergeables(selectedDiff, true);
+ } else {
+ requires = DiffUtil.getRequires(selectedDiff, false, selectedDiff.getSource());
+ unmergeables = DiffUtil.getUnmergeables(selectedDiff, false);
+ }
+ associateTreeItems(Lists.newLinkedList(Iterables.concat(requires, unmergeables)));
+ }
+ }
+
+ /**
+ * Maps tree items with the given list of diffs.
+ *
+ * @param diffs
+ * the given list of diffs.
+ */
+ private void associateTreeItems(List<Diff> diffs) {
+ Tree tree = fTreeViewer.getTree();
+ for (TreeItem item : tree.getItems()) {
+ associateTreeItem(item, diffs);
+ }
+ }
+
+ /**
+ * Maps, if necessary, the given tree item and all his children with the given list of diffs.
+ *
+ * @param item
+ * the given tree item.
+ * @param diffs
+ * the given list of diffs.
+ */
+ private void associateTreeItem(TreeItem item, List<Diff> diffs) {
+ Object data = item.getData();
+ if (data instanceof Adapter) {
+ Notifier target = ((Adapter)data).getTarget();
+ if (diffs.contains(target)) {
+ diffItems.put((Diff)target, item);
+ }
+ }
+ for (TreeItem child : item.getItems()) {
+ associateTreeItem(child, diffs);
+ }
+ }
+
+ /**
+ * Clear all data.
+ */
+ private void clearAllData() {
+ requires.clear();
+ unmergeables.clear();
+ diffItems.clear();
+ annotationsData.clear();
+ }
+
+ /**
+ * Handles the dispose event on this control.
+ */
+ public void handleDispose() {
+ removeMouseTrackListener(mouseTrackListener);
+ removeMouseMoveListener(mouseMoveListener);
+ removeMouseListener(mouseClickListener);
+ removePaintListener(paintListener);
+ }
+
+ /**
+ * Handles the paint event.
+ *
+ * @param e
+ * the paint event.
+ */
+ private void handlePaintEvent(PaintEvent e) {
+ annotationsData.clear();
+ Collection<IDifferenceFilter> filters = (Collection<IDifferenceFilter>)fConfiguration
+ .getProperty(EMFCompareConstants.SELECTED_FILTERS);
+ Collection<? extends Diff> filteredRequires = filteredDiffs(requires, filters);
+ Collection<? extends Diff> filteredUnmergeables = filteredDiffs(unmergeables, filters);
+ for (Diff diff : filteredRequires) {
+ for (TreeItem item : diffItems.get(diff)) {
+ createAnnotation(e, diff, item, requiredDiffFillColor, requiredDiffBorderColor);
+ }
+ }
+ for (Diff diff : filteredUnmergeables) {
+ for (TreeItem item : diffItems.get(diff)) {
+ createAnnotation(e, diff, item, unmergeableDiffFillColor, unmergeableDiffBorderColor);
+ }
+ }
+ }
+
+ /**
+ * Handles the mouse click event.
+ *
+ * @param e
+ * the mouse click event.
+ */
+ private void handleMouseClickEvent(MouseEvent e) {
+ for (Rectangle rect : annotationsData.keySet()) {
+ if (e.y >= rect.y && e.y <= rect.y + ANNOTATION_HEIGHT) {
+ TreeItem item = annotationsData.get(rect);
+ TreePath treePath = getTreePathFromItem(item);
+ fTreeViewer.expandToLevel(treePath, 0);
+ fTreeViewer.reveal(treePath);
+ if (isVerticalScrollBarEnabled()) {
+ TreeItem previousItem = getPreviousItem(item, 2);
+ fTreeViewer.getTree().setTopItem(previousItem);
+ }
+ redraw();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Handles the mouse move event.
+ *
+ * @param e
+ * the mouse move event.
+ */
+ private void handleMouveMoveEvent(MouseEvent e) {
+ Cursor cursor = null;
+ for (Rectangle rect : annotationsData.keySet()) {
+ if (e.y >= rect.y && e.y <= rect.y + ANNOTATION_HEIGHT) {
+ cursor = e.display.getSystemCursor(SWT.CURSOR_HAND);
+ break;
+ }
+ }
+ if (cursor != lastCursor) {
+ setCursor(cursor);
+ lastCursor = cursor;
+ }
+ }
+
+ /**
+ * Handles the mouse hover event.
+ *
+ * @param e
+ * the mouve hover event.
+ */
+ private void handleMouseHoverEvent(MouseEvent e) {
+ String overview = ""; //$NON-NLS-1$
+ for (Rectangle rect : annotationsData.keySet()) {
+ if (e.y >= rect.y && e.y <= rect.y + ANNOTATION_HEIGHT) {
+ TreeItem item = annotationsData.get(rect);
+ overview = item.getText();
+ break;
+ }
+ }
+ setToolTipText(overview);
+ }
+
+ /**
+ * Create an annotation in the tree ruler.
+ *
+ * @param e
+ * the PaintEvent.
+ * @param diff
+ * the Diff for which we want to create the annotation.
+ * @param treeItem
+ * the tree item associated with the diff.
+ * @param fill
+ * the annotation's fill color.
+ * @param border
+ * the annotation's border color.
+ */
+ private void createAnnotation(PaintEvent e, Diff diff, TreeItem treeItem, Color fill, Color border) {
+ TreeItem item = getDeepestVisibleTreeItem(treeItem, treeItem);
+ if (item != null) {
+ int y = item.getBounds().y;
+ int yRuler = getSize().y;
+ if (isVerticalScrollBarEnabled()) {
+ int yMin = Math.abs(item.getParent().getItems()[0].getBounds().y);
+ int yMax = getLastVisibleItem().getBounds().y;
+ int realYMax = yMax + yMin;
+ y = (y + yMin) * yRuler / realYMax;
+ if (y + Y_OFFSET + ANNOTATION_HEIGHT > yRuler) {
+ y = yRuler - Y_OFFSET - ANNOTATION_HEIGHT;
+ }
+ }
+ Rectangle rect = drawAnnotation(e.gc, 2, y + Y_OFFSET, fWidth - 5, ANNOTATION_HEIGHT, fill,
+ border);
+ annotationsData.put(rect, treeItem);
+ }
+ }
+
+ /**
+ * Returns the full tree path of the given tree item.
+ *
+ * @param item
+ * the given tree item.
+ * @return the full tree path of the given tree item.
+ */
+ private TreePath getTreePathFromItem(TreeItem item) {
+ LinkedList<Object> segments = Lists.newLinkedList();
+ TreeItem parent = item;
+ while (parent != null) {
+ Object segment = parent.getData();
+ Assert.isNotNull(segment);
+ segments.addFirst(segment);
+ parent = parent.getParentItem();
+ }
+ return new TreePath(segments.toArray());
+ }
+
+ /**
+ * Checks if the vertical scroll bar of the tree viewer associated with this tree ruler is activated and
+ * enabled.
+ *
+ * @return true if the vertical scroll bar is activated and enabled, false otherwise.
+ */
+ private boolean isVerticalScrollBarEnabled() {
+ ScrollBar verticalBar = fTreeViewer.getTree().getVerticalBar();
+ if (verticalBar != null) {
+ return verticalBar.isVisible() && verticalBar.isEnabled();
+ }
+ return false;
+ }
+
+ /**
+ * Draw an annotation (a Rectangle) on this tree ruler.
+ *
+ * @param gc
+ * the swt GC.
+ * @param x
+ * the x coordinate of the origin of the annotation.
+ * @param y
+ * the y coordinate of the origin of the annotation.
+ * @param w
+ * the width of the annotation.
+ * @param h
+ * the height of the annotation.
+ * @param fill
+ * the annotation's fill color.
+ * @param border
+ * the annotation's border color.
+ * @return the annotation (a Rectangle).
+ */
+ private Rectangle drawAnnotation(GC gc, int x, int y, int w, int h, Color fill, Color border) {
+ Rectangle rect = new Rectangle(x, y, w, h);
+ gc.setBackground(fill);
+ gc.fillRectangle(rect);
+
+ gc.setForeground(border);
+ gc.drawRectangle(x, y, w, h);
+ return rect;
+ }
+
+ /**
+ * Returns, for the given tree item, the deepest visible {@link TreeItem} in the Treeviewer associated
+ * with this TreeRuler.
+ *
+ * @param currentItem
+ * the given tree item.
+ * @param deepestVisibleItem
+ * the deepest visible tree item (a parent or the item itself) of the given item.
+ * @return the deepest visible tree item (a parent or the item itself).
+ */
+ private TreeItem getDeepestVisibleTreeItem(final TreeItem currentItem, final TreeItem deepestVisibleItem) {
+ TreeItem item = null;
+ TreeItem parent = currentItem.getParentItem();
+ if (parent == null) {
+ item = deepestVisibleItem;
+ } else if (parent.getExpanded()) {
+ item = getDeepestVisibleTreeItem(parent, deepestVisibleItem);
+ } else {
+ item = getDeepestVisibleTreeItem(parent, parent);
+ }
+ return item;
+ }
+
+ /**
+ * Get the previous item of the given {@link TreeItem}.
+ *
+ * @param treeItem
+ * the given {@link TreeItem}.
+ * @param index
+ * the index of the previous item.
+ * @return the previous item of the given {@link TreeItem}.
+ */
+ private TreeItem getPreviousItem(TreeItem treeItem, int index) {
+ TreeItem previousItem = treeItem;
+ if (index > 0) {
+ TreeItem parentItem = treeItem.getParentItem();
+ if (parentItem != null) {
+ int treeItemIndex = 0;
+ for (TreeItem siblingItem : parentItem.getItems()) {
+ if (siblingItem.equals(treeItem)) {
+ break;
+ }
+ treeItemIndex++;
+ }
+ if (treeItemIndex == 0) {
+ previousItem = getPreviousItem(parentItem, index - 1);
+ } else if (treeItemIndex == 1) {
+ TreeItem firstChild = parentItem.getItem(0);
+ previousItem = getLastVisibleItem(firstChild);
+ previousItem = getPreviousItem(previousItem, index - 1);
+ } else {
+ previousItem = getPreviousItem(getLastVisibleItem(parentItem.getItem(treeItemIndex - 1)),
+ index - 1);
+ }
+ } else {
+ // It is a root item. May be there are some previous root items.
+ Tree tree = treeItem.getParent();
+ int treeItemIndex = 0;
+ for (TreeItem siblingItem : tree.getItems()) {
+ if (siblingItem.equals(treeItem)) {
+ break;
+ }
+ treeItemIndex++;
+ }
+ if (treeItemIndex == 0) {
+ previousItem = treeItem;
+ } else if (treeItemIndex == 1) {
+ TreeItem firstRoot = tree.getItem(0);
+ if (firstRoot.getExpanded()) {
+ previousItem = getLastVisibleItem(firstRoot);
+ } else {
+ previousItem = firstRoot;
+ }
+ previousItem = getPreviousItem(previousItem, index - 1);
+ } else {
+ previousItem = tree.getItem(treeItemIndex - index);
+ }
+ }
+ }
+ return previousItem;
+ }
+
+ /**
+ * Returns the last visible {@link TreeItem} in the Treeviewer associated with this TreeRuler.
+ *
+ * @return the last visible TreeItem in the Treeviewer associated with this TreeRuler.
+ */
+ private TreeItem getLastVisibleItem() {
+ int rootChildren = fTreeViewer.getTree().getItemCount();
+ return getLastVisibleItem(fTreeViewer.getTree().getItem(rootChildren - 1));
+ }
+
+ /**
+ * Returns the last visible child of the given {@link TreeItem} in the Treeviewer associated with this
+ * TreeRuler.
+ *
+ * @param item
+ * teh given TreeItem.
+ * @return the last visible child of the given TreeItem in the Treeviewer associated with this TreeRuler.
+ */
+ private TreeItem getLastVisibleItem(TreeItem item) {
+ TreeItem lastVisibleItem = null;
+ int directChildren = item.getItemCount();
+ if (directChildren == 0) {
+ lastVisibleItem = item;
+ } else {
+ TreeItem lastDirectChildren = item.getItem(directChildren - 1);
+ if (lastDirectChildren.getData() == null) {
+ lastVisibleItem = item;
+ } else if (lastDirectChildren.getExpanded()) {
+ lastVisibleItem = getLastVisibleItem(lastDirectChildren);
+ } else {
+ lastVisibleItem = lastDirectChildren;
+ }
+ }
+ return lastVisibleItem;
+ }
+
+ /**
+ * From a list of {@link Diff}s, returns the diffs which are not filtered by a filter of the given list of
+ * {@link IDifferenceFilter}.
+ *
+ * @param unfilteredDiffs
+ * the given list of unfiltered diffs.
+ * @param filters
+ * the given list of IDifferenceFilter.
+ * @return A filtered list of diffs.
+ */
+ protected Collection<? extends Diff> filteredDiffs(Collection<? extends Diff> unfilteredDiffs,
+ Collection<IDifferenceFilter> filters) {
+ if (filters != null) {
+ List<Diff> filteredDiffs = Lists.newArrayList(unfilteredDiffs);
+ for (IDifferenceFilter filter : filters) {
+ for (Diff unfilteredDiff : unfilteredDiffs) {
+ if (filter.getPredicateWhenSelected().apply(unfilteredDiff)) {
+ filteredDiffs.remove(unfilteredDiff);
+ }
+ }
+ }
+ return filteredDiffs;
+ }
+ return unfilteredDiffs;
+
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeViewer.java
new file mode 100644
index 000000000..d5171601d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareDiffTreeViewer.java
@@ -0,0 +1,641 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
+
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareViewerPane;
+import org.eclipse.compare.CompareViewerSwitchingPane;
+import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.AdapterFactory;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.ide.ui.internal.actions.collapse.CollapseAllModelAction;
+import org.eclipse.emf.compare.ide.ui.internal.actions.expand.ExpandAllModelAction;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.actions.FilterActionMenu;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.actions.GroupActionMenu;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilterSelectionChangeEvent;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.StructureMergeViewerFilter;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroupProvider;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.StructureMergeViewerGrouper;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.impl.DefaultGroupProvider;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
+import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.swt.widgets.Widget;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.actions.CommandAction;
+import org.eclipse.ui.menus.IMenuService;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.ui.services.IServiceLocator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+
+/**
+ * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
+ */
+public class EMFCompareDiffTreeViewer extends DiffTreeViewer {
+
+ public static final String REQUIRED_DIFF_COLOR = "RequiredDiffColor"; //$NON-NLS-1$
+
+ public static final String REQUIRED_DIFF_BORDER_COLOR = "RequiredDiffBorderColor"; //$NON-NLS-1$
+
+ public static final String UNMERGEABLE_DIFF_COLOR = "UnmergeableDiffColor"; //$NON-NLS-1$
+
+ public static final String UNMERGEABLE_DIFF_BORDER_COLOR = "UnmergeableDiffBorderColor"; //$NON-NLS-1$
+
+ private final Color requiredDiffColor;
+
+ private final Color unmergeableDiffColor;
+
+ private final ISelectionChangedListener fSelectionChangedListener;
+
+ private final CompareViewerSwitchingPane fParent;
+
+ private ToolBarManager toolbarManager;
+
+ private Object fRoot;
+
+ /**
+ * The difference filter that will be applied to the structure viewer. Note that this will be initialized
+ * from {@link #createToolItems(ToolBarManager)} since that method is called from the super-constructor
+ * and we cannot init ourselves beforehand.
+ */
+ private StructureMergeViewerFilter structureMergeViewerFilter;
+
+ /**
+ * This will be used by our adapter factory in order to group together the differences located under the
+ * Comparison. Note that this will be initialized from {@link #createToolItems(ToolBarManager)} since that
+ * method is called from the super-constructor and we cannot init ourselves beforehand.
+ */
+ private StructureMergeViewerGrouper structureMergeViewerGrouper;
+
+ private MenuManager groupsMenuManager;
+
+ private MenuManager filtersMenuManager;
+
+ private GroupActionMenu groupActionMenu;
+
+ private DefaultGroupProvider defaultGroupProvider;
+
+ private FilterActionMenu filterActionMenu;
+
+ private EventBus eventBus;
+
+ private Listener fEraseItemListener;
+
+ /**
+ * @param parent
+ * @param adapterFactory
+ * @param configuration
+ */
+ public EMFCompareDiffTreeViewer(Composite parent, AdapterFactory adapterFactory,
+ CompareConfiguration configuration) {
+ super(parent, configuration);
+
+ ToolBarManager tbm = CompareViewerPane.getToolBarManager(parent.getParent());
+ if (tbm != null) {
+ tbm.removeAll();
+
+ tbm.add(new Separator("merge")); //$NON-NLS-1$
+ tbm.add(new Separator("modes")); //$NON-NLS-1$
+ tbm.add(new Separator("navigation")); //$NON-NLS-1$
+
+ createToolItems(tbm);
+ // updateActions();
+
+ tbm.update(true);
+ }
+
+ setLabelProvider(new DelegatingStyledCellLabelProvider(
+ new EMFCompareStructureMergeViewerLabelProvider(adapterFactory, this)));
+ setContentProvider(new EMFCompareStructureMergeViewerContentProvider(adapterFactory,
+ getStructureMergeViewerGrouper(), getStructureMergeViewerFilter(), configuration));
+
+ if (parent instanceof CompareViewerSwitchingPane) {
+ fParent = (CompareViewerSwitchingPane)parent;
+ } else {
+ fParent = null;
+ }
+
+ fSelectionChangedListener = new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ getControl().redraw();
+ getCompareConfiguration()
+ .setProperty(EMFCompareConstants.SMV_SELECTION, event.getSelection());
+ if (toolbarManager != null) {
+ for (IContributionItem item : toolbarManager.getItems()) {
+ item.update();
+ }
+ }
+ }
+ };
+ addSelectionChangedListener(fSelectionChangedListener);
+
+ fEraseItemListener = new Listener() {
+ public void handleEvent(Event event) {
+ EMFCompareDiffTreeViewer.this.handleEraseItemEvent(event);
+ }
+ };
+ getControl().addListener(SWT.EraseItem, fEraseItemListener);
+
+ // Wrap the defined comparer in our own.
+ setComparer(new DiffNodeComparer(super.getComparer()));
+
+ if (eventBus == null) {
+ eventBus = new EventBus();
+ eventBus.register(this);
+ }
+
+ JFaceResources.getColorRegistry().put(REQUIRED_DIFF_COLOR, new RGB(215, 255, 200));
+ JFaceResources.getColorRegistry().put(REQUIRED_DIFF_BORDER_COLOR, new RGB(195, 235, 180));
+ JFaceResources.getColorRegistry().put(UNMERGEABLE_DIFF_COLOR, new RGB(255, 205, 180));
+ JFaceResources.getColorRegistry().put(UNMERGEABLE_DIFF_BORDER_COLOR, new RGB(235, 185, 160));
+
+ requiredDiffColor = JFaceResources.getColorRegistry().get(REQUIRED_DIFF_COLOR);
+ unmergeableDiffColor = JFaceResources.getColorRegistry().get(UNMERGEABLE_DIFF_COLOR);
+
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#getComparator()
+ */
+ @Override
+ public ViewerComparator getComparator() {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Subscribe
+ public void recordFilterSelectionChange(IDifferenceFilterSelectionChangeEvent event) {
+ final Object property = getCompareConfiguration().getProperty(EMFCompareConstants.SELECTED_FILTERS);
+ final Collection<IDifferenceFilter> selectedFilters;
+ if (property == null) {
+ selectedFilters = new HashSet<IDifferenceFilter>();
+ } else {
+ selectedFilters = new HashSet<IDifferenceFilter>((Collection<IDifferenceFilter>)property);
+ }
+ switch (event.getAction()) {
+ case ACTIVATE:
+ selectedFilters.add(event.getFilter());
+ break;
+ case DEACTIVATE:
+ selectedFilters.remove(event.getFilter());
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_FILTERS, selectedFilters);
+ }
+
+ @Subscribe
+ public void recordGroupProviderSelectionChange(IDifferenceGroupProvider differenceGroupProvider) {
+ getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_GROUP, differenceGroupProvider);
+ }
+
+ public void configurationPropertyChanged() {
+ getControl().redraw();
+ if (toolbarManager != null) {
+ for (IContributionItem item : toolbarManager.getItems()) {
+ item.update();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#getRoot()
+ */
+ @Override
+ public Object getRoot() {
+ return fRoot;
+ }
+
+ public void setRoot(Object root) {
+ fRoot = root;
+ }
+
+ public void createChildrenSilently(Object o) {
+ if (o instanceof Tree) {
+ createChildren((Widget)o);
+ for (TreeItem item : ((Tree)o).getItems()) {
+ createChildrenSilently(item);
+ }
+ } else if (o instanceof TreeItem) {
+ createChildren((Widget)o);
+ for (TreeItem item : ((TreeItem)o).getItems()) {
+ createChildrenSilently(item);
+ }
+ }
+ }
+
+ @Override
+ public void initialSelection() {
+ super.initialSelection();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.jface.viewers.AbstractTreeViewer#isExpandable(java.lang.Object)
+ */
+ @Override
+ public boolean isExpandable(Object parent) {
+ if (hasFilters()) {
+ // workaround for 65762
+ return hasFilteredChildren(parent);
+ }
+ return super.isExpandable(parent);
+ }
+
+ /**
+ * Public method to test if a element has any children that passed the filters
+ *
+ * @param parent
+ * the element to test
+ * @return return <code>true</code> if the element has at least a child that passed the filters
+ */
+ public final boolean hasFilteredChildren(Object parent) {
+ Object[] rawChildren = getRawChildren(parent);
+ return containsNonFiltered(rawChildren, parent);
+ }
+
+ private boolean containsNonFiltered(Object[] elements, Object parent) {
+ if (elements.length == 0) {
+ return false;
+ }
+ if (!hasFilters()) {
+ return true;
+ }
+ ViewerFilter[] filters = getFilters();
+ for (int i = 0; i < elements.length; i++) {
+ Object object = elements[i];
+ if (!isFiltered(object, parent, filters)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * All element filter tests must go through this method. Can be overridden by subclasses.
+ *
+ * @param object
+ * the object to filter
+ * @param parent
+ * the parent
+ * @param filters
+ * the filters to apply
+ * @return true if the element is filtered
+ */
+ protected boolean isFiltered(Object object, Object parent, ViewerFilter[] filters) {
+ for (int i = 0; i < filters.length; i++) {
+ ViewerFilter filter = filters[i];
+ if (!filter.select(this, parent, object)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void refreshAfterDiff(String message, Object root) {
+ if (getControl().isDisposed()) {
+ return;
+ }
+
+ if (fParent != null) {
+ fParent.setTitleArgument(message);
+ }
+
+ refresh(root);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.StructuredViewer#setComparer(org.eclipse.jface.viewers.IElementComparer)
+ */
+ @Override
+ public void setComparer(IElementComparer comparer) {
+ // Wrap this new comparer in our own
+ super.setComparer(new DiffNodeComparer(comparer));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.compare.structuremergeviewer.DiffTreeViewer#createToolItems(org.eclipse.jface.action.ToolBarManager)
+ */
+ @Override
+ protected void createToolItems(ToolBarManager toolbarManager) {
+
+ this.toolbarManager = toolbarManager;
+
+ super.createToolItems(toolbarManager);
+
+ // Add extension point contributions to the structure merge viewer toolbar
+ IServiceLocator workbench = PlatformUI.getWorkbench();
+ IMenuService menuService = (IMenuService)workbench.getService(IMenuService.class);
+ if (menuService != null) {
+ menuService.populateContributionManager(toolbarManager,
+ "toolbar:org.eclipse.emf.compare.structuremergeviewer.toolbar");
+ }
+
+ Bundle uiWorkbenchBundle = Platform.getBundle("org.eclipse.ui.workbench"); //$NON-NLS-1$
+ Version junoStart = Version.parseVersion("3.103"); //$NON-NLS-1$
+
+ // XXX MBA change to 3.105 once bug #366528 is fixed
+ Version keplerStart = Version.parseVersion("3.105"); //$NON-NLS-1$
+
+ if (uiWorkbenchBundle != null && uiWorkbenchBundle.getVersion().compareTo(junoStart) >= 0
+ && uiWorkbenchBundle.getVersion().compareTo(keplerStart) < 0) {
+ IAction action = new CommandAction(PlatformUI.getWorkbench(),
+ "org.eclipse.emf.compare.ide.ui.saveComparisonModel"); //$NON-NLS-1$
+ action.setToolTipText("Save Comparison model"); //$NON-NLS-1$
+ action.setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
+ EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/saveas_edit.gif")); //$NON-NLS-1$
+ toolbarManager.add(action);
+ }
+
+ groupActionMenu = new GroupActionMenu(getStructureMergeViewerGrouper(), getGroupsMenuManager(),
+ getDefaultGroupProvider());
+ filterActionMenu = new FilterActionMenu(getStructureMergeViewerFilter(), getFiltersMenuManager());
+
+ toolbarManager.add(new Separator());
+ toolbarManager.add(new ExpandAllModelAction(this));
+ toolbarManager.add(new CollapseAllModelAction(this));
+ toolbarManager.add(new Separator());
+ toolbarManager.add(groupActionMenu);
+ toolbarManager.add(filterActionMenu);
+ }
+
+ /**
+ * Returns the viewer filter that is to be applied on the structure viewer.
+ * <p>
+ * Note that this will be called from {@link #createToolItems(ToolBarManager)}, which is called from the
+ * super-constructor, when we have had no time to initialize the {@link #structureMergeViewerFilter}
+ * field.
+ * </p>
+ *
+ * @return The difference filter that is to be applied on the structure viewer.
+ */
+ protected StructureMergeViewerFilter getStructureMergeViewerFilter() {
+ if (structureMergeViewerFilter == null) {
+ if (eventBus == null) {
+ eventBus = new EventBus();
+ eventBus.register(this);
+ }
+ structureMergeViewerFilter = new StructureMergeViewerFilter(eventBus);
+ structureMergeViewerFilter.install(this);
+ }
+ return structureMergeViewerFilter;
+ }
+
+ /**
+ * Returns the viewer grouper that is to be applied on the structure viewer.
+ * <p>
+ * Note that this will be called from {@link #createToolItems(ToolBarManager)}, which is called from the
+ * super-constructor, when we have had no time to initialize the {@link #structureMergeViewerGrouper}
+ * field.
+ * </p>
+ *
+ * @return The viewer grouper grouper that is to be applied on the structure viewer.
+ */
+ protected StructureMergeViewerGrouper getStructureMergeViewerGrouper() {
+ if (structureMergeViewerGrouper == null) {
+ if (eventBus == null) {
+ eventBus = new EventBus();
+ eventBus.register(this);
+ }
+ structureMergeViewerGrouper = new StructureMergeViewerGrouper(eventBus);
+ structureMergeViewerGrouper.install(this);
+ }
+ return structureMergeViewerGrouper;
+ }
+
+ /**
+ * Returns the menu manager that is to be applied to groups on the structure viewer.
+ *
+ * @return The menu manager that is to be applied to groups on the structure viewer.
+ */
+ public MenuManager getGroupsMenuManager() {
+ if (groupsMenuManager == null) {
+ groupsMenuManager = new MenuManager();
+ }
+ return groupsMenuManager;
+ }
+
+ /**
+ * @return the groupActionMenu
+ */
+ public GroupActionMenu getGroupActionMenu() {
+ return groupActionMenu;
+ }
+
+ /**
+ * @param groupActionMenu
+ * the groupActionMenu to set
+ */
+ public void setGroupActionMenu(GroupActionMenu groupActionMenu) {
+ this.groupActionMenu = groupActionMenu;
+ }
+
+ /**
+ * Returns the menu manager that is to be applied to filters on the structure viewer.
+ *
+ * @return The menu manager that is to be applied to filters on the structure viewer.
+ */
+ public MenuManager getFiltersMenuManager() {
+ if (filtersMenuManager == null) {
+ filtersMenuManager = new MenuManager();
+ }
+ return filtersMenuManager;
+ }
+
+ /**
+ * @return the filterActionMenu
+ */
+ public FilterActionMenu getFilterActionMenu() {
+ return filterActionMenu;
+ }
+
+ /**
+ * @param filterActionMenu
+ * the filterActionMenu to set
+ */
+ public void setFilterActionMenu(FilterActionMenu filterActionMenu) {
+ this.filterActionMenu = filterActionMenu;
+ }
+
+ /**
+ * Returns the default group provider that is to be applied on the structure viewer.
+ *
+ * @return The default group provider that is to be applied on the structure viewer.
+ */
+ public DefaultGroupProvider getDefaultGroupProvider() {
+ if (defaultGroupProvider == null) {
+ defaultGroupProvider = new DefaultGroupProvider();
+ }
+ return defaultGroupProvider;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.compare.structuremergeviewer.DiffTreeViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
+ */
+ @Override
+ protected void handleDispose(DisposeEvent event) {
+ getControl().removeListener(SWT.EraseItem, fEraseItemListener);
+ removeSelectionChangedListener(fSelectionChangedListener);
+ super.handleDispose(event);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.AbstractTreeViewer#getSortedChildren(java.lang.Object)
+ */
+ @Override
+ protected Object[] getSortedChildren(Object parentElementOrTreePath) {
+ Object[] result = super.getSortedChildren(parentElementOrTreePath);
+ if (parentElementOrTreePath instanceof Adapter
+ && ((Adapter)parentElementOrTreePath).getTarget() instanceof Conflict) {
+
+ Collections.sort(Arrays.asList(result), new Comparator<Object>() {
+ public int compare(Object o1, Object o2) {
+ return getValue(o1) - getValue(o2);
+ }
+
+ public int getValue(Object o) {
+ int value = 0;
+ if (o instanceof Adapter && ((Adapter)o).getTarget() instanceof Diff) {
+ if (((Diff)((Adapter)o).getTarget()).getSource() == DifferenceSource.LEFT) {
+ value = 1;
+ } else {
+ value = 2;
+ }
+ }
+ return value;
+ }
+ });
+
+ }
+ return result;
+ }
+
+ /**
+ * Handle the erase item event. When select a difference in the structure merge viewer, highlight required
+ * differences with a specific color, and highlight unmergeable differences with another color.
+ *
+ * @param event
+ * the erase item event.
+ */
+ protected void handleEraseItemEvent(Event event) {
+ ISelection selection = getSelection();
+ Object firstElement = ((IStructuredSelection)selection).getFirstElement();
+ if (firstElement instanceof Adapter) {
+ Notifier target = ((Adapter)firstElement).getTarget();
+ if (target instanceof Diff) {
+ TreeItem item = (TreeItem)event.item;
+ Object dataTreeItem = item.getData();
+ if (dataTreeItem instanceof Adapter) {
+ final Set<Diff> unmergeables;
+ final Set<Diff> requires;
+ Boolean leftToRight = (Boolean)getCompareConfiguration().getProperty(
+ EMFCompareConstants.MERGE_WAY);
+ boolean ltr = false;
+ if (leftToRight == null || leftToRight.booleanValue()) {
+ ltr = true;
+ }
+ boolean leftEditable = getCompareConfiguration().isLeftEditable();
+ boolean rightEditable = getCompareConfiguration().isRightEditable();
+ boolean bothSidesEditable = leftEditable && rightEditable;
+ Diff diff = (Diff)target;
+ if ((ltr && (leftEditable || bothSidesEditable))
+ || (!ltr && (rightEditable && !leftEditable))) {
+ requires = DiffUtil.getRequires(diff, true, diff.getSource());
+ unmergeables = DiffUtil.getUnmergeables(diff, true);
+ } else {
+ requires = DiffUtil.getRequires(diff, false, diff.getSource());
+ unmergeables = DiffUtil.getUnmergeables(diff, false);
+ }
+ final GC g = event.gc;
+ if (requires.contains(((Adapter)dataTreeItem).getTarget())) {
+ paintItemBackground(g, item, requiredDiffColor);
+ } else if (unmergeables.contains(((Adapter)dataTreeItem).getTarget())) {
+ paintItemBackground(g, item, unmergeableDiffColor);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Paint the background of the given item with the given color.
+ *
+ * @param g
+ * the GC associated to the item.
+ * @param item
+ * the given item.
+ * @param color
+ * the given color.
+ */
+ private void paintItemBackground(GC g, TreeItem item, Color color) {
+ Rectangle itemBounds = item.getBounds();
+ Tree tree = item.getParent();
+ Rectangle areaBounds = tree.getClientArea();
+ g.setClipping(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
+ g.setBackground(color);
+ g.fillRectangle(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
index 4895d4911..a9968c40e 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012, 2013 Obeo.
+ * Copyright (c) 2013 Obeo.
* 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
@@ -12,31 +12,23 @@ package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
import static com.google.common.collect.Iterables.getFirst;
-import com.google.common.eventbus.EventBus;
-import com.google.common.eventbus.Subscribe;
-
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.EventObject;
-import java.util.HashSet;
import java.util.Iterator;
import org.eclipse.compare.CompareConfiguration;
-import org.eclipse.compare.CompareViewerSwitchingPane;
+import org.eclipse.compare.INavigatable;
import org.eclipse.compare.ITypedElement;
-import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
+import org.eclipse.compare.internal.CompareHandlerService;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
@@ -46,17 +38,14 @@ import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Conflict;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.command.ICompareCopyCommand;
import org.eclipse.emf.compare.domain.ICompareEditingDomain;
import org.eclipse.emf.compare.domain.impl.EMFCompareEditingDomain;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
-import org.eclipse.emf.compare.ide.ui.internal.actions.collapse.CollapseAllModelAction;
-import org.eclipse.emf.compare.ide.ui.internal.actions.expand.ExpandAllModelAction;
+import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.RedoAction;
+import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.UndoAction;
import org.eclipse.emf.compare.ide.ui.internal.editor.ComparisonScopeInput;
import org.eclipse.emf.compare.ide.ui.internal.logical.ComparisonScopeBuilder;
import org.eclipse.emf.compare.ide.ui.internal.logical.IdenticalResourceMinimizer;
@@ -69,14 +58,6 @@ import org.eclipse.emf.compare.ide.ui.logical.IModelResolver;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.actions.FilterActionMenu;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.actions.GroupActionMenu;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilterSelectionChangeEvent;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.StructureMergeViewerFilter;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroupProvider;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.StructureMergeViewerGrouper;
-import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.impl.DefaultGroupProvider;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
@@ -84,70 +65,56 @@ import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.action.MenuManager;
-import org.eclipse.jface.action.Separator;
-import org.eclipse.jface.action.ToolBarManager;
-import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
-import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.jface.viewers.ITreeViewerListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.subscribers.SubscriberMergeContext;
import org.eclipse.team.internal.ui.mapping.ModelCompareEditorInput;
import org.eclipse.team.ui.synchronize.ISynchronizeParticipant;
import org.eclipse.team.ui.synchronize.ModelSynchronizeParticipant;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.internal.actions.CommandAction;
-import org.eclipse.ui.menus.IMenuService;
-import org.eclipse.ui.plugin.AbstractUIPlugin;
-import org.eclipse.ui.services.IServiceLocator;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Version;
+import org.eclipse.ui.actions.ActionFactory;
/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
+ * Implementation of {@link AbstractViewerWrapper}.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
*/
-public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements CommandStackListener {
-
- private final ICompareInputChangeListener fCompareInputChangeListener;
-
- private final ComposedAdapterFactory fAdapterFactory;
+public class EMFCompareStructureMergeViewer extends AbstractViewerWrapper implements CommandStackListener {
- private final CompareViewerSwitchingPane fParent;
+ private static final int TREE_RULER_WIDTH = 17;
- private Object fRoot;
+ private ComposedAdapterFactory fAdapterFactory;
- /**
- * The difference filter that will be applied to the structure viewer. Note that this will be initialized
- * from {@link #createToolItems(ToolBarManager)} since that method is called from the super-constructor
- * and we cannot init ourselves beforehand.
- */
- private StructureMergeViewerFilter structureMergeViewerFilter;
+ /** The tree ruler associated with this viewer. */
+ private EMFCompareDiffTreeRuler treeRuler;
- /**
- * This will be used by our adapter factory in order to group together the differences located under the
- * Comparison. Note that this will be initialized from {@link #createToolItems(ToolBarManager)} since that
- * method is called from the super-constructor and we cannot init ourselves beforehand.
- */
- private StructureMergeViewerGrouper structureMergeViewerGrouper;
+ private ICompareInputChangeListener fCompareInputChangeListener;
- private MenuManager groupsMenuManager;
+ /** The expand/collapse item listener. */
+ private ITreeViewerListener fWrappedTreeListener;
- private MenuManager filtersMenuManager;
+ /** The compare configuration property change listener. */
+ private IPropertyChangeListener fCompareConfigurationPropertyChangeListener;
- private GroupActionMenu groupActionMenu;
+ /** The tree viewer. */
+ private EMFCompareDiffTreeViewer diffTreeViewer;
- private DefaultGroupProvider defaultGroupProvider;
+ private UndoAction undoAction;
- private FilterActionMenu filterActionMenu;
+ private RedoAction redoAction;
- private EventBus eventBus;
+ private CompareHandlerService fHandlerService;
/**
* When comparing EObjects from a resource, the resource involved doesn't need to be unload by EMF
@@ -156,11 +123,27 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
private boolean resourcesShouldBeUnload;
/**
+ * Constructor.
+ *
* @param parent
- * @param configuration
+ * the SWT parent control under which to create the viewer's SWT control.
+ * @param config
+ * a compare configuration the newly created viewer might want to use.
*/
- public EMFCompareStructureMergeViewer(Composite parent, CompareConfiguration configuration) {
- super(parent, configuration);
+ public EMFCompareStructureMergeViewer(Composite parent, CompareConfiguration config) {
+ super(parent, config);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
+ * org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.ViewerWrapper.createControl(Composite,
+ * CompareConfiguration)
+ */
+ @Override
+ protected Control createControl(Composite parent, CompareConfiguration config) {
+ Composite control = new Composite(parent, SWT.NONE);
fAdapterFactory = new ComposedAdapterFactory(EMFCompareRCPPlugin.getDefault()
.getAdapterFactoryRegistry());
@@ -168,16 +151,25 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
fAdapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
fAdapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
- setLabelProvider(new DelegatingStyledCellLabelProvider(
- new EMFCompareStructureMergeViewerLabelProvider(fAdapterFactory, this)));
- setContentProvider(new EMFCompareStructureMergeViewerContentProvider(fAdapterFactory,
- getStructureMergeViewerGrouper(), getStructureMergeViewerFilter(), configuration));
-
- if (parent instanceof CompareViewerSwitchingPane) {
- fParent = (CompareViewerSwitchingPane)parent;
- } else {
- fParent = null;
- }
+ GridLayout layout = new GridLayout(2, false);
+ layout.marginWidth = 0;
+ layout.marginHeight = 0;
+ layout.horizontalSpacing = 0;
+ layout.verticalSpacing = 0;
+ GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+ control.setLayout(layout);
+ control.setLayoutData(data);
+ diffTreeViewer = new EMFCompareDiffTreeViewer(control, fAdapterFactory, config);
+ setViewer(diffTreeViewer);
+ control.setData(INavigatable.NAVIGATOR_PROPERTY, diffTreeViewer.getControl().getData(
+ INavigatable.NAVIGATOR_PROPERTY));
+ diffTreeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ GridData layoutData = new GridData(SWT.FILL, SWT.FILL, false, true);
+ layoutData.widthHint = TREE_RULER_WIDTH;
+ layoutData.minimumWidth = TREE_RULER_WIDTH;
+ treeRuler = new EMFCompareDiffTreeRuler(control, SWT.NONE, layoutData.widthHint, diffTreeViewer,
+ config);
+ treeRuler.setLayoutData(layoutData);
fCompareInputChangeListener = new ICompareInputChangeListener() {
public void compareInputChanged(ICompareInput input) {
@@ -185,79 +177,145 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
}
};
- // Wrap the defined comparer in our own.
- setComparer(new DiffNodeComparer(super.getComparer()));
+ fWrappedTreeListener = new ITreeViewerListener() {
- if (eventBus == null) {
- eventBus = new EventBus();
- eventBus.register(this);
- }
+ public void treeExpanded(TreeExpansionEvent event) {
+ treeRuler.redraw();
+ }
+
+ public void treeCollapsed(TreeExpansionEvent event) {
+ treeRuler.redraw();
+ }
+ };
+ diffTreeViewer.addTreeListener(fWrappedTreeListener);
+
+ fCompareConfigurationPropertyChangeListener = new IPropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent event) {
+ if (EMFCompareConstants.MERGE_WAY.equals(event.getProperty()) && event.getNewValue() != null) {
+ diffTreeViewer.configurationPropertyChanged();
+ treeRuler.computeConsequences();
+ treeRuler.redraw();
+ } else if (EMFCompareConstants.SELECTED_FILTERS.equals(event.getProperty())
+ && event.getNewValue() != null) {
+ diffTreeViewer.createChildrenSilently(diffTreeViewer.getTree());
+ treeRuler.computeConsequences();
+ treeRuler.redraw();
+ } else if (EMFCompareConstants.SELECTED_GROUP.equals(event.getProperty())
+ && event.getNewValue() != null) {
+ diffTreeViewer.createChildrenSilently(diffTreeViewer.getTree());
+ treeRuler.computeConsequences();
+ treeRuler.redraw();
+ }
+ }
+ };
+ getCompareConfiguration().addPropertyChangeListener(fCompareConfigurationPropertyChangeListener);
+
+ fHandlerService = CompareHandlerService.createFor(getCompareConfiguration().getContainer(),
+ diffTreeViewer.getControl().getShell());
+
+ // inputChangedTask.setPriority(Job.LONG);
- inputChangedTask.setPriority(Job.LONG);
+ return control;
}
/**
* {@inheritDoc}
*
- * @see org.eclipse.jface.viewers.StructuredViewer#getComparator()
+ * @see org.eclipse.jface.viewers.Viewer#fireSelectionChanged(SelectionChangedEvent)
*/
@Override
- public ViewerComparator getComparator() {
- return null;
+ protected void fireSelectionChanged(SelectionChangedEvent event) {
+ super.fireSelectionChanged(event);
+ treeRuler.selectionChanged(event);
+ treeRuler.redraw();
}
- @SuppressWarnings("unchecked")
- @Subscribe
- public void recordFilterSelectionChange(IDifferenceFilterSelectionChangeEvent event) {
- final Object property = getCompareConfiguration().getProperty(EMFCompareConstants.SELECTED_FILTERS);
- final Collection<IDifferenceFilter> selectedFilters;
- if (property == null) {
- selectedFilters = new HashSet<IDifferenceFilter>();
- } else {
- selectedFilters = new HashSet<IDifferenceFilter>((Collection<IDifferenceFilter>)property);
- }
- switch (event.getAction()) {
- case ACTIVATE:
- selectedFilters.add(event.getFilter());
- break;
- case DEACTIVATE:
- selectedFilters.remove(event.getFilter());
- break;
- default:
- throw new IllegalStateException();
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.jface.viewers.Viewer#inputChanged(Object, Object)
+ */
+ @Override
+ protected void inputChanged(Object input, Object oldInput) {
+ if (oldInput instanceof ICompareInput) {
+ ICompareInput old = (ICompareInput)oldInput;
+ old.removeCompareInputChangeListener(fCompareInputChangeListener);
}
- getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_FILTERS, selectedFilters);
- }
+ if (input instanceof ICompareInput) {
+ ICompareInput ci = (ICompareInput)input;
+ ci.addCompareInputChangeListener(fCompareInputChangeListener);
+
+ // Hack to display a message in the tree viewer while the differences are being computed.
+ TreeItem item = new TreeItem(diffTreeViewer.getTree(), SWT.NONE);
+ item.setText("Computing model differences...");
- @Subscribe
- public void recordGroupProviderSelectionChange(IDifferenceGroupProvider differenceGroupProvider) {
- getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_GROUP, differenceGroupProvider);
+ compareInputChanged(ci);
+ }
}
/**
- * Triggered by fCompareInputChangeListener and {@link #inputChanged(Object, Object)}.
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#handleDispose(DisposeEvent)
*/
- void compareInputChanged(ICompareInput input) {
- if (input == null) {
- // When closing, we don't need a progress monitor to handle the input change
- compareInputChanged((ICompareInput)null, new NullProgressMonitor());
- return;
+ @Override
+ protected void handleDispose(DisposeEvent event) {
+ if (fHandlerService != null) {
+ fHandlerService.dispose();
}
- CompareConfiguration cc = getCompareConfiguration();
- // The compare configuration is nulled when the viewer is disposed
- if (cc != null) {
- inputChangedTask.schedule();
+ getCompareConfiguration().removePropertyChangeListener(fCompareConfigurationPropertyChangeListener);
+ diffTreeViewer.removeTreeListener(fWrappedTreeListener);
+ Object input = getInput();
+ if (input instanceof ICompareInput) {
+ ICompareInput ci = (ICompareInput)input;
+ ci.removeCompareInputChangeListener(fCompareInputChangeListener);
}
+ compareInputChanged((ICompareInput)null);
+ treeRuler.handleDispose();
+ fAdapterFactory.dispose();
+ super.handleDispose(event);
}
/**
* {@inheritDoc}
*
- * @see org.eclipse.jface.viewers.StructuredViewer#getRoot()
+ * @see org.eclipse.emf.common.command.CommandStackListener#commandStackChanged(java.util.EventObject)
*/
- @Override
- protected Object getRoot() {
- return fRoot;
+ public void commandStackChanged(EventObject event) {
+ if (undoAction != null) {
+ undoAction.update();
+ }
+ if (redoAction != null) {
+ redoAction.update();
+ }
+
+ Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
+ if (mostRecentCommand instanceof ICompareCopyCommand) {
+ Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
+
+ SWTUtil.safeAsyncExec(new Runnable() {
+ public void run() {
+ refresh(true);
+ diffTreeViewer.createChildrenSilently(diffTreeViewer.getTree());
+ treeRuler.computeConsequences();
+ treeRuler.redraw();
+ }
+ });
+ if (!affectedObjects.isEmpty()) {
+ // MUST NOT call a setSelection with a list, o.e.compare does not handle it (cf
+ // org.eclipse.compare.CompareEditorInput#getElement(ISelection))
+ final Object adaptedAffectedObject = fAdapterFactory.adapt(getFirst(affectedObjects, null),
+ ICompareInput.class);
+ SWTUtil.safeAsyncExec(new Runnable() {
+ public void run() {
+ setSelectionToWidget(new StructuredSelection(adaptedAffectedObject), true);
+ }
+ });
+ }
+ } else {
+ // FIXME, should recompute the difference, something happened outside of this compare editor
+ }
+
}
private Job inputChangedTask = new Job("Compute Model Differences") {
@@ -270,53 +328,77 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
};
/**
- * Team left us with absolutely no way to determine whether our supplied input is the result of a
- * synchronization or not.
- * <p>
- * In order to properly resolve the logical model of the resource currently being compared we need to know
- * what "other" resources were part of its logical model, and we need to know the revisions of these
- * resources we are to load. All of this has already been computed by Team, but it would not let us know.
- * This method uses discouraged means to get around this "black box" locking from Team.
- * </p>
- * <p>
- * The basic need here is to retrieve the Subscriber from this point. We have a lot of accessible
- * variables, the two most important being the CompareConfiguration and ICompareInput... I could find no
- * way around the privileged access to the private ModelCompareEditorInput.participant field. There does
- * not seem to be any adapter (or Platform.getAdapterManager().getAdapter(...)) that would allow for this,
- * so I'm taking the long way 'round.
- * </p>
- *
- * @return The subscriber used for this comparison if any could be found, <code>null</code> otherwise.
+ * Triggered by fCompareInputChangeListener and {@link #inputChanged(Object, Object)}.
*/
- @SuppressWarnings("restriction")
- private Subscriber getSubscriber() {
- if (getCompareConfiguration().getContainer() instanceof ModelCompareEditorInput) {
- final ModelCompareEditorInput modelInput = (ModelCompareEditorInput)getCompareConfiguration()
- .getContainer();
- ISynchronizeParticipant participant = null;
- try {
- final Field field = ModelCompareEditorInput.class.getDeclaredField("participant"); //$NON-NLS-1$
- AccessController.doPrivileged(new PrivilegedAction<Object>() {
- public Object run() {
- field.setAccessible(true);
- return null;
- }
- });
- participant = (ISynchronizeParticipant)field.get(modelInput);
- } catch (NoSuchFieldException e) {
- // Swallow this, this private field was there at least from 3.5 to 4.3
- } catch (IllegalArgumentException e) {
- // Cannot happen
- } catch (IllegalAccessException e) {
- // "Should" not happen, but ignore it anyway
- }
- if (participant instanceof ModelSynchronizeParticipant
- && ((ModelSynchronizeParticipant)participant).getContext() instanceof SubscriberMergeContext) {
- return ((SubscriberMergeContext)((ModelSynchronizeParticipant)participant).getContext())
- .getSubscriber();
+ void compareInputChanged(ICompareInput input) {
+ if (input == null) {
+ // When closing, we don't need a progress monitor to handle the input change
+ compareInputChanged((ICompareInput)null, new NullProgressMonitor());
+ return;
+ }
+ CompareConfiguration cc = getCompareConfiguration();
+ // The compare configuration is nulled when the viewer is disposed
+ if (cc != null) {
+ inputChangedTask.schedule();
+ }
+ }
+
+ void compareInputChanged(ComparisonNode input, IProgressMonitor monitor) {
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty(
+ EMFCompareConstants.EDITING_DOMAIN);
+ editingDomain.getCommandStack().addCommandStackListener(this);
+
+ compareInputChanged(null, input.getTarget());
+ }
+
+ void compareInputChanged(ComparisonScopeInput input, IProgressMonitor monitor) {
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty(
+ EMFCompareConstants.EDITING_DOMAIN);
+ editingDomain.getCommandStack().addCommandStackListener(this);
+
+ EMFCompare comparator = (EMFCompare)getCompareConfiguration().getProperty(
+ EMFCompareConstants.COMPARATOR);
+
+ IComparisonScope comparisonScope = input.getComparisonScope();
+ Comparison comparison = comparator.compare(comparisonScope, BasicMonitor.toMonitor(monitor));
+ compareInputChanged(input.getComparisonScope(), comparison);
+ }
+
+ void compareInputChanged(final IComparisonScope scope, final Comparison comparison) {
+ if (!getControl().isDisposed()) { // guard against disposal
+ diffTreeViewer.setRoot(fAdapterFactory.adapt(comparison, ICompareInput.class));
+ getCompareConfiguration().setProperty(EMFCompareConstants.COMPARE_RESULT, comparison);
+
+ String message = null;
+ if (comparison.getDifferences().isEmpty()) {
+ message = "No Differences";
}
+
+ final String theMessage = message;
+ SWTUtil.safeAsyncExec(new Runnable() {
+ public void run() {
+ if (diffTreeViewer.getGroupActionMenu() != null) {
+ diffTreeViewer.getGroupActionMenu().createActions(scope, comparison);
+ }
+ if (diffTreeViewer.getFilterActionMenu() != null) {
+ diffTreeViewer.getFilterActionMenu().createActions(scope, comparison);
+ }
+ diffTreeViewer.refreshAfterDiff(theMessage, diffTreeViewer.getRoot());
+ // Mandatory for the EMFCompareDiffTreeRuler, all TreeItems must have been created
+ diffTreeViewer.createChildrenSilently(diffTreeViewer.getTree());
+ diffTreeViewer.initialSelection();
+ }
+ });
+
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)getCompareConfiguration()
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+
+ undoAction = new UndoAction(editingDomain);
+ redoAction = new RedoAction(editingDomain);
+
+ fHandlerService.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
+ fHandlerService.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
}
- return null;
}
void compareInputChanged(ICompareInput input, IProgressMonitor monitor) {
@@ -337,7 +419,6 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
final IComparisonScope scope = buildComparisonScope(left, right, origin, subMonitor
.newChild(85));
-
final Comparison compareResult = EMFCompare
.builder()
.setMatchEngineFactoryRegistry(
@@ -370,8 +451,8 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
ResourceSet rightResourceSet = null;
ResourceSet originResourceSet = null;
- if (fRoot != null) {
- Comparison comparison = (Comparison)((Adapter)fRoot).getTarget();
+ if (diffTreeViewer.getRoot() != null) {
+ Comparison comparison = (Comparison)((Adapter)diffTreeViewer.getRoot()).getTarget();
Iterator<Match> matchIt = comparison.getMatches().iterator();
if (comparison.isThreeWay()) {
while (matchIt.hasNext()
@@ -419,8 +500,10 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
getCompareConfiguration().setProperty(EMFCompareConstants.COMPARE_RESULT, null);
getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_FILTERS, null);
getCompareConfiguration().setProperty(EMFCompareConstants.SELECTED_GROUP, null);
+ getCompareConfiguration().setProperty(EMFCompareConstants.MERGE_WAY, null);
+ getCompareConfiguration().setProperty(EMFCompareConstants.SMV_SELECTION, null);
}
- fRoot = null;
+ diffTreeViewer.setRoot(null);
}
}
@@ -454,25 +537,54 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
return scopeBuilder.build(left, right, origin, monitor);
}
- void compareInputChanged(ComparisonNode input, IProgressMonitor monitor) {
- ICompareEditingDomain editingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty(
- EMFCompareConstants.EDITING_DOMAIN);
- editingDomain.getCommandStack().addCommandStackListener(this);
-
- compareInputChanged(null, input.getTarget());
- }
-
- void compareInputChanged(ComparisonScopeInput input, IProgressMonitor monitor) {
- ICompareEditingDomain editingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty(
- EMFCompareConstants.EDITING_DOMAIN);
- editingDomain.getCommandStack().addCommandStackListener(this);
-
- EMFCompare comparator = (EMFCompare)getCompareConfiguration().getProperty(
- EMFCompareConstants.COMPARATOR);
-
- IComparisonScope comparisonScope = input.getComparisonScope();
- Comparison comparison = comparator.compare(comparisonScope, BasicMonitor.toMonitor(monitor));
- compareInputChanged(input.getComparisonScope(), comparison);
+ /**
+ * Team left us with absolutely no way to determine whether our supplied input is the result of a
+ * synchronization or not.
+ * <p>
+ * In order to properly resolve the logical model of the resource currently being compared we need to know
+ * what "other" resources were part of its logical model, and we need to know the revisions of these
+ * resources we are to load. All of this has already been computed by Team, but it would not let us know.
+ * This method uses discouraged means to get around this "black box" locking from Team.
+ * </p>
+ * <p>
+ * The basic need here is to retrieve the Subscriber from this point. We have a lot of accessible
+ * variables, the two most important being the CompareConfiguration and ICompareInput... I could find no
+ * way around the privileged access to the private ModelCompareEditorInput.participant field. There does
+ * not seem to be any adapter (or Platform.getAdapterManager().getAdapter(...)) that would allow for this,
+ * so I'm taking the long way 'round.
+ * </p>
+ *
+ * @return The subscriber used for this comparison if any could be found, <code>null</code> otherwise.
+ */
+ @SuppressWarnings("restriction")
+ private Subscriber getSubscriber() {
+ if (getCompareConfiguration().getContainer() instanceof ModelCompareEditorInput) {
+ final ModelCompareEditorInput modelInput = (ModelCompareEditorInput)getCompareConfiguration()
+ .getContainer();
+ ISynchronizeParticipant participant = null;
+ try {
+ final Field field = ModelCompareEditorInput.class.getDeclaredField("participant"); //$NON-NLS-1$
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ field.setAccessible(true);
+ return null;
+ }
+ });
+ participant = (ISynchronizeParticipant)field.get(modelInput);
+ } catch (NoSuchFieldException e) {
+ // Swallow this, this private field was there at least from 3.5 to 4.3
+ } catch (IllegalArgumentException e) {
+ // Cannot happen
+ } catch (IllegalAccessException e) {
+ // "Should" not happen, but ignore it anyway
+ }
+ if (participant instanceof ModelSynchronizeParticipant
+ && ((ModelSynchronizeParticipant)participant).getContext() instanceof SubscriberMergeContext) {
+ return ((SubscriberMergeContext)((ModelSynchronizeParticipant)participant).getContext())
+ .getSubscriber();
+ }
+ }
+ return null;
}
private static void unload(ResourceSet resourceSet) {
@@ -493,344 +605,4 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
}
return null;
}
-
- void compareInputChanged(final IComparisonScope scope, final Comparison comparison) {
- if (!getControl().isDisposed()) { // guard against disposal
- fRoot = fAdapterFactory.adapt(comparison, ICompareInput.class);
- getCompareConfiguration().setProperty(EMFCompareConstants.COMPARE_RESULT, comparison);
-
- String message = null;
- if (comparison.getDifferences().isEmpty()) {
- message = "No Differences";
- }
-
- final String theMessage = message;
- SWTUtil.safeAsyncExec(new Runnable() {
- public void run() {
- groupActionMenu.createActions(scope, comparison);
- filterActionMenu.createActions(scope, comparison);
- refreshAfterDiff(theMessage, fRoot);
- initialSelection();
- }
- });
- }
- }
-
- /*
- * (non-Javadoc)
- * @see org.eclipse.jface.viewers.AbstractTreeViewer#isExpandable(java.lang.Object)
- */
- @Override
- public boolean isExpandable(Object parent) {
- if (hasFilters()) {
- // workaround for 65762
- return hasFilteredChildren(parent);
- }
- return super.isExpandable(parent);
- }
-
- /**
- * Public method to test if a element has any children that passed the filters
- *
- * @param parent
- * the element to test
- * @return return <code>true</code> if the element has at least a child that passed the filters
- */
- public final boolean hasFilteredChildren(Object parent) {
- Object[] rawChildren = getRawChildren(parent);
- return containsNonFiltered(rawChildren, parent);
- }
-
- private boolean containsNonFiltered(Object[] elements, Object parent) {
- if (elements.length == 0) {
- return false;
- }
- if (!hasFilters()) {
- return true;
- }
- ViewerFilter[] filters = getFilters();
- for (int i = 0; i < elements.length; i++) {
- Object object = elements[i];
- if (!isFiltered(object, parent, filters)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * All element filter tests must go through this method. Can be overridden by subclasses.
- *
- * @param object
- * the object to filter
- * @param parent
- * the parent
- * @param filters
- * the filters to apply
- * @return true if the element is filtered
- */
- protected boolean isFiltered(Object object, Object parent, ViewerFilter[] filters) {
- for (int i = 0; i < filters.length; i++) {
- ViewerFilter filter = filters[i];
- if (!filter.select(this, parent, object)) {
- return true;
- }
- }
- return false;
- }
-
- private void refreshAfterDiff(String message, Object root) {
- if (getControl().isDisposed()) {
- return;
- }
-
- if (fParent != null) {
- fParent.setTitleArgument(message);
- }
-
- refresh(root);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.jface.viewers.StructuredViewer#setComparer(org.eclipse.jface.viewers.IElementComparer)
- */
- @Override
- public void setComparer(IElementComparer comparer) {
- // Wrap this new comparer in our own
- super.setComparer(new DiffNodeComparer(comparer));
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.structuremergeviewer.DiffTreeViewer#createToolItems(org.eclipse.jface.action.ToolBarManager)
- */
- @Override
- protected void createToolItems(ToolBarManager toolbarManager) {
- super.createToolItems(toolbarManager);
-
- // Add extension point contributions to the structure merge viewer toolbar
- IServiceLocator workbench = PlatformUI.getWorkbench();
- IMenuService menuService = (IMenuService)workbench.getService(IMenuService.class);
- if (menuService != null) {
- menuService.populateContributionManager(toolbarManager,
- "toolbar:org.eclipse.emf.compare.structuremergeviewer.toolbar"); //$NON-NLS-1$
- }
-
- Bundle uiWorkbenchBundle = Platform.getBundle("org.eclipse.ui.workbench"); //$NON-NLS-1$
- Version junoStart = Version.parseVersion("3.103"); //$NON-NLS-1$
-
- // XXX MBA change to 3.105 once bug #366528 is fixed
- Version keplerStart = Version.parseVersion("3.105"); //$NON-NLS-1$
-
- if (uiWorkbenchBundle != null && uiWorkbenchBundle.getVersion().compareTo(junoStart) >= 0
- && uiWorkbenchBundle.getVersion().compareTo(keplerStart) < 0) {
- IAction action = new CommandAction(PlatformUI.getWorkbench(),
- "org.eclipse.emf.compare.ide.ui.saveComparisonModel"); //$NON-NLS-1$
- action.setToolTipText("Save Comparison model");
- action.setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
- EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/saveas_edit.gif")); //$NON-NLS-1$
- toolbarManager.add(action);
- }
-
- groupActionMenu = new GroupActionMenu(getStructureMergeViewerGrouper(), getGroupsMenuManager(),
- getDefaultGroupProvider());
- filterActionMenu = new FilterActionMenu(getStructureMergeViewerFilter(), getFiltersMenuManager());
-
- toolbarManager.add(new Separator());
- toolbarManager.add(new ExpandAllModelAction(this));
- toolbarManager.add(new CollapseAllModelAction(this));
- toolbarManager.add(new Separator());
- toolbarManager.add(groupActionMenu);
- toolbarManager.add(filterActionMenu);
-
- }
-
- /**
- * Returns the viewer filter that is to be applied on the structure viewer.
- * <p>
- * Note that this will be called from {@link #createToolItems(ToolBarManager)}, which is called from the
- * super-constructor, when we have had no time to initialize the {@link #structureMergeViewerFilter}
- * field.
- * </p>
- *
- * @return The difference filter that is to be applied on the structure viewer.
- */
- protected StructureMergeViewerFilter getStructureMergeViewerFilter() {
- if (structureMergeViewerFilter == null) {
- if (eventBus == null) {
- eventBus = new EventBus();
- eventBus.register(this);
- }
- structureMergeViewerFilter = new StructureMergeViewerFilter(eventBus);
- structureMergeViewerFilter.install(this);
- }
- return structureMergeViewerFilter;
- }
-
- /**
- * Returns the viewer grouper that is to be applied on the structure viewer.
- * <p>
- * Note that this will be called from {@link #createToolItems(ToolBarManager)}, which is called from the
- * super-constructor, when we have had no time to initialize the {@link #structureMergeViewerGrouper}
- * field.
- * </p>
- *
- * @return The viewer grouper grouper that is to be applied on the structure viewer.
- */
- protected StructureMergeViewerGrouper getStructureMergeViewerGrouper() {
- if (structureMergeViewerGrouper == null) {
- if (eventBus == null) {
- eventBus = new EventBus();
- eventBus.register(this);
- }
- structureMergeViewerGrouper = new StructureMergeViewerGrouper(eventBus);
- structureMergeViewerGrouper.install(this);
- }
- return structureMergeViewerGrouper;
- }
-
- /**
- * Returns the menu manager that is to be applied to groups on the structure viewer.
- *
- * @return The menu manager that is to be applied to groups on the structure viewer.
- */
- public MenuManager getGroupsMenuManager() {
- if (groupsMenuManager == null) {
- groupsMenuManager = new MenuManager();
- }
- return groupsMenuManager;
- }
-
- /**
- * Returns the menu manager that is to be applied to filters on the structure viewer.
- *
- * @return The menu manager that is to be applied to filters on the structure viewer.
- */
- public MenuManager getFiltersMenuManager() {
- if (filtersMenuManager == null) {
- filtersMenuManager = new MenuManager();
- }
- return filtersMenuManager;
- }
-
- /**
- * Returns the default group provider that is to be applied on the structure viewer.
- *
- * @return The default group provider that is to be applied on the structure viewer.
- */
- public DefaultGroupProvider getDefaultGroupProvider() {
- if (defaultGroupProvider == null) {
- defaultGroupProvider = new DefaultGroupProvider();
- }
- return defaultGroupProvider;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.structuremergeviewer.DiffTreeViewer#inputChanged(java.lang.Object,
- * java.lang.Object)
- */
- @Override
- protected void inputChanged(Object input, Object oldInput) {
- if (oldInput instanceof ICompareInput) {
- ICompareInput old = (ICompareInput)oldInput;
- old.removeCompareInputChangeListener(fCompareInputChangeListener);
- }
- if (input instanceof ICompareInput) {
- ICompareInput ci = (ICompareInput)input;
- ci.addCompareInputChangeListener(fCompareInputChangeListener);
-
- // Hack to display a message in the tree viewer while the differences are being computed.
- TreeItem item = new TreeItem(getTree(), SWT.NONE);
- item.setText("Computing model differences...");
-
- compareInputChanged(ci);
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.structuremergeviewer.DiffTreeViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
- */
- @Override
- protected void handleDispose(DisposeEvent event) {
- Object input = getInput();
- if (input instanceof ICompareInput) {
- ICompareInput ci = (ICompareInput)input;
- ci.removeCompareInputChangeListener(fCompareInputChangeListener);
- }
- compareInputChanged((ICompareInput)null);
- fAdapterFactory.dispose();
-
- super.handleDispose(event);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.common.command.CommandStackListener#commandStackChanged(java.util.EventObject)
- */
- public void commandStackChanged(EventObject event) {
- Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
- if (mostRecentCommand instanceof ICompareCopyCommand) {
- Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
-
- SWTUtil.safeAsyncExec(new Runnable() {
- public void run() {
- refresh(true);
- }
- });
- if (!affectedObjects.isEmpty()) {
- // MUST NOT call a setSelection with a list, o.e.compare does not handle it (cf
- // org.eclipse.compare.CompareEditorInput#getElement(ISelection))
- final Object adaptedAffectedObject = fAdapterFactory.adapt(getFirst(affectedObjects, null),
- ICompareInput.class);
- SWTUtil.safeAsyncExec(new Runnable() {
- public void run() {
- setSelectionToWidget(new StructuredSelection(adaptedAffectedObject), true);
- }
- });
- }
- } else {
- // FIXME, should recompute the difference, something happened outside of this compare editor
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.jface.viewers.AbstractTreeViewer#getSortedChildren(java.lang.Object)
- */
- @Override
- protected Object[] getSortedChildren(Object parentElementOrTreePath) {
- Object[] result = super.getSortedChildren(parentElementOrTreePath);
- if (parentElementOrTreePath instanceof Adapter
- && ((Adapter)parentElementOrTreePath).getTarget() instanceof Conflict) {
-
- Collections.sort(Arrays.asList(result), new Comparator<Object>() {
- public int compare(Object o1, Object o2) {
- return getValue(o1) - getValue(o2);
- }
-
- public int getValue(Object o) {
- int value = 0;
- if (o instanceof Adapter && ((Adapter)o).getTarget() instanceof Diff) {
- if (((Diff)((Adapter)o).getTarget()).getSource() == DifferenceSource.LEFT) {
- value = 1;
- } else {
- value = 2;
- }
- }
- return value;
- }
- });
-
- }
- return result;
- }
}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerContentProvider.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerContentProvider.java
index 492208bce..901488d5b 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerContentProvider.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerContentProvider.java
@@ -23,6 +23,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.LinkedHashSet;
import java.util.List;
import org.eclipse.compare.CompareConfiguration;
@@ -97,8 +99,19 @@ class EMFCompareStructureMergeViewerContentProvider extends AdapterFactoryConten
if (element instanceof Adapter) {
ret = getAdapterFactory().adapt(super.getParent(((Adapter)element).getTarget()),
ICompareInput.class);
+ if (ret instanceof ComparisonNode) {
+ Comparison root = ((ComparisonNode)ret).getTarget();
+ final Iterable<? extends IDifferenceGroup> groups = fViewerGrouper.getGroups(root);
+ Collection<IDifferenceGroup> parentGroups = new LinkedHashSet<IDifferenceGroup>();
+ for (IDifferenceGroup iDifferenceGroup : groups) {
+ parentGroups.add(iDifferenceGroup);
+ }
+ if (!parentGroups.isEmpty()) {
+ ret = parentGroups;
+ }
+ }
} else if (element instanceof IDifferenceGroup) {
- ret = ((IDifferenceGroup)element).getComparison();
+ ret = getAdapterFactory().adapt(((IDifferenceGroup)element).getComparison(), ICompareInput.class);
} else {
ret = null;
}
@@ -298,7 +311,7 @@ class EMFCompareStructureMergeViewerContentProvider extends AdapterFactoryConten
* the given differences.
* @return true if the object should be contained in the given differences, false otherwise.
*/
- private boolean isPartOfGroup(Object object, final Iterable<? extends Diff> differences) {
+ public static boolean isPartOfGroup(Object object, final Iterable<? extends Diff> differences) {
final Predicate<? super EObject> isPartOfTree = new Predicate<EObject>() {
public boolean apply(EObject input) {
return Iterables.contains(differences, input);
@@ -380,7 +393,7 @@ class EMFCompareStructureMergeViewerContentProvider extends AdapterFactoryConten
*
* @return the wrapped AbstractEDiffNode.
*/
- private AbstractEDiffNode delegate() {
+ public AbstractEDiffNode delegate() {
return fDelegate;
}
@@ -510,5 +523,15 @@ class EMFCompareStructureMergeViewerContentProvider extends AdapterFactoryConten
public boolean isAdapterForType(Object type) {
return delegate().isAdapterForType(type);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return delegate().equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate().hashCode();
+ }
}
}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerCreator.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerCreator.java
index b9b13a0de..9c8b99dfa 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerCreator.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewerCreator.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012 Obeo.
+ * Copyright (c) 2012, 2013 Obeo.
* 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
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectAllChanges.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectAllChanges.java
new file mode 100644
index 000000000..6ad6ac943
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectAllChanges.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import static com.google.common.collect.Iterables.filter;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CompoundCommand;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.command.ICompareCopyCommand;
+import org.eclipse.emf.compare.domain.ICompareEditingDomain;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.compare.utils.EMFComparePredicates;
+import org.eclipse.emf.ecore.change.util.ChangeRecorder;
+import org.eclipse.emf.edit.command.ChangeCommand;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Abstract handler that manages the accept all and reject all actions (when one side of a diff is not
+ * editable).
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public abstract class AbstractAcceptRejectAllChanges extends AbstractHandler {
+
+ /** The compare configuration object used to get the compare model. */
+ protected CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ boolean rightEditableOnly = !configuration.isLeftEditable() && configuration.isRightEditable();
+ boolean leftEditableOnly = configuration.isLeftEditable() && !configuration.isRightEditable();
+ if (leftEditableOnly) {
+ manageChanges(false);
+ } else if (rightEditableOnly) {
+ manageChanges(true);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if the way of merge of the given diff correspond to a copy or a simple change state (unresolved
+ * to merged).
+ *
+ * @param diff
+ * the given diff.
+ * @param leftToRight
+ * the way of merge.
+ * @return true if the way of merge of the given diff correspond to a copy, false if it corresponds to a
+ * simple change state.
+ */
+ protected abstract boolean isCopyDiffCase(Diff diff, boolean leftToRight);
+
+ /**
+ * Manage changes (copy or change state) for all non-conflicting diffs.
+ *
+ * @param leftToRight
+ * the way of merge.
+ */
+ private void manageChanges(final boolean leftToRight) {
+ final List<Diff> differences;
+ Comparison comparison = (Comparison)configuration.getProperty(EMFCompareConstants.COMPARE_RESULT);
+ if (comparison.isThreeWay()) {
+ differences = ImmutableList.copyOf(filter(comparison.getDifferences(), new Predicate<Diff>() {
+ public boolean apply(Diff diff) {
+ final boolean unresolved = diff.getState() == DifferenceState.UNRESOLVED;
+ final boolean nonConflictual = diff.getConflict() == null;
+ final boolean fromLeftToRight = leftToRight && diff.getSource() == DifferenceSource.LEFT;
+ final boolean fromRightToLeft = !leftToRight
+ && diff.getSource() == DifferenceSource.RIGHT;
+ return unresolved && nonConflictual && (fromLeftToRight || fromRightToLeft);
+ }
+ }));
+ } else {
+ differences = ImmutableList.copyOf(filter(comparison.getDifferences(), EMFComparePredicates
+ .hasState(DifferenceState.UNRESOLVED)));
+ }
+
+ if (differences.size() > 0) {
+
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+
+ AcceptRejectAllChangesCompoundCommand compoundCommand = new AcceptRejectAllChangesCompoundCommand(
+ leftToRight);
+
+ for (Diff diff : differences) {
+ if (DifferenceState.UNRESOLVED == diff.getState()) {
+ if (isCopyDiffCase(diff, leftToRight)) {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(diff, leftToRight, configuration
+ .isLeftEditable(), configuration.isRightEditable());
+ compoundCommand.append(editingDomain.createCopyCommand(Lists.newArrayList(diff),
+ leftToRight, EMFCompareRCPPlugin.getDefault().getMergerRegistry()));
+ } else {
+ compoundCommand.append(createChangeStateFromUnresolvedToMergedCommand(diff,
+ !leftToRight));
+ }
+ }
+ }
+
+ editingDomain.getCommandStack().execute(compoundCommand);
+ }
+ }
+
+ /**
+ * Execute a command that change the state of the given diff from {@link DifferenceState#UNRESOLVED} to
+ * {@link DifferenceState#MERGED}.
+ *
+ * @param diffToChangeState
+ * the given diff.
+ * @param leftToRight
+ * the way of merge.
+ * @return A command that change the state of the given diff from {@link DifferenceState#UNRESOLVED} to
+ * {@link DifferenceState#MERGED}.
+ */
+ protected Command createChangeStateFromUnresolvedToMergedCommand(Diff diffToChangeState,
+ boolean leftToRight) {
+ if (diffToChangeState != null) {
+ ICompareEditingDomain compareEditingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ Command changeStateCommand = new AcceptRejectAllChangesCommand(compareEditingDomain
+ .getChangeRecorder(), diffToChangeState, leftToRight, configuration);
+ return changeStateCommand;
+ }
+ return null;
+ }
+
+ /**
+ * A specific {@link ChangeCommand} that change the state of the given diff from
+ * {@link DifferenceState#UNRESOLVED} to {@link DifferenceState#MERGED}.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+ private static class AcceptRejectAllChangesCommand extends ChangeCommand implements ICompareCopyCommand {
+
+ /** The difference concerned by the command. */
+ private Diff difference;
+
+ /** The way of merge. */
+ private boolean leftToRight;
+
+ /** The compare configuration object. */
+ private CompareConfiguration configuration;
+
+ /**
+ * Constructor.
+ *
+ * @param changeRecorder
+ * the change recorder used by the command.
+ * @param difference
+ * the difference concerned by the command.
+ * @param leftToRight
+ * the way of merge.
+ * @param configuration
+ * the compare configuration object.
+ */
+ public AcceptRejectAllChangesCommand(ChangeRecorder changeRecorder, Diff difference,
+ boolean leftToRight, CompareConfiguration configuration) {
+ super(changeRecorder, difference);
+ this.difference = difference;
+ this.leftToRight = leftToRight;
+ this.configuration = configuration;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.command.ChangeCommand#doExecute()
+ */
+ @Override
+ public void doExecute() {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(difference, leftToRight, configuration
+ .isLeftEditable(), configuration.isRightEditable());
+ difference.setState(DifferenceState.MERGED);
+
+ }
+
+ /**
+ * Returns true if the command will be applied from left to right side, false otherwise.
+ *
+ * @return true if the command will be applied from left to right side, false otherwise.
+ */
+ public boolean isLeftToRight() {
+ return leftToRight;
+ }
+
+ }
+
+ /**
+ * A specific {@link CompoundCommand} that implements {@link ICompareCopyCommand}.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+ private static class AcceptRejectAllChangesCompoundCommand extends CompoundCommand implements ICompareCopyCommand {
+
+ /** The way of merge. */
+ private boolean leftToRight;
+
+ /**
+ * Constructor.
+ *
+ * @param leftToRight
+ * the way of merge.
+ */
+ public AcceptRejectAllChangesCompoundCommand(boolean leftToRight) {
+ this.leftToRight = leftToRight;
+ }
+
+ /**
+ * Returns true if the command will be applied from left to right side, false otherwise.
+ *
+ * @return true if the command will be applied from left to right side, false otherwise.
+ */
+ public boolean isLeftToRight() {
+ return leftToRight;
+ }
+
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectChange.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectChange.java
new file mode 100644
index 000000000..317da68e6
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractAcceptRejectChange.java
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.command.ICompareCopyCommand;
+import org.eclipse.emf.compare.domain.ICompareEditingDomain;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.ecore.change.util.ChangeRecorder;
+import org.eclipse.emf.edit.command.ChangeCommand;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Abstract handler that manages the accept and reject actions (when one side of a diff is not editable).
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public abstract class AbstractAcceptRejectChange extends AbstractHandler {
+
+ /** The compare configuration object used to get the compare model. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ Object diffNode = ((CompareEditorInput)editorInput).getSelectedEdition();
+ if (diffNode instanceof Adapter) {
+ Notifier diff = ((Adapter)diffNode).getTarget();
+ if (diff instanceof Diff) {
+ boolean rightEditableOnly = !configuration.isLeftEditable()
+ && configuration.isRightEditable();
+ boolean leftEditableOnly = configuration.isLeftEditable()
+ && !configuration.isRightEditable();
+ if (leftEditableOnly) {
+ if (isCopyDiffCase((Diff)diff, false)) {
+ EMFCompareUIHandlerUtil.copyDiff((Diff)diff, false, configuration);
+ } else {
+ changeStateFromUnresolvedToMerged((Diff)diff, true);
+ }
+ } else if (rightEditableOnly) {
+ if (isCopyDiffCase((Diff)diff, true)) {
+ EMFCompareUIHandlerUtil.copyDiff((Diff)diff, true, configuration);
+ } else {
+ changeStateFromUnresolvedToMerged((Diff)diff, false);
+ }
+ }
+ // Select next diff
+ EMFCompareUIHandlerUtil.navigate(true, configuration);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if the way of merge of the given diff correspond to a copy or a simple change state (unresolved
+ * to merged).
+ *
+ * @param diff
+ * the given diff.
+ * @param leftToRight
+ * the way of merge.
+ * @return true if the way of merge of the given diff correspond to a copy, false if it corresponds to a
+ * simple change state.
+ */
+ protected abstract boolean isCopyDiffCase(Diff diff, boolean leftToRight);
+
+ /**
+ * Execute a command that change the state of the given diff from {@link DifferenceState#UNRESOLVED} to
+ * {@link DifferenceState#MERGED}.
+ *
+ * @param diffToChangeState
+ * the given diff.
+ * @param leftToRight
+ * the way of merge.
+ */
+ private void changeStateFromUnresolvedToMerged(Diff diffToChangeState, boolean leftToRight) {
+ if (diffToChangeState != null) {
+ ICompareEditingDomain compareEditingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ Command changeStateCommand = new AcceptRejectChangeCommand(compareEditingDomain
+ .getChangeRecorder(), diffToChangeState, leftToRight, configuration);
+ compareEditingDomain.getCommandStack().execute(changeStateCommand);
+ }
+ }
+
+ /**
+ * A specific {@link ChangeCommand} that change the state of the given diff and all its required diffs
+ * from {@link DifferenceState#UNRESOLVED} to {@link DifferenceState#MERGED}.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+ private static class AcceptRejectChangeCommand extends ChangeCommand implements ICompareCopyCommand {
+
+ /** The difference concerned by the command. */
+ private Diff difference;
+
+ /** The way of merge. */
+ private boolean leftToRight;
+
+ /** The compare configuration object. */
+ private CompareConfiguration configuration;
+
+ /**
+ * Constructor.
+ *
+ * @param changeRecorder
+ * the change recorder used by the command.
+ * @param difference
+ * the difference concerned by the command.
+ * @param leftToRight
+ * the way of merge.
+ * @param configuration
+ * the compare configuration object.
+ */
+ public AcceptRejectChangeCommand(ChangeRecorder changeRecorder, Diff difference, boolean leftToRight,
+ CompareConfiguration configuration) {
+ super(changeRecorder, ImmutableSet.<Notifier> builder().add(difference).addAll(
+ DiffUtil.getRequires(difference, leftToRight, difference.getSource())).build());
+ this.difference = difference;
+ this.leftToRight = leftToRight;
+ this.configuration = configuration;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.command.ChangeCommand#doExecute()
+ */
+ @Override
+ public void doExecute() {
+ for (Diff require : DiffUtil.getRequires(difference, leftToRight, difference.getSource())) {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(require, leftToRight, configuration
+ .isLeftEditable(), configuration.isRightEditable());
+ require.setState(DifferenceState.MERGED);
+ }
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(difference, leftToRight, configuration
+ .isLeftEditable(), configuration.isRightEditable());
+ difference.setState(DifferenceState.MERGED);
+
+ }
+
+ /**
+ * Returns true if the command will be applied from left to right side, false otherwise.
+ *
+ * @return true if the command will be applied from left to right side, false otherwise.
+ */
+ public boolean isLeftToRight() {
+ return leftToRight;
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedAllTo.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedAllTo.java
new file mode 100644
index 000000000..0cc28123a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedAllTo.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Abstract Handler that manages a merge of a all non-conflicting difference in case of both sides of the
+ * comparison are editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public abstract class AbstractMergedAllTo extends AbstractHandler {
+
+ /** The compare configuration object used to get the compare model. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ setConfiguration(((CompareEditorInput)editorInput).getCompareConfiguration());
+ copyAllDiffs();
+ }
+ return null;
+ }
+
+ /**
+ * Copy all non-conflicting differences.
+ */
+ protected abstract void copyAllDiffs();
+
+ /**
+ * Get the compare configuration object.
+ *
+ * @return the configuration
+ */
+ public CompareConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Set the compare configuration object.
+ *
+ * @param configuration
+ * the configuration to set
+ */
+ public void setConfiguration(CompareConfiguration configuration) {
+ this.configuration = configuration;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedTo.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedTo.java
new file mode 100644
index 000000000..ddb017340
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AbstractMergedTo.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Abstract Handler that manages a merge of a difference in case of both sides of the comparison are editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public abstract class AbstractMergedTo extends AbstractHandler {
+
+ /** The compare configuration object used to get the compare model. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ setConfiguration(((CompareEditorInput)editorInput).getCompareConfiguration());
+ Object diffNode = ((CompareEditorInput)editorInput).getSelectedEdition();
+ if (diffNode instanceof Adapter) {
+ Notifier diff = ((Adapter)diffNode).getTarget();
+ if (diff instanceof Diff) {
+ copyDiff((Diff)diff);
+ // Select next diff
+ EMFCompareUIHandlerUtil.navigate(true, configuration);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Copy the diff.
+ *
+ * @param diff
+ * the given diff.
+ */
+ protected abstract void copyDiff(Diff diff);
+
+ /**
+ * Get the compare configuration object.
+ *
+ * @return the configuration
+ */
+ public CompareConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Set the compare configuration object.
+ *
+ * @param configuration
+ * the configuration to set
+ */
+ public void setConfiguration(CompareConfiguration configuration) {
+ this.configuration = configuration;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptAllChanges.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptAllChanges.java
new file mode 100644
index 000000000..2929d5702
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptAllChanges.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a merge of all non-conflicting differences in case of one side of the comparison is
+ * not editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class AcceptAllChanges extends AbstractAcceptRejectAllChanges {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractAcceptRejectAllChanges#isCopyDiffCase(org.eclipse.emf.compare.Diff,
+ * boolean)
+ */
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptChange.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptChange.java
new file mode 100644
index 000000000..89967207a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/AcceptChange.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a merge of a difference in case of one side of the comparison is not editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class AcceptChange extends AbstractAcceptRejectChange {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractAcceptRejectChange#isCopyDiffCase(org.eclipse.emf.compare.Diff,
+ * boolean)
+ */
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownHandler.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownHandler.java
new file mode 100644
index 000000000..3d9494106
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownHandler.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import java.util.Map;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.internal.CompareEditor;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.Widget;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.commands.IElementUpdater;
+import org.eclipse.ui.handlers.HandlerUtil;
+import org.eclipse.ui.menus.UIElement;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+
+/**
+ * Handler that manages the click on the dropdown menu of the toolbar of the structure merge viewer.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class DropDownHandler extends AbstractHandler implements IElementUpdater {
+
+ /** The compare configuration object. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ Object trigger = event.getTrigger();
+ if (trigger instanceof Event) {
+ Event eventWidget = (Event)event.getTrigger();
+ Widget widget = eventWidget.widget;
+ if (widget instanceof ToolItem) {
+ ToolItem toolItem = (ToolItem)widget;
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ Boolean mergeWay = (Boolean)configuration.getProperty(EMFCompareConstants.MERGE_WAY);
+ if (mergeWay == null || mergeWay.booleanValue()) {
+ configuration.setProperty(EMFCompareConstants.MERGE_WAY, new Boolean(false));
+ toolItem.setImage(EMFCompareIDEUIPlugin
+ .getImage("icons/full/toolb16/right_to_left.gif")); //$NON-NLS-1$
+ } else {
+ configuration.setProperty(EMFCompareConstants.MERGE_WAY, new Boolean(true));
+ toolItem.setImage(EMFCompareIDEUIPlugin
+ .getImage("icons/full/toolb16/left_to_right.gif")); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.ui.commands.IElementUpdater#updateElement(UIElement, Map)
+ */
+ public void updateElement(UIElement element, Map parameters) {
+ if (configuration == null) {
+ Object value = parameters.get("org.eclipse.ui.IWorkbenchWindow"); //$NON-NLS-1$
+ if (value instanceof IWorkbenchWindow) {
+ IWorkbenchPage pa = ((IWorkbenchWindow)value).getActivePage();
+ IEditorPart editor = pa.getActiveEditor();
+ if (editor instanceof CompareEditor) {
+ IEditorInput editorInput = editor.getEditorInput();
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ }
+ }
+ }
+ }
+ if (configuration != null) {
+ Boolean mergeWay = (Boolean)configuration.getProperty(EMFCompareConstants.MERGE_WAY);
+ if (mergeWay == null || mergeWay.booleanValue()) {
+ element.setIcon(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
+ "icons/full/toolb16/left_to_right.gif")); //$NON-NLS-1$
+ } else {
+ element.setIcon(AbstractUIPlugin.imageDescriptorFromPlugin(EMFCompareIDEUIPlugin.PLUGIN_ID,
+ "icons/full/toolb16/right_to_left.gif")); //$NON-NLS-1$
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownLeftToRight.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownLeftToRight.java
new file mode 100644
index 000000000..beeca1c86
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownLeftToRight.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandService;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Handler that manages the click on the menu item left to right in the dropdown menu of the toolbar of the
+ * structure merge viewer.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class DropDownLeftToRight extends AbstractHandler {
+
+ /** The compare configuration object. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ configuration.setProperty(EMFCompareConstants.MERGE_WAY, new Boolean(true));
+ }
+
+ ICommandService commandService = (ICommandService)PlatformUI.getWorkbench().getService(
+ ICommandService.class);
+ commandService.refreshElements("org.eclipse.emf.compare.ide.ui.dropdown", null); //$NON-NLS-1$
+
+ return null;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownRightToLeft.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownRightToLeft.java
new file mode 100644
index 000000000..3daff914f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/DropDownRightToLeft.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.commands.ICommandService;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Handler that manages the click on the menu item right to left in the dropdown menu of the toolbar of the
+ * structure merge viewer.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class DropDownRightToLeft extends AbstractHandler {
+
+ /** The compare configuration object. */
+ private CompareConfiguration configuration;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ configuration.setProperty(EMFCompareConstants.MERGE_WAY, new Boolean(false));
+ }
+ ICommandService commandService = (ICommandService)PlatformUI.getWorkbench().getService(
+ ICommandService.class);
+ commandService.refreshElements("org.eclipse.emf.compare.ide.ui.dropdown", null); //$NON-NLS-1$
+ return null;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToLeft.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToLeft.java
new file mode 100644
index 000000000..543f4c945
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToLeft.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+
+/**
+ * Handler that manages a merge from left to right of all non-conflicting differences in case of both sides of
+ * the comparison are editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class MergedAllToLeft extends AbstractMergedAllTo {
+
+ @Override
+ protected void copyAllDiffs() {
+ EMFCompareUIHandlerUtil.copyAllDiffs(false, getConfiguration());
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToRight.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToRight.java
new file mode 100644
index 000000000..eab8d32f0
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedAllToRight.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+
+/**
+ * Handler that manages a merge from left to right of all non-conflicting differences in case of both sides of
+ * the comparison are editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class MergedAllToRight extends AbstractMergedAllTo {
+
+ @Override
+ protected void copyAllDiffs() {
+ EMFCompareUIHandlerUtil.copyAllDiffs(true, getConfiguration());
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToLeft.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToLeft.java
new file mode 100644
index 000000000..dde61ec92
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToLeft.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+
+/**
+ * Handler that manages a merge from right to left of a difference in case of both sides of the comparison are
+ * editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class MergedToLeft extends AbstractMergedTo {
+
+ @Override
+ protected void copyDiff(Diff diff) {
+ EMFCompareUIHandlerUtil.copyDiff(diff, false, getConfiguration());
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToRight.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToRight.java
new file mode 100644
index 000000000..5e754f386
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/MergedToRight.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+
+/**
+ * Handler that manages a merge from left to right of a difference in case of both sides of the comparison are
+ * editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class MergedToRight extends AbstractMergedTo {
+
+ @Override
+ protected void copyDiff(Diff diff) {
+ EMFCompareUIHandlerUtil.copyDiff(diff, true, getConfiguration());
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectAllChanges.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectAllChanges.java
new file mode 100644
index 000000000..1e9cd62a9
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectAllChanges.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a merge of all non-conflicting differences in case of one side of the comparison is
+ * not editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class RejectAllChanges extends AbstractAcceptRejectAllChanges {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractAcceptRejectAllChanges#isCopyDiffCase(org.eclipse.emf.compare.Diff,
+ * boolean)
+ */
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectChange.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectChange.java
new file mode 100644
index 000000000..996a129bd
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/RejectChange.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a reject of a difference in case of one side of the comparison is not editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class RejectChange extends AbstractAcceptRejectChange {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractAcceptRejectChange#isCopyDiffCase(org.eclipse.emf.compare.Diff,
+ * boolean)
+ */
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectNextDiff.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectNextDiff.java
new file mode 100644
index 000000000..e2e6bb163
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectNextDiff.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Handler that manages the select next diff button.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class SelectNextDiff extends AbstractHandler {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ // Select next diff
+ EMFCompareUIHandlerUtil.navigate(true, configuration);
+ }
+ return null;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectPreviousDiff.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectPreviousDiff.java
new file mode 100644
index 000000000..5ecd4e10d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/SelectPreviousDiff.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util.EMFCompareUIHandlerUtil;
+import org.eclipse.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+/**
+ * Handler that manages the select previous diff button.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public class SelectPreviousDiff extends AbstractHandler {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ // Select next diff
+ EMFCompareUIHandlerUtil.navigate(false, configuration);
+ }
+ return null;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/AcceptRejectChangePropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/AcceptRejectChangePropertyTester.java
new file mode 100644
index 000000000..a79978c13
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/AcceptRejectChangePropertyTester.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+/**
+ * A property tester linked with
+ * {@link org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractMergedTo}. It returns
+ * true when only one model side is editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class AcceptRejectChangePropertyTester extends PropertyTester {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.expressions.PropertyTester#test(java.lang.Object, java.lang.String,
+ * java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ if (configuration.isLeftEditable() && !configuration.isRightEditable()) {
+ return true;
+ } else if (!configuration.isLeftEditable() && configuration.isRightEditable()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/DiffSelectedPropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/DiffSelectedPropertyTester.java
new file mode 100644
index 000000000..ebad5be65
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/DiffSelectedPropertyTester.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+/**
+ * A property tester that checks if a diff is selected in the compare editor.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class DiffSelectedPropertyTester extends PropertyTester {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.expressions.PropertyTester#test(java.lang.Object, java.lang.String,
+ * java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ ISelection selection = (ISelection)configuration
+ .getProperty(EMFCompareConstants.SMV_SELECTION);
+ if (selection instanceof IStructuredSelection) {
+ Object element = ((IStructuredSelection)selection).getFirstElement();
+ if (element instanceof Adapter) {
+ Notifier diffNode = ((Adapter)element).getTarget();
+ if (diffNode instanceof Diff) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/LeftToRightSidePropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/LeftToRightSidePropertyTester.java
new file mode 100644
index 000000000..780860517
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/LeftToRightSidePropertyTester.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+/**
+ * A property tester that check the way of merge.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class LeftToRightSidePropertyTester extends PropertyTester {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.expressions.PropertyTester#test(java.lang.Object, java.lang.String,
+ * java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ Boolean leftToRight = (Boolean)configuration.getProperty(EMFCompareConstants.MERGE_WAY);
+ if (leftToRight == null || leftToRight.booleanValue()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/MergedToPropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/MergedToPropertyTester.java
new file mode 100644
index 000000000..75447c35f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/MergedToPropertyTester.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+/**
+ * A property tester linked with
+ * {@link org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AbstractMergedTo}. It returns
+ * true when both model sides are editable.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class MergedToPropertyTester extends PropertyTester {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.expressions.PropertyTester#test(java.lang.Object, java.lang.String,
+ * java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ if (configuration.isLeftEditable() && configuration.isRightEditable()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/ModelSaveablePropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/ModelSaveablePropertyTester.java
index 3b409db57..ea6890f04 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/ModelSaveablePropertyTester.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/ModelSaveablePropertyTester.java
@@ -8,7 +8,7 @@
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
-package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler;
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
@@ -17,8 +17,9 @@ import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
/**
- * A property tester linked with {@link SaveComparisonModel}. It tests the editable property of both model
- * sides.
+ * A property tester linked with
+ * {@link org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.SaveComparisonModel}. It tests
+ * the editable property of both model sides.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
* @since 3.0
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/RightToLeftSidePropertyTester.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/RightToLeftSidePropertyTester.java
new file mode 100644
index 000000000..92310abc7
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/propertytester/RightToLeftSidePropertyTester.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.propertytester;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+/**
+ * A property tester that check the way of merge.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class RightToLeftSidePropertyTester extends PropertyTester {
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.core.expressions.PropertyTester#test(java.lang.Object, java.lang.String,
+ * java.lang.Object[], java.lang.Object)
+ */
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ Boolean leftToRight = (Boolean)configuration.getProperty(EMFCompareConstants.MERGE_WAY);
+ if (leftToRight != null && !leftToRight.booleanValue()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/util/EMFCompareUIHandlerUtil.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/util/EMFCompareUIHandlerUtil.java
new file mode 100644
index 000000000..59e1b6bb9
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/handler/util/EMFCompareUIHandlerUtil.java
@@ -0,0 +1,194 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.handler.util;
+
+import static com.google.common.collect.Iterables.addAll;
+import static com.google.common.collect.Iterables.filter;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareNavigator;
+import org.eclipse.compare.ICompareNavigator;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.domain.ICompareEditingDomain;
+import org.eclipse.emf.compare.internal.merge.DiffMergeDataAdapter;
+import org.eclipse.emf.compare.internal.merge.IDiffMergeData;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl.CascadingDifferencesFilter;
+import org.eclipse.emf.compare.utils.EMFComparePredicates;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+
+/**
+ * Util class that provides utilities methods for RCP UI handlers.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public final class EMFCompareUIHandlerUtil {
+
+ /**
+ * Utility classes don't need a default constructor.
+ */
+ private EMFCompareUIHandlerUtil() {
+ // Hides default constructor.
+ }
+
+ /**
+ * Checks the state of the cascading differences filter.
+ *
+ * @param configuration
+ * the compare configuration object.
+ * @return true, if the cascading differences filter is active, false otherwise.
+ */
+ public static boolean isCascadingDifferencesFilterActive(CompareConfiguration configuration) {
+ Object property = configuration.getProperty(EMFCompareConstants.SELECTED_FILTERS);
+ final Collection<IDifferenceFilter> selectedFilters;
+ if (property != null) {
+ selectedFilters = (Collection<IDifferenceFilter>)property;
+ for (IDifferenceFilter iDifferenceFilter : selectedFilters) {
+ if (iDifferenceFilter instanceof CascadingDifferencesFilter) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Copy the given diff.
+ *
+ * @param diffToCopy
+ * the given diff to copy.
+ * @param leftToRight
+ * the way of merge.
+ * @param configuration
+ * the compare configuration object.
+ */
+ public static void copyDiff(Diff diffToCopy, boolean leftToRight, CompareConfiguration configuration) {
+ if (diffToCopy != null) {
+ List<Diff> diffsToCopy = new ArrayList<Diff>();
+ diffsToCopy.add(diffToCopy);
+ for (Diff require : DiffUtil.getRequires(diffToCopy, leftToRight, diffToCopy.getSource())) {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(require, leftToRight, configuration
+ .isLeftEditable(), configuration.isRightEditable());
+ }
+ if (EMFCompareUIHandlerUtil.isCascadingDifferencesFilterActive(configuration)) {
+ addAll(diffsToCopy, org.eclipse.emf.compare.utils.DiffUtil.getSubDiffs(leftToRight).apply(
+ diffToCopy));
+ }
+ for (Diff diff : diffsToCopy) {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(diff, leftToRight,
+ configuration.isLeftEditable(), configuration.isRightEditable());
+ }
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ Command copyCommand = editingDomain.createCopyCommand(diffsToCopy, leftToRight,
+ EMFCompareRCPPlugin.getDefault().getMergerRegistry());
+ editingDomain.getCommandStack().execute(copyCommand);
+ }
+ }
+
+ /**
+ * Copy all non-conflicting changes.
+ *
+ * @param leftToRight
+ * the way of merge.
+ * @param configuration
+ * the compare configuration object.
+ */
+ public static void copyAllDiffs(final boolean leftToRight, CompareConfiguration configuration) {
+ final List<Diff> differences;
+ Comparison comparison = (Comparison)configuration.getProperty(EMFCompareConstants.COMPARE_RESULT);
+ if (comparison.isThreeWay()) {
+ differences = ImmutableList.copyOf(filter(comparison.getDifferences(), new Predicate<Diff>() {
+ public boolean apply(Diff diff) {
+ final boolean unresolved = diff.getState() == DifferenceState.UNRESOLVED;
+ final boolean nonConflictual = diff.getConflict() == null;
+ final boolean fromLeftToRight = leftToRight && diff.getSource() == DifferenceSource.LEFT;
+ final boolean fromRightToLeft = !leftToRight
+ && diff.getSource() == DifferenceSource.RIGHT;
+ return unresolved && nonConflictual && (fromLeftToRight || fromRightToLeft);
+ }
+ }));
+ } else {
+ differences = ImmutableList.copyOf(filter(comparison.getDifferences(), EMFComparePredicates
+ .hasState(DifferenceState.UNRESOLVED)));
+ }
+
+ if (differences.size() > 0) {
+ for (Diff diff : differences) {
+ EMFCompareUIHandlerUtil.setMergeDataForDiff(diff, leftToRight,
+ configuration.isLeftEditable(), configuration.isRightEditable());
+ }
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ final Command copyCommand = editingDomain.createCopyCommand(differences, leftToRight,
+ EMFCompareRCPPlugin.getDefault().getMergerRegistry());
+
+ editingDomain.getCommandStack().execute(copyCommand);
+ }
+ }
+
+ /**
+ * Called by the framework to navigate to the next (or previous) difference. This will open the content
+ * viewer for the next (or previous) diff displayed in the structure viewer.
+ *
+ * @param next
+ * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if
+ * we should go to the previous instead.
+ * @param configuration
+ * the compare configuration object.
+ */
+ public static void navigate(boolean next, CompareConfiguration configuration) {
+ final ICompareNavigator navigator = configuration.getContainer().getNavigator();
+ if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) {
+ navigator.selectChange(next);
+ }
+ }
+
+ /**
+ * Set the merge way for the given diff. After a merge, it allows to know the way of the merge.
+ *
+ * @param diff
+ * the given diff.
+ * @param leftToRight
+ * the way of the merge.
+ * @param leftEditable
+ * the left side of the difference is editable.
+ * @param rightEditable
+ * the right side of the difference is editable.
+ */
+ public static void setMergeDataForDiff(Diff diff, boolean leftToRight, boolean leftEditable,
+ boolean rightEditable) {
+ Adapter adapter = EcoreUtil.getExistingAdapter(diff, IDiffMergeData.class);
+ if (adapter != null) {
+ ((IDiffMergeData)adapter).setMergedTo(leftToRight);
+ ((IDiffMergeData)adapter).setLeftEditable(leftEditable);
+ ((IDiffMergeData)adapter).setRightEditable(rightEditable);
+ } else {
+ diff.eAdapters().add(new DiffMergeDataAdapter(leftToRight, leftEditable, rightEditable));
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/EMFCompareConstants.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/EMFCompareConstants.java
index aa7c8f985..4d1b07392 100644
--- a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/EMFCompareConstants.java
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/EMFCompareConstants.java
@@ -43,6 +43,10 @@ public final class EMFCompareConstants {
public static final String SELECTED_GROUP = EMFCompareRCPUIPlugin.PLUGIN_ID + ".SELECTED_GROUP"; //$NON-NLS-1$
+ public static final String SMV_SELECTION = EMFCompareRCPUIPlugin.PLUGIN_ID + ".SMV_SELECTION"; //$NON-NLS-1$
+
+ public static final String MERGE_WAY = EMFCompareRCPUIPlugin.PLUGIN_ID + ".MERGE_WAY"; //$NON-NLS-1$
+
// ITypedElement#getType()
public static final String NODE_TYPE__EMF_RESOURCESET = "NODE_TYPE__EMF_RESOURCESET"; //$NON-NLS-1$
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/groups/StructureMergeViewerGrouper.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/groups/StructureMergeViewerGrouper.java
index e2ed6a97e..29970dfa2 100644
--- a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/groups/StructureMergeViewerGrouper.java
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/groups/StructureMergeViewerGrouper.java
@@ -91,8 +91,8 @@ public final class StructureMergeViewerGrouper {
if (this.provider != provider) {
this.provider = provider;
filteredGroups = null;
- eventBus.post(provider);
refreshViewers();
+ eventBus.post(provider);
}
}
diff --git a/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
index 6f1c8b284..10caf73a8 100644
--- a/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
@@ -13,6 +13,7 @@ Export-Package: org.eclipse.emf.compare,
org.eclipse.emf.compare.equi,
org.eclipse.emf.compare.impl,
org.eclipse.emf.compare.internal;x-friends:="org.eclipse.emf.compare.logical,org.eclipse.emf.compare.ide",
+ org.eclipse.emf.compare.internal.merge;x-friends:="org.eclipse.emf.compare.rcp.ui,org.eclipse.emf.compare.edit,org.eclipse.emf.compare.ide.ui",
org.eclipse.emf.compare.internal.postprocessor.factories;x-internal:=true,
org.eclipse.emf.compare.internal.spec;x-friends:="org.eclipse.emf.compare.tests",
org.eclipse.emf.compare.internal.utils;
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/DiffMergeDataAdapter.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/DiffMergeDataAdapter.java
new file mode 100644
index 000000000..513e76b47
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/DiffMergeDataAdapter.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.internal.merge;
+
+import org.eclipse.emf.common.notify.impl.AdapterImpl;
+
+/**
+ * Adapter that help to know the way of merge and the editable sides of a difference.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class DiffMergeDataAdapter extends AdapterImpl implements IDiffMergeData {
+
+ /** The merge way. */
+ boolean leftToRight;
+
+ /** Left side is editable. */
+ boolean leftEditable;
+
+ /** Right side is editable. */
+ boolean rightEditable;
+
+ /**
+ * Constructor.
+ *
+ * @param leftToRight
+ * The merge way.
+ * @param leftEditable
+ * Left side editable.
+ * @param rightEditable
+ * Right side editable.
+ */
+ public DiffMergeDataAdapter(boolean leftToRight, boolean leftEditable, boolean rightEditable) {
+ this.leftToRight = leftToRight;
+ this.leftEditable = leftEditable;
+ this.rightEditable = rightEditable;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#hasBeenMergedToLeft()
+ */
+ public boolean hasBeenMergedToLeft() {
+ return !leftToRight;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#hasBeenMergedToRight()
+ */
+ public boolean hasBeenMergedToRight() {
+ return leftToRight;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#setMergedTo(boolean)
+ */
+ public void setMergedTo(boolean lToR) {
+ this.leftToRight = lToR;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#isLeftEditable()
+ */
+ public boolean isLeftEditable() {
+ return leftEditable;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#isRightEditable()
+ */
+ public boolean isRightEditable() {
+ return rightEditable;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#setLeftEditable(boolean)
+ */
+ public void setLeftEditable(boolean leftEditable) {
+ this.leftEditable = leftEditable;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.internal.merge.IDiffMergeData#setRightEditable(boolean)
+ */
+ public void setRightEditable(boolean rightEditable) {
+ this.rightEditable = rightEditable;
+ }
+
+ @Override
+ public boolean isAdapterForType(Object type) {
+ return type == IDiffMergeData.class;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IDiffMergeData.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IDiffMergeData.java
new file mode 100644
index 000000000..7531b546d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IDiffMergeData.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.internal.merge;
+
+/**
+ * Interface implemented by {@link org.eclipse.emf.compare.internal.merge.DiffMergeDataAdapter}. It helps to
+ * know the way of merge and the editable sides of a difference.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ */
+public interface IDiffMergeData {
+
+ /**
+ * Check if the difference has been merged from right to left.
+ *
+ * @return true if the difference has been merged from right to left, false otherwise.
+ */
+ boolean hasBeenMergedToLeft();
+
+ /**
+ * Check if the difference has been merged from left to right.
+ *
+ * @return true if the difference has been merged from left to right, false otherwise.
+ */
+ boolean hasBeenMergedToRight();
+
+ /**
+ * Set the way of merge.
+ *
+ * @param leftToRight
+ * true if the difference has been merge from left to right, false otehrwise.
+ */
+ void setMergedTo(boolean leftToRight);
+
+ /**
+ * Check if the left side of the difference is editable.
+ *
+ * @return true if the left side of the difference is editable, false otherwise.
+ */
+ boolean isLeftEditable();
+
+ /**
+ * Check if the right side of the difference is editable.
+ *
+ * @return true if the right side of the difference is editable, false otherwise.
+ */
+ boolean isRightEditable();
+
+ /**
+ * Set that the left side of the difference is editable or not.
+ *
+ * @param leftEditable
+ * true if the left side of the difference is editable, false otherwise.
+ */
+ void setLeftEditable(boolean leftEditable);
+
+ /**
+ * Set that the right side of the difference is editable or not.
+ *
+ * @param rightEditable
+ * true if the right side of the difference is editable, false otherwise.
+ */
+ void setRightEditable(boolean rightEditable);
+
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/utils/DiffUtil.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/utils/DiffUtil.java
index dfa567f9a..8dedf68c9 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/utils/DiffUtil.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/utils/DiffUtil.java
@@ -10,6 +10,11 @@
*******************************************************************************/
package org.eclipse.emf.compare.internal.utils;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.base.Predicates.or;
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
+
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
@@ -27,6 +32,8 @@ import java.util.Set;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.DifferenceSource;
@@ -866,6 +873,127 @@ public final class DiffUtil {
}
/**
+ * Get the list of all required differences for merge of the given difference (required, required of
+ * required...).
+ *
+ * @param diff
+ * the given difference.
+ * @param leftToRight
+ * the way of merge.
+ * @param originalDiffSource
+ * the source of the given diff.
+ * @return the list of all required differences.
+ * @since 3.0
+ */
+ public static Set<Diff> getRequires(Diff diff, boolean leftToRight, DifferenceSource originalDiffSource) {
+ return getRequires(diff, diff, leftToRight, originalDiffSource, Sets.newHashSet());
+ }
+
+ /**
+ * Get the list of all required differences for merge of the given original difference (required, required
+ * of required...).
+ *
+ * @param currentDiff
+ * the current difference being processed.
+ * @param originalDiff
+ * the original given difference.
+ * @param leftToRight
+ * the way of merge.
+ * @param originalDiffSource
+ * the source of the given diff.
+ * @param processedDiffs
+ * the list of already processed diffs.
+ * @return the list of all required differences.
+ * @since 3.0
+ */
+ private static Set<Diff> getRequires(Diff currentDiff, Diff originalDiff, boolean leftToRight,
+ DifferenceSource originalDiffSource, Set<Object> processedDiffs) {
+ Set<Diff> requires = Sets.newHashSet();
+ final List<Diff> diffRequires;
+ if (leftToRight) {
+ if (DifferenceSource.LEFT == originalDiffSource) {
+ diffRequires = currentDiff.getRequires();
+ } else if (DifferenceSource.RIGHT == originalDiffSource) {
+ diffRequires = currentDiff.getRequiredBy();
+ } else {
+ diffRequires = Collections.emptyList();
+ }
+ } else {
+ if (DifferenceSource.RIGHT == originalDiffSource) {
+ diffRequires = currentDiff.getRequires();
+ } else if (DifferenceSource.LEFT == originalDiffSource) {
+ diffRequires = currentDiff.getRequiredBy();
+ } else {
+ diffRequires = Collections.emptyList();
+ }
+ }
+ diffRequires.addAll(currentDiff.getRefinedBy());
+ for (Diff require : diffRequires) {
+ if (!originalDiff.equals(require) && !processedDiffs.contains(require)) {
+ processedDiffs.add(require);
+ requires.add(require);
+ requires.addAll(getRequires(require, originalDiff, leftToRight, originalDiffSource,
+ processedDiffs));
+ }
+ }
+ return requires;
+ }
+
+ /**
+ * Get the list of unmergeable differences after the merge of the given difference.
+ *
+ * @param diff
+ * the given difference.
+ * @param leftToRight
+ * the way of merge.
+ * @return the list of unmergeable differences.
+ * @since 3.0
+ */
+ public static Set<Diff> getUnmergeables(Diff diff, boolean leftToRight) {
+ Set<Diff> unmergeables = Sets.newHashSet();
+ Conflict conflict = diff.getConflict();
+ if (conflict != null && conflict.getKind() == ConflictKind.REAL) {
+ for (Diff diffConflict : conflict.getDifferences()) {
+ if (leftToRight
+ && and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(
+ diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ } else if (leftToRight
+ && and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE)))
+ .apply(diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ } else if (!leftToRight
+ && and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE)))
+ .apply(diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ } else if (!leftToRight
+ && and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(
+ diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ }
+
+ }
+ }
+ return unmergeables;
+ }
+
+ /**
* Retrieves the "source" list of the given {@code diff}. This will be different according to the kind of
* change and the direction of the merging.
*
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
index 8d852314c..93460f792 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
@@ -1,190 +1,190 @@
-/*******************************************************************************
- * Copyright (c) 2012, 2013 Obeo.
- * 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:
- * Obeo - initial API and implementation
- *******************************************************************************/
-package org.eclipse.emf.compare.merge;
-
-import java.util.List;
-
-import org.eclipse.emf.common.util.Monitor;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.utils.EMFCompareCopier;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.ecore.util.InternalEList;
-
-/**
- * Abstract implementation of an {@link IMerger}. This can be used as a base implementation to avoid
- * re-implementing the whole contract.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- * @since 3.0
- */
-public abstract class AbstractMerger implements IMerger {
- /** Ranking of this merger. */
- private int ranking;
-
- /** Registry from which this merger has been created.. */
- private Registry registry;
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#getRanking()
- */
- public int getRanking() {
- return ranking;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#setRanking(int)
- */
- public void setRanking(int r) {
- ranking = r;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#getRegistry()
- */
- public Registry getRegistry() {
- return registry;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#setRegistry(org.eclipse.emf.compare.merge.IMerger.Registry)
- */
- public void setRegistry(Registry registry) {
- if (this.registry != null && registry != null) {
- throw new IllegalStateException("The registry has to be set only once."); //$NON-NLS-1$
- }
- this.registry = registry;
- }
-
- /**
- * This will merge all {@link Diff#getRequiredBy() differences that require} {@code diff} in the given
- * direction.
- *
- * @param diff
- * We need to merge all differences that require this one (see {@link Diff#getRequiredBy()}.
- * @param rightToLeft
- * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all differences that require
- * {@code diff}. Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeRequiredBy(Diff diff, boolean rightToLeft, Monitor monitor) {
- // TODO log back to the user what we will merge along?
- for (Diff dependency : diff.getRequiredBy()) {
- // TODO: what to do when state = Discarded but is required?
- mergeDiff(dependency, rightToLeft, monitor);
- }
- }
-
- /**
- * This will merge all {@link Diff#getRequires() differences required by} {@code diff} in the given
- * direction.
- *
- * @param diff
- * The difference which requirements we need to merge.
- * @param rightToLeft
- * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all required differences.
- * Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeRequires(Diff diff, boolean rightToLeft, Monitor monitor) {
- // TODO log back to the user what we will merge along?
- for (Diff dependency : diff.getRequires()) {
- // TODO: what to do when state = Discarded but is required?
- mergeDiff(dependency, rightToLeft, monitor);
- }
- }
-
- /**
- * This can be used by mergers to merge another (required, equivalent...) difference using the right
- * merger for that diff.
- *
- * @param diff
- * The diff we need to merge.
- * @param rightToLeft
- * Direction of that merge.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeDiff(Diff diff, boolean rightToLeft, Monitor monitor) {
- if (rightToLeft) {
- final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
- delegate.copyRightToLeft(diff, monitor);
- } else {
- final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
- delegate.copyLeftToRight(diff, monitor);
- }
- }
-
- /**
- * This will create a copy of the given EObject that can be used as the target of an addition (or the
- * reverting of a deletion).
- * <p>
- * The target will be self-contained and will have no reference towards any other EObject set (neither
- * containment nor "classic" references). All of its attributes' values will match the given
- * {@code referenceObject}'s.
- * </p>
- *
- * @param referenceObject
- * The EObject for which we'll create a copy.
- * @return A self-contained copy of {@code referenceObject}.
- * @see EMFCompareCopier#copy(EObject)
- */
- protected EObject createCopy(EObject referenceObject) {
- /*
- * We can't simply use EcoreUtil.copy. References will have their own diffs and will thus be merged
- * later on.
- */
- final EcoreUtil.Copier copier = new EMFCompareCopier();
- return copier.copy(referenceObject);
- }
-
- /**
- * Adds the given {@code value} into the given {@code list} at the given {@code index}. An {@code index}
- * under than zero or above the list's size will mean that the value should be appended at the end of the
- * list.
- *
- * @param list
- * The list into which {@code value} should be added.
- * @param value
- * The value we need to add to {@code list}.
- * @param <E>
- * Type of objects contained in the list.
- * @param insertionIndex
- * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
- * should be appended at the end of the list.
- */
- @SuppressWarnings("unchecked")
- protected <E> void addAt(List<E> list, E value, int insertionIndex) {
- if (list instanceof InternalEList<?>) {
- if (insertionIndex < 0 || insertionIndex > list.size()) {
- ((InternalEList<Object>)list).addUnique(value);
- } else {
- ((InternalEList<Object>)list).addUnique(insertionIndex, value);
- }
- } else {
- if (insertionIndex < 0 || insertionIndex > list.size()) {
- list.add(value);
- } else {
- list.add(insertionIndex, value);
- }
- }
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.merge;
+
+import java.util.List;
+
+import org.eclipse.emf.common.util.Monitor;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.utils.EMFCompareCopier;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.util.InternalEList;
+
+/**
+ * Abstract implementation of an {@link IMerger}. This can be used as a base implementation to avoid
+ * re-implementing the whole contract.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @since 3.0
+ */
+public abstract class AbstractMerger implements IMerger {
+ /** Ranking of this merger. */
+ private int ranking;
+
+ /** Registry from which this merger has been created.. */
+ private Registry registry;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#getRanking()
+ */
+ public int getRanking() {
+ return ranking;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#setRanking(int)
+ */
+ public void setRanking(int r) {
+ ranking = r;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#getRegistry()
+ */
+ public Registry getRegistry() {
+ return registry;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#setRegistry(org.eclipse.emf.compare.merge.IMerger.Registry)
+ */
+ public void setRegistry(Registry registry) {
+ if (this.registry != null && registry != null) {
+ throw new IllegalStateException("The registry has to be set only once."); //$NON-NLS-1$
+ }
+ this.registry = registry;
+ }
+
+ /**
+ * This will merge all {@link Diff#getRequiredBy() differences that require} {@code diff} in the given
+ * direction.
+ *
+ * @param diff
+ * We need to merge all differences that require this one (see {@link Diff#getRequiredBy()}.
+ * @param rightToLeft
+ * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all differences that require
+ * {@code diff}. Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeRequiredBy(Diff diff, boolean rightToLeft, Monitor monitor) {
+ // TODO log back to the user what we will merge along?
+ for (Diff dependency : diff.getRequiredBy()) {
+ // TODO: what to do when state = Discarded but is required?
+ mergeDiff(dependency, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * This will merge all {@link Diff#getRequires() differences required by} {@code diff} in the given
+ * direction.
+ *
+ * @param diff
+ * The difference which requirements we need to merge.
+ * @param rightToLeft
+ * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all required differences.
+ * Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeRequires(Diff diff, boolean rightToLeft, Monitor monitor) {
+ // TODO log back to the user what we will merge along?
+ for (Diff dependency : diff.getRequires()) {
+ // TODO: what to do when state = Discarded but is required?
+ mergeDiff(dependency, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * This can be used by mergers to merge another (required, equivalent...) difference using the right
+ * merger for that diff.
+ *
+ * @param diff
+ * The diff we need to merge.
+ * @param rightToLeft
+ * Direction of that merge.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeDiff(Diff diff, boolean rightToLeft, Monitor monitor) {
+ if (rightToLeft) {
+ final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
+ delegate.copyRightToLeft(diff, monitor);
+ } else {
+ final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
+ delegate.copyLeftToRight(diff, monitor);
+ }
+ }
+
+ /**
+ * This will create a copy of the given EObject that can be used as the target of an addition (or the
+ * reverting of a deletion).
+ * <p>
+ * The target will be self-contained and will have no reference towards any other EObject set (neither
+ * containment nor "classic" references). All of its attributes' values will match the given
+ * {@code referenceObject}'s.
+ * </p>
+ *
+ * @param referenceObject
+ * The EObject for which we'll create a copy.
+ * @return A self-contained copy of {@code referenceObject}.
+ * @see EMFCompareCopier#copy(EObject)
+ */
+ protected EObject createCopy(EObject referenceObject) {
+ /*
+ * We can't simply use EcoreUtil.copy. References will have their own diffs and will thus be merged
+ * later on.
+ */
+ final EcoreUtil.Copier copier = new EMFCompareCopier();
+ return copier.copy(referenceObject);
+ }
+
+ /**
+ * Adds the given {@code value} into the given {@code list} at the given {@code index}. An {@code index}
+ * under than zero or above the list's size will mean that the value should be appended at the end of the
+ * list.
+ *
+ * @param list
+ * The list into which {@code value} should be added.
+ * @param value
+ * The value we need to add to {@code list}.
+ * @param <E>
+ * Type of objects contained in the list.
+ * @param insertionIndex
+ * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
+ * should be appended at the end of the list.
+ */
+ @SuppressWarnings("unchecked")
+ protected <E> void addAt(List<E> list, E value, int insertionIndex) {
+ if (list instanceof InternalEList<?>) {
+ if (insertionIndex < 0 || insertionIndex > list.size()) {
+ ((InternalEList<Object>)list).addUnique(value);
+ } else {
+ ((InternalEList<Object>)list).addUnique(insertionIndex, value);
+ }
+ } else {
+ if (insertionIndex < 0 || insertionIndex > list.size()) {
+ list.add(value);
+ } else {
+ list.add(insertionIndex, value);
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
index f77eef08e..ce9d162b4 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
@@ -1,753 +1,753 @@
-/*******************************************************************************
- * Copyright (c) 2012, 2013 Obeo.
- * 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:
- * Obeo - initial API and implementation
- *******************************************************************************/
-package org.eclipse.emf.compare.merge;
-
-import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
-
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
-
-import java.util.Iterator;
-import java.util.List;
-
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.common.util.Monitor;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceSource;
-import org.eclipse.emf.compare.DifferenceState;
-import org.eclipse.emf.compare.Match;
-import org.eclipse.emf.compare.ReferenceChange;
-import org.eclipse.emf.compare.internal.utils.DiffUtil;
-import org.eclipse.emf.compare.utils.IEqualityHelper;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.ecore.xmi.XMIResource;
-
-/**
- * This specific implementation of {@link AbstractMerger} will be used to merge reference changes.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
-public class ReferenceChangeMerger extends AbstractMerger {
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
- */
- public boolean isMergerFor(Diff target) {
- return target instanceof ReferenceChange;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,
- * org.eclipse.emf.common.util.Monitor)
- */
- public void copyLeftToRight(Diff target, Monitor monitor) {
- // Don't merge an already merged (or discarded) diff
- if (target.getState() != DifferenceState.UNRESOLVED) {
- return;
- }
- final ReferenceChange diff = (ReferenceChange)target;
-
- // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
- diff.setState(DifferenceState.MERGED);
-
- if (diff.getSource() == DifferenceSource.LEFT) {
- // merge all "requires" diffs
- mergeRequires(diff, false, monitor);
- handleImplies(diff, false, monitor);
- } else {
- // merge all "required by" diffs
- mergeRequiredBy(diff, false, monitor);
- handleImpliedBy(diff, false, monitor);
- }
-
- boolean hasToBeMerged = true;
- if (diff.getEquivalence() != null) {
- hasToBeMerged = handleEquivalences(diff, false, monitor);
- }
-
- if (hasToBeMerged) {
- if (diff.getSource() == DifferenceSource.LEFT) {
- accept(diff, false);
- } else {
- reject(diff, false);
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff,
- * org.eclipse.emf.common.util.Monitor)
- */
- public void copyRightToLeft(Diff target, Monitor monitor) {
- // Don't merge an already merged (or discarded) diff
- if (target.getState() != DifferenceState.UNRESOLVED) {
- return;
- }
- final ReferenceChange diff = (ReferenceChange)target;
-
- // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
- diff.setState(DifferenceState.MERGED);
-
- if (diff.getSource() == DifferenceSource.LEFT) {
- // merge all "required by" diffs
- mergeRequiredBy(diff, true, monitor);
- handleImpliedBy(diff, true, monitor);
- } else {
- // merge all "requires" diffs
- mergeRequires(diff, true, monitor);
- handleImplies(diff, true, monitor);
- }
-
- boolean hasToBeMerged = true;
- if (diff.getEquivalence() != null) {
- hasToBeMerged = handleEquivalences(diff, true, monitor);
- }
-
- if (hasToBeMerged) {
- if (diff.getSource() == DifferenceSource.LEFT) {
- reject(diff, true);
- } else {
- accept(diff, true);
- }
- }
- }
-
- /**
- * Merge the given difference rejecting it.
- *
- * @param diff
- * The difference to merge.
- * @param rightToLeft
- * The direction of the merge.
- */
- private void reject(final ReferenceChange diff, boolean rightToLeft) {
- DifferenceSource source = diff.getSource();
- switch (diff.getKind()) {
- case ADD:
- // We have a ADD on left, thus nothing in right. We need to revert the addition
- removeFromTarget(diff, rightToLeft);
- break;
- case DELETE:
- // DELETE in the left, thus an element in right. We need to re-create that element
- addInTarget(diff, rightToLeft);
- break;
- case MOVE:
- moveElement(diff, rightToLeft);
- break;
- case CHANGE:
- EObject container = null;
- if (source == DifferenceSource.LEFT) {
- container = diff.getMatch().getLeft();
-
- } else {
- container = diff.getMatch().getRight();
- }
- // Is it an unset?
- if (container != null) {
- final EObject leftValue = (EObject)container.eGet(diff.getReference(), false);
- if (leftValue == null) {
- // Value has been unset in the right, and we are merging towards right.
- // We need to re-add this element
- addInTarget(diff, rightToLeft);
- } else {
- // We'll actually need to "reset" this reference to its original value
- resetInTarget(diff, rightToLeft);
- }
- } else {
- // we have no left, and the source is on the left. Can only be an unset
- addInTarget(diff, rightToLeft);
- }
- break;
- default:
- break;
- }
- }
-
- /**
- * Merge the given difference accepting it.
- *
- * @param diff
- * The difference to merge.
- * @param rightToLeft
- * The direction of the merge.
- */
- private void accept(final ReferenceChange diff, boolean rightToLeft) {
- DifferenceSource source = diff.getSource();
- switch (diff.getKind()) {
- case ADD:
- // Create the same element in right
- addInTarget(diff, rightToLeft);
- break;
- case DELETE:
- // Delete that same element from right
- removeFromTarget(diff, rightToLeft);
- break;
- case MOVE:
- moveElement(diff, rightToLeft);
- break;
- case CHANGE:
- EObject container = null;
- if (source == DifferenceSource.LEFT) {
- container = diff.getMatch().getLeft();
- } else {
- container = diff.getMatch().getRight();
- }
- // Is it an unset?
- if (container != null) {
- final EObject leftValue = (EObject)container.eGet(diff.getReference(), false);
- if (leftValue == null) {
- removeFromTarget(diff, rightToLeft);
- } else {
- addInTarget(diff, rightToLeft);
- }
- } else {
- // we have no left, and the source is on the left. Can only be an unset
- removeFromTarget(diff, rightToLeft);
- }
- break;
- default:
- break;
- }
- }
-
- /**
- * Mark as MERGED all the implied differences recursively from the given one.
- *
- * @param diff
- * The difference from which the implications have to be marked.
- * @param rightToLeft
- * The direction of the merge.
- * @param monitor
- * Monitor.
- */
- private void handleImplies(Diff diff, boolean rightToLeft, Monitor monitor) {
- for (Diff implied : diff.getImplies()) {
- implied.setState(DifferenceState.MERGED);
- handleImplies(implied, rightToLeft, monitor);
- }
- }
-
- /**
- * Mark as MERGED all the implying differences recursively from the given one.
- *
- * @param diff
- * The difference from which the implications have to be marked.
- * @param rightToLeft
- * The direction of the merge.
- * @param monitor
- * Monitor.
- */
- private void handleImpliedBy(Diff diff, boolean rightToLeft, Monitor monitor) {
- for (Diff impliedBy : diff.getImpliedBy()) {
- impliedBy.setState(DifferenceState.MERGED);
- handleImpliedBy(impliedBy, rightToLeft, monitor);
- }
- }
-
- /**
- * This will be called when trying to copy a "MOVE" diff.
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Whether we should move the value in the left or right side.
- */
- protected void moveElement(ReferenceChange diff, boolean rightToLeft) {
- final Comparison comparison = diff.getMatch().getComparison();
- final Match valueMatch = comparison.getMatch(diff.getValue());
- final EReference reference = diff.getReference();
-
- final EObject expectedContainer;
- if (reference.isContainment()) {
- /*
- * We cannot "trust" the holding match (getMatch) in this case. However, "valueMatch" cannot be
- * null : we cannot have detected a move if the moved element is not matched on both sides. Use
- * that information to retrieve the proper "target" container.
- */
- final Match targetContainerMatch;
- // If it exists, use the source side's container as reference
- if (rightToLeft && valueMatch.getRight() != null) {
- targetContainerMatch = comparison.getMatch(valueMatch.getRight().eContainer());
- } else if (!rightToLeft && valueMatch.getLeft() != null) {
- targetContainerMatch = comparison.getMatch(valueMatch.getLeft().eContainer());
- } else {
- // Otherwise, the value we're moving on one side has been removed from its source side.
- targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer());
- }
- if (rightToLeft) {
- expectedContainer = targetContainerMatch.getLeft();
- } else {
- expectedContainer = targetContainerMatch.getRight();
- }
- } else if (rightToLeft) {
- expectedContainer = diff.getMatch().getLeft();
- } else {
- expectedContainer = diff.getMatch().getRight();
- }
- if (expectedContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final EObject expectedValue;
- if (valueMatch == null) {
- // The value being moved is out of the scope
- /*
- * Note : there should not be a way to end up with a "move" for an out of scope value : a move can
- * only be detected if the object is matched on both sides, otherwise all we can see is "add" and
- * "delete"... Is this "fallback" code even reachable? If so, how?
- */
- // We need to look it up
- if (reference.isMany()) {
- @SuppressWarnings("unchecked")
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- expectedValue = findMatchIn(comparison, targetList, diff.getValue());
- } else {
- expectedValue = (EObject)expectedContainer.eGet(reference);
- }
- } else {
- if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
- }
- // We now know the target container, target reference and target value.
- doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
- }
-
- /**
- * This will do the actual work of moving the element into its reference. All sanity checks were made in
- * {@link #moveElement(boolean)} and no more verification will be made here.
- *
- * @param diff
- * The diff we are currently merging.
- * @param comparison
- * Comparison holding this Diff.
- * @param expectedContainer
- * The container in which we are reorganizing a reference.
- * @param expectedValue
- * The value that is to be moved within its reference.
- * @param rightToLeft
- * Whether we should move the value in the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void doMove(ReferenceChange diff, Comparison comparison, EObject expectedContainer,
- EObject expectedValue, boolean rightToLeft) {
- final EReference reference = diff.getReference();
- if (reference.isMany()) {
- // Element to move cannot be part of the LCS... or there would not be a MOVE diff
- int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
-
- /*
- * However, it could still have been located "before" its new index, in which case we need to take
- * it into account.
- */
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- final int currentIndex = targetList.indexOf(expectedValue);
- if (insertionIndex > currentIndex && currentIndex >= 0) {
- insertionIndex--;
- }
-
- if (currentIndex == -1) {
- // happens for container changes for example.
- if (!reference.isContainment()) {
- targetList.remove(expectedValue);
- }
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- targetList.add(expectedValue);
- } else {
- targetList.add(insertionIndex, expectedValue);
- }
- } else if (targetList instanceof EList<?>) {
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- ((EList<EObject>)targetList).move(targetList.size() - 1, expectedValue);
- } else {
- ((EList<EObject>)targetList).move(insertionIndex, expectedValue);
- }
- } else {
- targetList.remove(expectedValue);
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- targetList.add(expectedValue);
- } else {
- targetList.add(insertionIndex, expectedValue);
- }
- }
- } else {
- expectedContainer.eSet(reference, expectedValue);
- }
- }
-
- /**
- * This will be called when we need to create an element in the target side.
- * <p>
- * All necessary sanity checks have been made to ensure that the current operation is one that should
- * create an object in its side or add an objet to a reference. In other words, either :
- * <ul>
- * <li>We are copying from right to left and
- * <ul>
- * <li>we are copying an addition to the right side (we need to create the same object in the left), or</li>
- * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
- * </ul>
- * </li>
- * <li>We are copying from left to right and
- * <ul>
- * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
- * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
- * </ul>
- * </li>
- * </ul>
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us whether we are to add an object on the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void addInTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EObject expectedContainer;
- if (rightToLeft) {
- expectedContainer = match.getLeft();
- } else {
- expectedContainer = match.getRight();
- }
-
- if (expectedContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final Comparison comparison = match.getComparison();
- final EReference reference = diff.getReference();
- final EObject expectedValue;
- final Match valueMatch = comparison.getMatch(diff.getValue());
- if (valueMatch == null) {
- // This is an out of scope value.
- if (diff.getValue().eIsProxy()) {
- // Copy the proxy
- expectedValue = EcoreUtil.copy(diff.getValue());
- } else {
- // Use the same value.
- expectedValue = diff.getValue();
- }
- } else if (rightToLeft) {
- if (reference.isContainment()) {
- expectedValue = createCopy(diff.getValue());
- valueMatch.setLeft(expectedValue);
- } else {
- expectedValue = valueMatch.getLeft();
- }
- } else {
- if (reference.isContainment()) {
- expectedValue = createCopy(diff.getValue());
- valueMatch.setRight(expectedValue);
- } else {
- expectedValue = valueMatch.getRight();
- }
- }
-
- // We have the container, reference and value. We need to know the insertion index.
- if (reference.isMany()) {
- final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
-
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- addAt(targetList, expectedValue, insertionIndex);
- } else {
- expectedContainer.eSet(reference, expectedValue);
- }
-
- if (reference.isContainment()) {
- // Copy XMI ID when applicable.
- final Resource initialResource = diff.getValue().eResource();
- final Resource targetResource = expectedValue.eResource();
- if (initialResource instanceof XMIResource && targetResource instanceof XMIResource) {
- ((XMIResource)targetResource).setID(expectedValue, ((XMIResource)initialResource).getID(diff
- .getValue()));
- }
- }
- }
-
- /**
- * This will be called when we need to remove an element from the target side.
- * <p>
- * All necessary sanity checks have been made to ensure that the current operation is one that should
- * delete an object. In other words, we are :
- * <ul>
- * <li>Copying from right to left and either
- * <ul>
- * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
- * <li>we are copying an addition to the left side (we need to revert the addition).</li>
- * </ul>
- * </li>
- * <li>Copying from left to right and either
- * <ul>
- * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
- * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
- * </ul>
- * </li>
- * </ul>
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us whether we are to add an object on the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void removeFromTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EReference reference = diff.getReference();
- final EObject currentContainer;
- if (rightToLeft) {
- currentContainer = match.getLeft();
- } else {
- currentContainer = match.getRight();
- }
- final Comparison comparison = match.getComparison();
- final Match valueMatch = comparison.getMatch(diff.getValue());
-
- if (currentContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final EObject expectedValue;
- if (valueMatch == null) {
- // value is out of the scope... we need to look it up
- if (reference.isMany()) {
- final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
- expectedValue = findMatchIn(comparison, targetList, diff.getValue());
- } else {
- // the value will not be needed anyway
- expectedValue = null;
- }
- } else if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
-
- // We have the container, reference and value to remove. Expected value can be null when the
- // deletion was made on both side (i.e. a pseudo delete)
- if (reference.isContainment() && expectedValue != null) {
- EcoreUtil.remove(expectedValue);
- if (rightToLeft && valueMatch != null) {
- valueMatch.setLeft(null);
- } else if (valueMatch != null) {
- valueMatch.setRight(null);
- }
- // TODO remove dangling? remove empty Match?
- } else if (reference.isMany()) {
- /*
- * TODO if the same value appears twice, should we try and find the one that has actually been
- * deleted? Can it happen? For now, remove the first occurence we find.
- */
- final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
- targetList.remove(expectedValue);
- } else {
- currentContainer.eUnset(reference);
- }
- }
-
- /**
- * This will be called by the merge operations in order to reset a reference to its original value, be
- * that the left or right side.
- * <p>
- * Should never be called on multi-valued references.
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us the direction of this merge operation.
- */
- protected void resetInTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EReference reference = diff.getReference();
- final EObject targetContainer;
- if (rightToLeft) {
- targetContainer = match.getLeft();
- } else {
- targetContainer = match.getRight();
- }
-
- final EObject originContainer;
- if (match.getComparison().isThreeWay()) {
- originContainer = match.getOrigin();
- } else if (rightToLeft) {
- originContainer = match.getRight();
- } else {
- originContainer = match.getLeft();
- }
-
- if (originContainer == null || !safeEIsSet(targetContainer, reference)
- || !safeEIsSet(originContainer, reference)) {
- targetContainer.eUnset(reference);
- } else {
- final EObject originalValue = (EObject)originContainer.eGet(reference);
- final Match valueMatch = match.getComparison().getMatch(originalValue);
- final EObject expectedValue;
- if (valueMatch == null) {
- // Value is out of the scope, use it as-is
- expectedValue = originalValue;
- } else if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
- targetContainer.eSet(reference, expectedValue);
- }
- }
-
- /**
- * Handles the equivalences of this difference.
- * <p>
- * Note that in certain cases, we'll merge our opposite instead of merging this diff. Specifically, we'll
- * do that for one-to-many eOpposites : we'll merge the 'many' side instead of the 'unique' one. This
- * allows us not to worry about the order of the references on that 'many' side.
- * </p>
- * <p>
- * This is called before the merge of <code>this</code>. In short, if this returns <code>false</code>, we
- * won't carry on merging <code>this</code> after returning.
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Direction of the merge.
- * @param monitor
- * The monitor to use in order to report progress information.
- * @return <code>true</code> if the current difference should still be merged after handling its
- * equivalences, <code>false</code> if it should be considered "already merged".
- */
- protected boolean handleEquivalences(ReferenceChange diff, boolean rightToLeft, Monitor monitor) {
- final EReference reference = diff.getReference();
- boolean continueMerge = true;
- for (Diff equivalent : diff.getEquivalence().getDifferences()) {
- // For 1..*, merge diff on many-valued to preserve ordering
- if (equivalent instanceof ReferenceChange
- && reference.getEOpposite() == ((ReferenceChange)equivalent).getReference()
- && equivalent.getState() == DifferenceState.UNRESOLVED) {
- // This equivalence is on our eOpposite. Should we merge it instead of 'this'?
- final boolean mergeEquivalence = !reference.isMany()
- && ((ReferenceChange)equivalent).getReference().isMany();
- if (mergeEquivalence) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- }
- }
-
- /*
- * If one of the equivalent differences is implied or implying (depending on the merge direction)
- * a merged diff, then we have a dependency loop : the "current" difference has already been
- * merged because of this implication. This will allow us to break out of that loop.
- */
- if (rightToLeft) {
- if (diff.getSource() == DifferenceSource.LEFT) {
- continueMerge = continueMerge
- && !containsAny(diff.getRequiredBy(), equivalent.getImplies());
- } else {
- continueMerge = continueMerge
- && !containsAny(diff.getRequires(), equivalent.getImpliedBy());
- }
- } else {
- if (diff.getSource() == DifferenceSource.LEFT) {
- continueMerge = continueMerge
- && !containsAny(diff.getRequires(), equivalent.getImpliedBy());
- } else {
- continueMerge = continueMerge
- && !containsAny(diff.getRequiredBy(), equivalent.getImplies());
- }
- }
-
- equivalent.setState(DifferenceState.MERGED);
- }
- return continueMerge;
- }
-
- /**
- * Utility method to check that the first sequence contains one of the elements of the second sequence at
- * least.
- *
- * @param sequence1
- * The first sequence.
- * @param sequence2
- * The second sequence.
- * @return True if the given first sequence contains one of the elements of the second sequence at least.
- * false otherwise.
- */
- private boolean containsAny(List<? extends EObject> sequence1, List<? extends EObject> sequence2) {
- return Iterables.any(sequence2, Predicates.in(sequence1));
- }
-
- /**
- * Seeks a match of the given {@code element} in the given list, using the equality helper to find it.
- * This is only used when moving or deleting proxies for now.
- *
- * @param comparison
- * The comparison which Diff we are currently merging.
- * @param list
- * The list from which we seek a value.
- * @param element
- * The value for which we need a match in {@code list}.
- * @return The match of {@code element} in {@code list}, {@code null} if none.
- */
- protected EObject findMatchIn(Comparison comparison, List<EObject> list, EObject element) {
- final IEqualityHelper helper = comparison.getEqualityHelper();
- final Iterator<EObject> it = list.iterator();
- while (it.hasNext()) {
- final EObject next = it.next();
- if (helper.matchingValues(next, element)) {
- return next;
- }
- }
- return null;
- }
-
- /**
- * This will be used by the distinct merge actions in order to find the index at which a value should be
- * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
- * more on this.
- * <p>
- * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
- * be considered as "no index" and the value will be inserted at the end of its target list.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param diff
- * The diff which merging will trigger the need for an insertion index in its target list.
- * @param rightToLeft
- * {@code true} if the merging will be done into the left list, so that we should consider the
- * right model as the source and the left as the target.
- * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
- * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
- * target list.
- * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
- */
- protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
- return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Obeo.
+ * 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:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.merge;
+
+import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.common.util.Monitor;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.ReferenceChange;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.utils.IEqualityHelper;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.xmi.XMIResource;
+
+/**
+ * This specific implementation of {@link AbstractMerger} will be used to merge reference changes.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class ReferenceChangeMerger extends AbstractMerger {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
+ */
+ public boolean isMergerFor(Diff target) {
+ return target instanceof ReferenceChange;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,
+ * org.eclipse.emf.common.util.Monitor)
+ */
+ public void copyLeftToRight(Diff target, Monitor monitor) {
+ // Don't merge an already merged (or discarded) diff
+ if (target.getState() != DifferenceState.UNRESOLVED) {
+ return;
+ }
+ final ReferenceChange diff = (ReferenceChange)target;
+
+ // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
+ diff.setState(DifferenceState.MERGED);
+
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ // merge all "requires" diffs
+ mergeRequires(diff, false, monitor);
+ handleImplies(diff, false, monitor);
+ } else {
+ // merge all "required by" diffs
+ mergeRequiredBy(diff, false, monitor);
+ handleImpliedBy(diff, false, monitor);
+ }
+
+ boolean hasToBeMerged = true;
+ if (diff.getEquivalence() != null) {
+ hasToBeMerged = handleEquivalences(diff, false, monitor);
+ }
+
+ if (hasToBeMerged) {
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ accept(diff, false);
+ } else {
+ reject(diff, false);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff,
+ * org.eclipse.emf.common.util.Monitor)
+ */
+ public void copyRightToLeft(Diff target, Monitor monitor) {
+ // Don't merge an already merged (or discarded) diff
+ if (target.getState() != DifferenceState.UNRESOLVED) {
+ return;
+ }
+ final ReferenceChange diff = (ReferenceChange)target;
+
+ // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
+ diff.setState(DifferenceState.MERGED);
+
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ // merge all "required by" diffs
+ mergeRequiredBy(diff, true, monitor);
+ handleImpliedBy(diff, true, monitor);
+ } else {
+ // merge all "requires" diffs
+ mergeRequires(diff, true, monitor);
+ handleImplies(diff, true, monitor);
+ }
+
+ boolean hasToBeMerged = true;
+ if (diff.getEquivalence() != null) {
+ hasToBeMerged = handleEquivalences(diff, true, monitor);
+ }
+
+ if (hasToBeMerged) {
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ reject(diff, true);
+ } else {
+ accept(diff, true);
+ }
+ }
+ }
+
+ /**
+ * Merge the given difference rejecting it.
+ *
+ * @param diff
+ * The difference to merge.
+ * @param rightToLeft
+ * The direction of the merge.
+ */
+ private void reject(final ReferenceChange diff, boolean rightToLeft) {
+ DifferenceSource source = diff.getSource();
+ switch (diff.getKind()) {
+ case ADD:
+ // We have a ADD on left, thus nothing in right. We need to revert the addition
+ removeFromTarget(diff, rightToLeft);
+ break;
+ case DELETE:
+ // DELETE in the left, thus an element in right. We need to re-create that element
+ addInTarget(diff, rightToLeft);
+ break;
+ case MOVE:
+ moveElement(diff, rightToLeft);
+ break;
+ case CHANGE:
+ EObject container = null;
+ if (source == DifferenceSource.LEFT) {
+ container = diff.getMatch().getLeft();
+
+ } else {
+ container = diff.getMatch().getRight();
+ }
+ // Is it an unset?
+ if (container != null) {
+ final EObject leftValue = (EObject)container.eGet(diff.getReference(), false);
+ if (leftValue == null) {
+ // Value has been unset in the right, and we are merging towards right.
+ // We need to re-add this element
+ addInTarget(diff, rightToLeft);
+ } else {
+ // We'll actually need to "reset" this reference to its original value
+ resetInTarget(diff, rightToLeft);
+ }
+ } else {
+ // we have no left, and the source is on the left. Can only be an unset
+ addInTarget(diff, rightToLeft);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Merge the given difference accepting it.
+ *
+ * @param diff
+ * The difference to merge.
+ * @param rightToLeft
+ * The direction of the merge.
+ */
+ private void accept(final ReferenceChange diff, boolean rightToLeft) {
+ DifferenceSource source = diff.getSource();
+ switch (diff.getKind()) {
+ case ADD:
+ // Create the same element in right
+ addInTarget(diff, rightToLeft);
+ break;
+ case DELETE:
+ // Delete that same element from right
+ removeFromTarget(diff, rightToLeft);
+ break;
+ case MOVE:
+ moveElement(diff, rightToLeft);
+ break;
+ case CHANGE:
+ EObject container = null;
+ if (source == DifferenceSource.LEFT) {
+ container = diff.getMatch().getLeft();
+ } else {
+ container = diff.getMatch().getRight();
+ }
+ // Is it an unset?
+ if (container != null) {
+ final EObject leftValue = (EObject)container.eGet(diff.getReference(), false);
+ if (leftValue == null) {
+ removeFromTarget(diff, rightToLeft);
+ } else {
+ addInTarget(diff, rightToLeft);
+ }
+ } else {
+ // we have no left, and the source is on the left. Can only be an unset
+ removeFromTarget(diff, rightToLeft);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Mark as MERGED all the implied differences recursively from the given one.
+ *
+ * @param diff
+ * The difference from which the implications have to be marked.
+ * @param rightToLeft
+ * The direction of the merge.
+ * @param monitor
+ * Monitor.
+ */
+ private void handleImplies(Diff diff, boolean rightToLeft, Monitor monitor) {
+ for (Diff implied : diff.getImplies()) {
+ implied.setState(DifferenceState.MERGED);
+ handleImplies(implied, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * Mark as MERGED all the implying differences recursively from the given one.
+ *
+ * @param diff
+ * The difference from which the implications have to be marked.
+ * @param rightToLeft
+ * The direction of the merge.
+ * @param monitor
+ * Monitor.
+ */
+ private void handleImpliedBy(Diff diff, boolean rightToLeft, Monitor monitor) {
+ for (Diff impliedBy : diff.getImpliedBy()) {
+ impliedBy.setState(DifferenceState.MERGED);
+ handleImpliedBy(impliedBy, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * This will be called when trying to copy a "MOVE" diff.
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Whether we should move the value in the left or right side.
+ */
+ protected void moveElement(ReferenceChange diff, boolean rightToLeft) {
+ final Comparison comparison = diff.getMatch().getComparison();
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+ final EReference reference = diff.getReference();
+
+ final EObject expectedContainer;
+ if (reference.isContainment()) {
+ /*
+ * We cannot "trust" the holding match (getMatch) in this case. However, "valueMatch" cannot be
+ * null : we cannot have detected a move if the moved element is not matched on both sides. Use
+ * that information to retrieve the proper "target" container.
+ */
+ final Match targetContainerMatch;
+ // If it exists, use the source side's container as reference
+ if (rightToLeft && valueMatch.getRight() != null) {
+ targetContainerMatch = comparison.getMatch(valueMatch.getRight().eContainer());
+ } else if (!rightToLeft && valueMatch.getLeft() != null) {
+ targetContainerMatch = comparison.getMatch(valueMatch.getLeft().eContainer());
+ } else {
+ // Otherwise, the value we're moving on one side has been removed from its source side.
+ targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer());
+ }
+ if (rightToLeft) {
+ expectedContainer = targetContainerMatch.getLeft();
+ } else {
+ expectedContainer = targetContainerMatch.getRight();
+ }
+ } else if (rightToLeft) {
+ expectedContainer = diff.getMatch().getLeft();
+ } else {
+ expectedContainer = diff.getMatch().getRight();
+ }
+ if (expectedContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // The value being moved is out of the scope
+ /*
+ * Note : there should not be a way to end up with a "move" for an out of scope value : a move can
+ * only be detected if the object is matched on both sides, otherwise all we can see is "add" and
+ * "delete"... Is this "fallback" code even reachable? If so, how?
+ */
+ // We need to look it up
+ if (reference.isMany()) {
+ @SuppressWarnings("unchecked")
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ expectedValue = findMatchIn(comparison, targetList, diff.getValue());
+ } else {
+ expectedValue = (EObject)expectedContainer.eGet(reference);
+ }
+ } else {
+ if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ }
+ // We now know the target container, target reference and target value.
+ doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
+ }
+
+ /**
+ * This will do the actual work of moving the element into its reference. All sanity checks were made in
+ * {@link #moveElement(boolean)} and no more verification will be made here.
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param comparison
+ * Comparison holding this Diff.
+ * @param expectedContainer
+ * The container in which we are reorganizing a reference.
+ * @param expectedValue
+ * The value that is to be moved within its reference.
+ * @param rightToLeft
+ * Whether we should move the value in the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void doMove(ReferenceChange diff, Comparison comparison, EObject expectedContainer,
+ EObject expectedValue, boolean rightToLeft) {
+ final EReference reference = diff.getReference();
+ if (reference.isMany()) {
+ // Element to move cannot be part of the LCS... or there would not be a MOVE diff
+ int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
+
+ /*
+ * However, it could still have been located "before" its new index, in which case we need to take
+ * it into account.
+ */
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ final int currentIndex = targetList.indexOf(expectedValue);
+ if (insertionIndex > currentIndex && currentIndex >= 0) {
+ insertionIndex--;
+ }
+
+ if (currentIndex == -1) {
+ // happens for container changes for example.
+ if (!reference.isContainment()) {
+ targetList.remove(expectedValue);
+ }
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ targetList.add(expectedValue);
+ } else {
+ targetList.add(insertionIndex, expectedValue);
+ }
+ } else if (targetList instanceof EList<?>) {
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ ((EList<EObject>)targetList).move(targetList.size() - 1, expectedValue);
+ } else {
+ ((EList<EObject>)targetList).move(insertionIndex, expectedValue);
+ }
+ } else {
+ targetList.remove(expectedValue);
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ targetList.add(expectedValue);
+ } else {
+ targetList.add(insertionIndex, expectedValue);
+ }
+ }
+ } else {
+ expectedContainer.eSet(reference, expectedValue);
+ }
+ }
+
+ /**
+ * This will be called when we need to create an element in the target side.
+ * <p>
+ * All necessary sanity checks have been made to ensure that the current operation is one that should
+ * create an object in its side or add an objet to a reference. In other words, either :
+ * <ul>
+ * <li>We are copying from right to left and
+ * <ul>
+ * <li>we are copying an addition to the right side (we need to create the same object in the left), or</li>
+ * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
+ * </ul>
+ * </li>
+ * <li>We are copying from left to right and
+ * <ul>
+ * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
+ * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us whether we are to add an object on the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void addInTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EObject expectedContainer;
+ if (rightToLeft) {
+ expectedContainer = match.getLeft();
+ } else {
+ expectedContainer = match.getRight();
+ }
+
+ if (expectedContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final Comparison comparison = match.getComparison();
+ final EReference reference = diff.getReference();
+ final EObject expectedValue;
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+ if (valueMatch == null) {
+ // This is an out of scope value.
+ if (diff.getValue().eIsProxy()) {
+ // Copy the proxy
+ expectedValue = EcoreUtil.copy(diff.getValue());
+ } else {
+ // Use the same value.
+ expectedValue = diff.getValue();
+ }
+ } else if (rightToLeft) {
+ if (reference.isContainment()) {
+ expectedValue = createCopy(diff.getValue());
+ valueMatch.setLeft(expectedValue);
+ } else {
+ expectedValue = valueMatch.getLeft();
+ }
+ } else {
+ if (reference.isContainment()) {
+ expectedValue = createCopy(diff.getValue());
+ valueMatch.setRight(expectedValue);
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ }
+
+ // We have the container, reference and value. We need to know the insertion index.
+ if (reference.isMany()) {
+ final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
+
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ addAt(targetList, expectedValue, insertionIndex);
+ } else {
+ expectedContainer.eSet(reference, expectedValue);
+ }
+
+ if (reference.isContainment()) {
+ // Copy XMI ID when applicable.
+ final Resource initialResource = diff.getValue().eResource();
+ final Resource targetResource = expectedValue.eResource();
+ if (initialResource instanceof XMIResource && targetResource instanceof XMIResource) {
+ ((XMIResource)targetResource).setID(expectedValue, ((XMIResource)initialResource).getID(diff
+ .getValue()));
+ }
+ }
+ }
+
+ /**
+ * This will be called when we need to remove an element from the target side.
+ * <p>
+ * All necessary sanity checks have been made to ensure that the current operation is one that should
+ * delete an object. In other words, we are :
+ * <ul>
+ * <li>Copying from right to left and either
+ * <ul>
+ * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
+ * <li>we are copying an addition to the left side (we need to revert the addition).</li>
+ * </ul>
+ * </li>
+ * <li>Copying from left to right and either
+ * <ul>
+ * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
+ * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us whether we are to add an object on the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void removeFromTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EReference reference = diff.getReference();
+ final EObject currentContainer;
+ if (rightToLeft) {
+ currentContainer = match.getLeft();
+ } else {
+ currentContainer = match.getRight();
+ }
+ final Comparison comparison = match.getComparison();
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+
+ if (currentContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // value is out of the scope... we need to look it up
+ if (reference.isMany()) {
+ final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
+ expectedValue = findMatchIn(comparison, targetList, diff.getValue());
+ } else {
+ // the value will not be needed anyway
+ expectedValue = null;
+ }
+ } else if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+
+ // We have the container, reference and value to remove. Expected value can be null when the
+ // deletion was made on both side (i.e. a pseudo delete)
+ if (reference.isContainment() && expectedValue != null) {
+ EcoreUtil.remove(expectedValue);
+ if (rightToLeft && valueMatch != null) {
+ valueMatch.setLeft(null);
+ } else if (valueMatch != null) {
+ valueMatch.setRight(null);
+ }
+ // TODO remove dangling? remove empty Match?
+ } else if (reference.isMany()) {
+ /*
+ * TODO if the same value appears twice, should we try and find the one that has actually been
+ * deleted? Can it happen? For now, remove the first occurence we find.
+ */
+ final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
+ targetList.remove(expectedValue);
+ } else {
+ currentContainer.eUnset(reference);
+ }
+ }
+
+ /**
+ * This will be called by the merge operations in order to reset a reference to its original value, be
+ * that the left or right side.
+ * <p>
+ * Should never be called on multi-valued references.
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us the direction of this merge operation.
+ */
+ protected void resetInTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EReference reference = diff.getReference();
+ final EObject targetContainer;
+ if (rightToLeft) {
+ targetContainer = match.getLeft();
+ } else {
+ targetContainer = match.getRight();
+ }
+
+ final EObject originContainer;
+ if (match.getComparison().isThreeWay()) {
+ originContainer = match.getOrigin();
+ } else if (rightToLeft) {
+ originContainer = match.getRight();
+ } else {
+ originContainer = match.getLeft();
+ }
+
+ if (originContainer == null || !safeEIsSet(targetContainer, reference)
+ || !safeEIsSet(originContainer, reference)) {
+ targetContainer.eUnset(reference);
+ } else {
+ final EObject originalValue = (EObject)originContainer.eGet(reference);
+ final Match valueMatch = match.getComparison().getMatch(originalValue);
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // Value is out of the scope, use it as-is
+ expectedValue = originalValue;
+ } else if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ targetContainer.eSet(reference, expectedValue);
+ }
+ }
+
+ /**
+ * Handles the equivalences of this difference.
+ * <p>
+ * Note that in certain cases, we'll merge our opposite instead of merging this diff. Specifically, we'll
+ * do that for one-to-many eOpposites : we'll merge the 'many' side instead of the 'unique' one. This
+ * allows us not to worry about the order of the references on that 'many' side.
+ * </p>
+ * <p>
+ * This is called before the merge of <code>this</code>. In short, if this returns <code>false</code>, we
+ * won't carry on merging <code>this</code> after returning.
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Direction of the merge.
+ * @param monitor
+ * The monitor to use in order to report progress information.
+ * @return <code>true</code> if the current difference should still be merged after handling its
+ * equivalences, <code>false</code> if it should be considered "already merged".
+ */
+ protected boolean handleEquivalences(ReferenceChange diff, boolean rightToLeft, Monitor monitor) {
+ final EReference reference = diff.getReference();
+ boolean continueMerge = true;
+ for (Diff equivalent : diff.getEquivalence().getDifferences()) {
+ // For 1..*, merge diff on many-valued to preserve ordering
+ if (equivalent instanceof ReferenceChange
+ && reference.getEOpposite() == ((ReferenceChange)equivalent).getReference()
+ && equivalent.getState() == DifferenceState.UNRESOLVED) {
+ // This equivalence is on our eOpposite. Should we merge it instead of 'this'?
+ final boolean mergeEquivalence = !reference.isMany()
+ && ((ReferenceChange)equivalent).getReference().isMany();
+ if (mergeEquivalence) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ }
+ }
+
+ /*
+ * If one of the equivalent differences is implied or implying (depending on the merge direction)
+ * a merged diff, then we have a dependency loop : the "current" difference has already been
+ * merged because of this implication. This will allow us to break out of that loop.
+ */
+ if (rightToLeft) {
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ continueMerge = continueMerge
+ && !containsAny(diff.getRequiredBy(), equivalent.getImplies());
+ } else {
+ continueMerge = continueMerge
+ && !containsAny(diff.getRequires(), equivalent.getImpliedBy());
+ }
+ } else {
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ continueMerge = continueMerge
+ && !containsAny(diff.getRequires(), equivalent.getImpliedBy());
+ } else {
+ continueMerge = continueMerge
+ && !containsAny(diff.getRequiredBy(), equivalent.getImplies());
+ }
+ }
+
+ equivalent.setState(DifferenceState.MERGED);
+ }
+ return continueMerge;
+ }
+
+ /**
+ * Utility method to check that the first sequence contains one of the elements of the second sequence at
+ * least.
+ *
+ * @param sequence1
+ * The first sequence.
+ * @param sequence2
+ * The second sequence.
+ * @return True if the given first sequence contains one of the elements of the second sequence at least.
+ * false otherwise.
+ */
+ private boolean containsAny(List<? extends EObject> sequence1, List<? extends EObject> sequence2) {
+ return Iterables.any(sequence2, Predicates.in(sequence1));
+ }
+
+ /**
+ * Seeks a match of the given {@code element} in the given list, using the equality helper to find it.
+ * This is only used when moving or deleting proxies for now.
+ *
+ * @param comparison
+ * The comparison which Diff we are currently merging.
+ * @param list
+ * The list from which we seek a value.
+ * @param element
+ * The value for which we need a match in {@code list}.
+ * @return The match of {@code element} in {@code list}, {@code null} if none.
+ */
+ protected EObject findMatchIn(Comparison comparison, List<EObject> list, EObject element) {
+ final IEqualityHelper helper = comparison.getEqualityHelper();
+ final Iterator<EObject> it = list.iterator();
+ while (it.hasNext()) {
+ final EObject next = it.next();
+ if (helper.matchingValues(next, element)) {
+ return next;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This will be used by the distinct merge actions in order to find the index at which a value should be
+ * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
+ * more on this.
+ * <p>
+ * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
+ * be considered as "no index" and the value will be inserted at the end of its target list.
+ * </p>
+ *
+ * @param comparison
+ * This will be used in order to retrieve the Match for EObjects when comparing them.
+ * @param diff
+ * The diff which merging will trigger the need for an insertion index in its target list.
+ * @param rightToLeft
+ * {@code true} if the merging will be done into the left list, so that we should consider the
+ * right model as the source and the left as the target.
+ * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
+ * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
+ * target list.
+ * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
+ */
+ protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
+ return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);
+ }
+}

Back to the top