Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Langer2017-11-22 13:39:09 +0000
committerLaurent Goubet2018-12-11 10:34:05 +0000
commit2f639210b1a82189d84f160e87fc3d9a1e0c250b (patch)
treea5242b7a19bb5198640f88b77567a43b071acb63
parent745ca4b14982b775fa250f66acb886f08d08869d (diff)
downloadorg.eclipse.emf.compare-2f639210b1a82189d84f160e87fc3d9a1e0c250b.tar.gz
org.eclipse.emf.compare-2f639210b1a82189d84f160e87fc3d9a1e0c250b.tar.xz
org.eclipse.emf.compare-2f639210b1a82189d84f160e87fc3d9a1e0c250b.zip
[527567] Provide support for a general property merge viewer3.3.5
Adds and registers Property Content Merge Viewer for all input types. Bug: 527567 Change-Id: Ic4654bbfaf6557dafc18b3bc8499711439cd314a Signed-off-by: Philip Langer <planger@eclipsesource.com>
-rw-r--r--plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml9
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF1
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.pngbin0 -> 328 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_categories.pngbin0 -> 192 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/plugin.xml19
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ColumnResizer.java334
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/DiffPropertyItem.java73
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyAccessor.java186
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyCategoryItem.java36
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.java578
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.properties67
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewerCreator.java40
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyDescriptorItem.java423
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyItem.java775
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyListElementItem.java44
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeMergeViewer.java190
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeViewer.java78
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyValuePlaceholderItem.java29
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ResourcePropertyDescriptor.java112
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/RootPropertyItem.java38
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties12
21 files changed, 3040 insertions, 4 deletions
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml b/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml
index 2b27e1555..3480f5a3d 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml
@@ -2,7 +2,7 @@
<?eclipse version="3.2"?>
<!--
- Copyright (c) 2013 Obeo.
+ Copyright (c) 2013, 2017 Obeo and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
@@ -10,6 +10,7 @@
Contributors:
Obeo - initial API and implementation
+ Philip Langer - bug 527567
-->
<plugin>
@@ -56,6 +57,12 @@
id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.TextFallbackCompareViewer"
label="Text Compare">
</viewer>
+ <viewer
+ class="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property.PropertyContentMergeViewerCreator"
+ extensions="diagramcompare_match, diagramcompare_diff"
+ id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property.PropertyContentMergeViewerCreator"
+ label="Properties Compare">
+ </viewer>
</extension>
<extension
point="org.eclipse.emf.compare.ide.modelInclusionTester">
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
index 229e78c94..7aa16ac77 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF
@@ -31,6 +31,7 @@ Export-Package: org.eclipse.emf.compare.ide.ui.dependency,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer;x-internal:=true,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.accessor;x-internal:=true,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label;x-internal:=true,
+ org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.provider;x-internal:=true,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.table;x-internal:=true,
org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.text;x-internal:=true,
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png
new file mode 100644
index 000000000..27c8738a7
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_categories.png b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_categories.png
new file mode 100644
index 000000000..ae6d660ab
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_categories.png
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
index ff628813c..378fa58a1 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.ide.ui/plugin.xml
@@ -2,7 +2,7 @@
<?eclipse version="3.4"?>
<!--
- Copyright (c) 2012, 2016 Obeo and others.
+ Copyright (c) 2012, 2017 Obeo and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
@@ -13,7 +13,7 @@
Stefan Dirix - bugs 456699, 474723
Michael Borkowski - bug 467191
Simon Delisle - bug 495753
- Philip Langer - bug 508855
+ Philip Langer - bug 508855, 527567
Martin fleck - bug 512677
-->
@@ -113,6 +113,21 @@
id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.TextFallbackCompareViewer"
label="%emf.compare.fallback.textcompare">
</viewer>
+ <viewer
+ class="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property.PropertyContentMergeViewerCreator"
+ extensions=
+ "org.eclipse.emf.compare.rcp.ui.fallbackText,
+ org.eclipse.emf.compare.rcp.ui.eTreeDiff,
+ org.eclipse.emf.compare.rcp.ui.eMatch,
+ org.eclipse.emf.compare.rcp.ui.eResourceDiff,
+ org.eclipse.emf.compare.rcp.ui.eListDiff,
+ org.eclipse.emf.compare.rcp.ui.eTextDiff,
+ org.eclipse.emf.compare.rcp.ui.eNoDiff,
+ org.eclipse.emf.compare.rcp.ui.eNoVisibleItem,
+ org.eclipse.emf.compare.rcp.ui.eOnlyPseudoConflicts"
+ id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property.PropertyContentMergeViewerCreator"
+ label="Properties Compare">
+ </viewer>
<contentTypeBinding
contentTypeId="org.eclipse.emf.ecore.xmi"
contentMergeViewerId="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.WaitViewer">
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ColumnResizer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ColumnResizer.java
new file mode 100644
index 000000000..d62d63820
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ColumnResizer.java
@@ -0,0 +1,334 @@
+/**
+ * Copyright (c) 2017 Eclipse contributors and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.TreeEvent;
+import org.eclipse.swt.events.TreeListener;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * <p>
+ * <b>Copied from EMF 2.14's org.eclipse.emf.common.ui.viewer.ColumnResizer.</b>
+ * </p>
+ * This utility class provides {@link #addColumnResizer(Tree)} and {@link #addColumnResizer(Table)} to create
+ * a {@link ColumnResizer.Handler} that will pack all columns to their minimal size and will respond to
+ * {@link ControlListener#controlResized(ControlEvent) control resized} events to update the column sizes
+ * automatically. If needed, the last column will be expanded beyond its minimal packed size to make it large
+ * enough such that all Tree or Table columns exactly fit the {@link Composite#getClientArea() client area}.
+ * When the contents of the {@link Tree} or {@link Table} changes, the Handler can be instructed to
+ * {@link ColumnResizer.Handler#resizeColumns() resize} the columns.
+ */
+@SuppressWarnings({"nls", "boxing" })
+class ColumnResizer {
+
+ private ColumnResizer() {
+ throw new RuntimeException("No instances");
+ }
+
+ /**
+ * A handler created by {@link ColumnResizer#addColumnResizer(Tree)} or
+ * {@link ColumnResizer#addColumnResizer(Table)}. that provides the ability to manually
+ * {@link #resizeColumns() resize} the columns to their minimal packed size when the contents of the
+ * {@link Tree} or {@link Table} changes. If needed, the last column will be expanded beyond its minimal
+ * packed size to make it large enough such that all Tree or Table columns exactly fit the
+ * {@link Composite#getClientArea() client area}. The handler itself will respond to
+ * {@link ControlListener#controlResized(ControlEvent) control resized} events to update the column sizes
+ * automatically.
+ */
+ public static abstract class Handler {
+ private Handler() {
+ }
+
+ /**
+ * Resize all columns to their minimal packed size. This should be called when the contents of the
+ * {@link Tree} or {@link Table} changes.
+ */
+ public abstract void resizeColumns();
+
+ /**
+ * This removes this handler from the {@link Tree} or {@link Table} to which it was added.
+ */
+ public abstract void dispose();
+ }
+
+ /**
+ * Creates a handler for resizing all columns to their minimal packed size.
+ */
+ public static Handler addColumnResizer(Tree tree) {
+ return new TreeColumnResizeHandler(tree);
+ }
+
+ /**
+ * Creates a handler for resizing all columns to their minimal packed size.
+ */
+ public static Handler addColumnResizer(Table table) {
+ return new TableColumnResizeHandler(table);
+ }
+
+ private static class ParentHandler extends ControlAdapter implements DisposeListener, Runnable {
+ private final Composite control;
+
+ private final Composite parent;
+
+ private boolean dispatched;
+
+ public ParentHandler(Composite control) {
+ this.control = control;
+ this.parent = control.getParent();
+ control.addDisposeListener(this);
+ parent.addControlListener(this);
+ }
+
+ public void run() {
+ if (!parent.isDisposed() && dispatched) {
+ dispatched = false;
+ parent.setRedraw(true);
+ }
+ }
+
+ @Override
+ public void controlResized(ControlEvent e) {
+ if (!dispatched) {
+ parent.setRedraw(false);
+ dispatched = true;
+ parent.getDisplay().asyncExec(this);
+ }
+ }
+
+ public void widgetDisposed(DisposeEvent e) {
+ parent.removeControlListener(this);
+ }
+
+ public void dispose() {
+ run();
+ dispatched = false;
+ control.removeDisposeListener(this);
+ parent.removeControlListener(this);
+ }
+ }
+
+ private static abstract class ColumnResizerHandler<T extends Composite, C extends Item> extends Handler implements ControlListener {
+ private final T control;
+
+ private final ParentHandler parentHandler;
+
+ private int clientWidth = -1;
+
+ private List<Integer> columnWidths = Collections.emptyList();
+
+ private boolean resizing;
+
+ public ColumnResizerHandler(T control) {
+ this.control = control;
+ for (C column : getColumns()) {
+ disableResizeable(column);
+ }
+
+ parentHandler = new ParentHandler(control);
+ control.addControlListener(this);
+ }
+
+ public T getControl() {
+ return control;
+ }
+
+ protected abstract boolean isPackable();
+
+ protected abstract List<? extends C> getColumns();
+
+ protected abstract void disableResizeable(C column);
+
+ protected abstract int getWidth(C column);
+
+ protected abstract void setWidth(C column, int width);
+
+ protected abstract void pack(C column);
+
+ protected List<Integer> getColumnWidths() {
+ List<? extends C> columns = getColumns();
+ List<Integer> result = new ArrayList<Integer>(columns.size());
+ for (C column : columns) {
+ result.add(getWidth(column));
+ }
+ return result;
+ }
+
+ public void controlResized(ControlEvent controlEvent) {
+ if (!resizing && isPackable()) {
+ T ctrl = getControl();
+ Rectangle clientArea = ctrl.getClientArea();
+ int clientWdth = clientArea.width - clientArea.x;
+ List<Integer> columnWdths = getColumnWidths();
+
+ boolean inputChanged = controlEvent == null;
+ if (inputChanged || clientWdth != this.clientWidth || this.columnWidths.equals(columnWdths)) {
+ try {
+ resizing = true;
+ ctrl.setRedraw(false);
+
+ List<? extends C> columns = getColumns();
+ for (C column : columns) {
+ pack(column);
+ }
+
+ List<Integer> packedColumnWidths = getColumnWidths();
+ int total = 0;
+ int limit = columns.size() - 1;
+ for (int i = 0; i < limit; ++i) {
+ int width = packedColumnWidths.get(i) + 10;
+ total += width;
+ setWidth(columns.get(i), width);
+ }
+
+ int width = packedColumnWidths.get(limit);
+ if (total + width < clientWdth) {
+ width = clientWdth - total;
+ }
+ setWidth(columns.get(limit), width);
+ } finally {
+ this.clientWidth = clientWdth;
+ this.columnWidths = getColumnWidths();
+ ctrl.setRedraw(true);
+ parentHandler.run();
+ resizing = false;
+ }
+ }
+ }
+ }
+
+ public void controlMoved(ControlEvent e) {
+ }
+
+ @Override
+ public void resizeColumns() {
+ controlResized(null);
+ }
+
+ @Override
+ public void dispose() {
+ parentHandler.dispose();
+ control.removeControlListener(this);
+ }
+ }
+
+ private static class TreeColumnResizeHandler extends ColumnResizerHandler<Tree, TreeColumn> {
+ public TreeColumnResizeHandler(Tree tree) {
+ super(tree);
+
+ class TreeStateListener implements TreeListener, Runnable {
+ private boolean dispatched;
+
+ public void run() {
+ dispatched = false;
+ if (!getControl().isDisposed()) {
+ resizeColumns();
+ }
+ }
+
+ private void dispatch() {
+ if (!dispatched) {
+ dispatched = true;
+ getControl().getDisplay().asyncExec(this);
+ }
+ }
+
+ public void treeCollapsed(TreeEvent e) {
+ dispatch();
+ }
+
+ public void treeExpanded(TreeEvent e) {
+ dispatch();
+ }
+ }
+
+ tree.addTreeListener(new TreeStateListener());
+ }
+
+ @Override
+ protected List<? extends TreeColumn> getColumns() {
+ return Arrays.asList(getControl().getColumns());
+ }
+
+ @Override
+ protected void disableResizeable(TreeColumn column) {
+ column.setResizable(false);
+ }
+
+ @Override
+ protected int getWidth(TreeColumn column) {
+ return column.getWidth();
+ }
+
+ @Override
+ protected void setWidth(TreeColumn column, int width) {
+ column.setWidth(width);
+ }
+
+ @Override
+ protected boolean isPackable() {
+ return getControl().getItemCount() == 0 || !getControl().getItems()[0].isDisposed();
+ }
+
+ @Override
+ protected void pack(TreeColumn column) {
+ column.pack();
+ }
+ }
+
+ private static class TableColumnResizeHandler extends ColumnResizerHandler<Table, TableColumn> {
+ public TableColumnResizeHandler(Table table) {
+ super(table);
+ }
+
+ @Override
+ protected List<? extends TableColumn> getColumns() {
+ return Arrays.asList(getControl().getColumns());
+ }
+
+ @Override
+ protected void disableResizeable(TableColumn column) {
+ column.setResizable(false);
+ }
+
+ @Override
+ protected int getWidth(TableColumn column) {
+ return column.getWidth();
+ }
+
+ @Override
+ protected void setWidth(TableColumn column, int width) {
+ column.setWidth(width);
+ }
+
+ @Override
+ protected boolean isPackable() {
+ return getControl().getItemCount() == 0 || !getControl().getItems()[0].isDisposed();
+ }
+
+ @Override
+ protected void pack(TableColumn column) {
+ column.pack();
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/DiffPropertyItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/DiffPropertyItem.java
new file mode 100644
index 000000000..25c36c2b8
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/DiffPropertyItem.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2018 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+
+abstract class DiffPropertyItem extends PropertyItem {
+
+ private Diff diff;
+
+ private Object value;
+
+ DiffPropertyItem(EMFCompareConfiguration configuration, Diff diff, Object value,
+ MergeViewerSide side) {
+ super(configuration, null, "", side); //$NON-NLS-1$
+ this.diff = diff;
+ this.value = value;
+ }
+
+ DiffPropertyItem(EMFCompareConfiguration configuration, IItemLabelProvider itemLabelProvider,
+ Diff diff, Object value, MergeViewerSide side) {
+ super(configuration, itemLabelProvider.getImage(value), itemLabelProvider.getText(value), side);
+ this.diff = diff;
+ this.value = value;
+ }
+
+ @Override
+ protected boolean isMatchingItem(PropertyItem propertyItem) {
+ return getDiff() == propertyItem.getDiff() || isMatchingValue(getObject(), propertyItem.getObject());
+ }
+
+ @Override
+ protected Object getObject() {
+ return value;
+ }
+
+ @Override
+ public Diff getDiff() {
+ return diff;
+ }
+
+ @Override
+ public String getText(Object object) {
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public Object getImage(Object object) {
+ return null;
+ }
+
+ @Override
+ protected String getPropertyText() {
+ return text;
+ }
+
+ @Override
+ protected Object getPropertyImage() {
+ return image;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyAccessor.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyAccessor.java
new file mode 100644
index 000000000..73602f71e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyAccessor.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.contentmergeviewer.accessor.ICompareAccessor;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.IMergeViewerItem;
+import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
+import org.eclipse.swt.graphics.Image;
+
+/**
+ * A specialized {@link ICompareAccessor} to wrap a {@link PropertyItem}.
+ */
+class PropertyAccessor implements ICompareAccessor {
+ /**
+ * The {@link PropertyItem#createPropertyItem(EMFCompareConfiguration, Object, MergeViewerSide) root}
+ * property item.
+ */
+ final PropertyItem rootPropertyItem;
+
+ /**
+ * The {@link #getInitialItem() initial} item.
+ *
+ * @see #setInitialItem(Diff)
+ * @see #setInitialItem(PropertyItem)
+ */
+ private PropertyItem initialItem;
+
+ /**
+ * Creates an instance for the given configuration, side object, and side.
+ *
+ * @param configuration
+ * the compare configuration
+ * @param object
+ * the side object.
+ * @param side
+ * the side of that object.
+ */
+ public PropertyAccessor(EMFCompareConfiguration configuration, Object object, MergeViewerSide side) {
+ this.rootPropertyItem = PropertyItem.createPropertyItem(configuration, object, side);
+ }
+
+ /**
+ * Returns the root property item of this property accessor.
+ *
+ * @return the root property item of this property accessor.
+ */
+ public PropertyItem getRootPropertyItem() {
+ return rootPropertyItem;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return rootPropertyItem.getText();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Image getImage() {
+ return ExtendedImageRegistry.INSTANCE.getImage(rootPropertyItem.getImage());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getType() {
+ return "property"; //$NON-NLS-1$
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Comparison getComparison() {
+ return rootPropertyItem.getConfiguration().getComparison();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IMergeViewerItem getInitialItem() {
+ return initialItem;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ImmutableList<? extends IMergeViewerItem> getItems() {
+ return ImmutableList.copyOf(rootPropertyItem.getPropertyItems());
+ }
+
+ /**
+ * Set the {@link #getInitialItem() initial} item based on the given diff. If the diff is {@code null}, it
+ * will set the first property item with a {@link PropertyItem#getDiff() diff} or that
+ * {@link PropertyItem#isModified() is modified}.
+ *
+ * @param diff
+ * the diff or {@code null}.
+ */
+ public void setInitialItem(Diff diff) {
+ initialItem = findPropertyItem(diff, rootPropertyItem.getPropertyItems());
+ // If we didn't find one for the given diff...
+ if (initialItem == null && diff != null) {
+ // Look for one with a diff or that is modified.
+ initialItem = findPropertyItem(null, rootPropertyItem.getPropertyItems());
+ }
+ }
+
+ /**
+ * Finds the property item with the given diff in the given list or property items. This method works
+ * recursively on the {@link PropertyItem#getPropertyItems() children} of each property item. If the diff
+ * is {@code null}, it will find the first property item with a {@link PropertyItem#getDiff() diff} or
+ * that {@link PropertyItem#isModified() is modified}.
+ *
+ * @param diff
+ * the diff or {@code null}.
+ * @param propertyItems
+ * the list of {@link PropertyItem property items}.
+ * @return The appropriate {@link #getInitialItem() initial} item.
+ */
+ private PropertyItem findPropertyItem(Diff diff, List<PropertyItem> propertyItems) {
+ for (PropertyItem propertyItem : propertyItems) {
+ if (isPropertyItemForDiff(diff, propertyItem)
+ || (diff == null && isPropertyItemModifiedOrHasDiff(propertyItem))) {
+ return propertyItem;
+ }
+
+ // Force the children to be computed, if necessary.
+ if (propertyItem.hasChildren(propertyItem)) {
+ PropertyItem result = findPropertyItem(diff, propertyItem.getPropertyItems());
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean isPropertyItemForDiff(Diff diff, PropertyItem propertyItem) {
+ return diff != null && propertyItem.getDiff() == diff;
+ }
+
+ private boolean isPropertyItemModifiedOrHasDiff(PropertyItem propertyItem) {
+ return propertyItem.getDiff() != null || propertyItem.isModified();
+ }
+
+ /**
+ * Sets the {@link #getInitialItem() initial} item based on the given property item. It will
+ * {@link PropertyItem#findItem(PropertyItem) find} the item of the appropriate
+ * {@link PropertyItem#getSide() side} in the root property item.
+ *
+ * @param propertyItem
+ * a property item or {@code null}.
+ */
+ public void setInitialItem(PropertyItem propertyItem) {
+ if (propertyItem == null) {
+ initialItem = null;
+ } else {
+ PropertyItem sidePropertyItem = propertyItem.getSide(rootPropertyItem.getSide());
+ if (sidePropertyItem == null) {
+ initialItem = null;
+ } else if (sidePropertyItem.getRootItem() != rootPropertyItem) {
+ initialItem = rootPropertyItem.findItem(sidePropertyItem);
+ } else {
+ initialItem = sidePropertyItem;
+ }
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyCategoryItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyCategoryItem.java
new file mode 100644
index 000000000..146247239
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyCategoryItem.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2018 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+
+class PropertyCategoryItem extends PropertyItem {
+
+ private String categoryName;
+
+ PropertyCategoryItem(EMFCompareConfiguration configuration, String categoryName, MergeViewerSide side) {
+ super(configuration, null, categoryName, side);
+ this.categoryName = categoryName;
+ }
+
+ @Override
+ protected Object getObject() {
+ return categoryName;
+ }
+
+ @Override
+ protected boolean isMatchingItem(PropertyItem propertyItem) {
+ // They match if the other property item is also a category with the same category name.
+ return getClass().isInstance(propertyItem) && categoryName.equals(propertyItem.getText());
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.java
new file mode 100644
index 000000000..d7e2e3007
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.java
@@ -0,0 +1,578 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import static com.google.common.collect.Iterables.getFirst;
+
+import com.google.common.collect.Iterables;
+
+import java.util.ResourceBundle;
+
+import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
+import org.eclipse.compare.contentmergeviewer.IMergeViewerContentProvider;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.Equivalence;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.MatchResource;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree.AbstractTreeContentMergeViewer;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.CompareInputAdapter;
+import org.eclipse.emf.compare.rcp.ui.internal.configuration.IEMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.nodes.DiffNode;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+import org.eclipse.emf.edit.tree.TreeNode;
+import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.viewers.AbstractTreeViewer;
+import org.eclipse.jface.viewers.IContentProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeViewerListener;
+import org.eclipse.jface.viewers.TreeExpansionEvent;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * A specialized {@link ContentMergeViewer} that shows property views for the three side viewers.
+ */
+class PropertyContentMergeViewer extends AbstractTreeContentMergeViewer {
+ /**
+ * The bundle name of the properties file containing all displayed strings needed by the base class.
+ */
+ private static final String BUNDLE_NAME = PropertyContentMergeViewer.class.getName();
+
+ /**
+ * The three values of {@link MergeViewerSide} in the order {@link MergeViewerSide#ANCESTOR},
+ * {@link MergeViewerSide#LEFT}, and {@link MergeViewerSide#RIGHT}.
+ */
+ static final MergeViewerSide[] MERGE_VIEWER_SIDES = new MergeViewerSide[] {MergeViewerSide.ANCESTOR,
+ MergeViewerSide.LEFT, MergeViewerSide.RIGHT };
+
+ /**
+ * The {@link IEMFCompareConfiguration#getBooleanProperty(String) configuration property key} used to
+ * indicate whether {@link IItemPropertyDescriptor#getFilterFlags(Object) advanced properties} should be
+ * shown.
+ *
+ * @see #createToolItems(ToolBarManager)
+ * @see PropertyItem#createPropertyItem(EMFCompareConfiguration, Object, MergeViewerSide)
+ */
+ static String SHOW_ADVANCED_PROPERTIES = "SHOW_ADVANCED_PROPERTIES"; //$NON-NLS-1$
+
+ /**
+ * The {@link IEMFCompareConfiguration#getBooleanProperty(String) configuration property key} used to
+ * indicate whether {@link IItemPropertyDescriptor#getCategory(Object) categories} should be shown.
+ *
+ * @see #createToolItems(ToolBarManager)
+ * @see PropertyItem#createPropertyItem(EMFCompareConfiguration, Object, MergeViewerSide)
+ */
+ static String SHOW_CATEGORIES = "SHOW_CATEGORIES"; //$NON-NLS-1$
+
+ /**
+ * The value used by {@link #updateContent(Object, Object, Object)} as a substitution for the ancestor.
+ *
+ * @see #buildPropertiesFromSides(Object, Object, Object)
+ */
+ private PropertyAccessor ancestorPropertyAccessor;
+
+ /**
+ * The value used by {@link #updateContent(Object, Object, Object)} as a substitution for the left.
+ *
+ * @see #buildPropertiesFromSides(Object, Object, Object)
+ */
+ private PropertyAccessor leftPropertyAccessor;
+
+ /**
+ * The value used by {@link #updateContent(Object, Object, Object)} as a substitution for the right.
+ *
+ * @see #buildPropertiesFromSides(Object, Object, Object)
+ */
+ private PropertyAccessor rightPropertyAccessor;
+
+ /**
+ * A listener attached the tree of {@link #getPropertyMergeViewer(MergeViewerSide) property merge viewer}
+ * that synchronizes the expansion state of the three trees as each individual tree expands and collapses
+ * a tree item.
+ *
+ * @see #createMergeViewer(Composite, MergeViewerSide)
+ */
+ private ITreeViewerListener treeListener = new ITreeViewerListener() {
+ public void treeExpanded(TreeExpansionEvent event) {
+ update(event, true);
+ }
+
+ public void treeCollapsed(TreeExpansionEvent event) {
+ update(event, false);
+ }
+
+ private void update(TreeExpansionEvent event, boolean expanded) {
+ AbstractTreeViewer treeViewer = event.getTreeViewer();
+ PropertyItem element = (PropertyItem)event.getElement();
+ for (MergeViewerSide side : MERGE_VIEWER_SIDES) {
+ PropertyTreeMergeViewer propertyMergeViewer = getPropertyMergeViewer(side);
+ if (propertyMergeViewer.getStructuredViewer() != treeViewer) {
+ PropertyItem sideElement = element.getSide(side);
+ if (sideElement != null) {
+ propertyMergeViewer.setExpandedState(sideElement, expanded);
+ propertyMergeViewer.columnResizer.resizeColumns();
+ }
+ }
+ }
+ redrawCenterControl();
+ }
+ };
+
+ /**
+ * Creates an instance contained by the parent composite for the given configuration.
+ *
+ * @param parent
+ * composite in which to create this instance.
+ * @param configuration
+ * the configuration used by this instance.
+ */
+ protected PropertyContentMergeViewer(Composite parent, EMFCompareConfiguration configuration) {
+ super(SWT.NONE, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
+ buildControl(parent);
+ }
+
+ /**
+ * Returns the property merge viewer for the given side.
+ *
+ * @param side
+ * the side.
+ * @return the property merge viewer for the given side.
+ */
+ protected PropertyTreeMergeViewer getPropertyMergeViewer(MergeViewerSide side) {
+ switch (side) {
+ case ANCESTOR:
+ return (PropertyTreeMergeViewer)getAncestorMergeViewer();
+ case LEFT:
+ return (PropertyTreeMergeViewer)getLeftMergeViewer();
+ case RIGHT:
+ default:
+ return (PropertyTreeMergeViewer)getRightMergeViewer();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation is specialized to set its own specialized content provider during initialization.
+ * The initialization first happens when the base class' constructor is invoked.
+ * </p>
+ */
+ @Override
+ public void setContentProvider(IContentProvider contentProvider) {
+ // If this is the first time this is called, i.e., by the base class constructor...
+ if (getContentProvider() == null) {
+ // Wrap the default content provider with our own delegating implementation.
+ final IMergeViewerContentProvider mergeViewerContentProvider = (IMergeViewerContentProvider)contentProvider;
+ super.setContentProvider(new PropertyContentProvider(mergeViewerContentProvider));
+ } else {
+ super.setContentProvider(contentProvider);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation creates a {@link PropertyContentMergeViewer}.
+ * </p>
+ */
+ @Override
+ protected IMergeViewer createMergeViewer(Composite parent, final MergeViewerSide side) {
+ PropertyTreeMergeViewer propertyMergeViewer = new PropertyTreeMergeViewer(parent, side, this,
+ getCompareConfiguration());
+ hookListeners(propertyMergeViewer);
+ propertyMergeViewer.getStructuredViewer().addTreeListener(treeListener);
+ return propertyMergeViewer;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation creates tool items for showing/hiding advanced properties and for showing/hiding
+ * categories.
+ * </p>
+ *
+ * @see #SHOW_ADVANCED_PROPERTIES
+ * @see #SHOW_CATEGORIES
+ */
+ @Override
+ protected void createToolItems(ToolBarManager toolBarManager) {
+ super.createToolItems(toolBarManager);
+
+ Action showCategories = new ToggleAction(SHOW_CATEGORIES, true,
+ "icons/full/toolb16/show_categories.png", "PropertyContentMergeViewer.hideCategories.tooltip", //$NON-NLS-1$ //$NON-NLS-2$
+ "PropertyContentMergeViewer.showCategories.tooltip"); //$NON-NLS-1$
+ toolBarManager.add(new ActionContributionItem(showCategories));
+
+ Action showAdvancedProperties = new ToggleAction(SHOW_ADVANCED_PROPERTIES, false,
+ "icons/full/toolb16/show_advanced_properties.png", //$NON-NLS-1$
+ "PropertyContentMergeViewer.hideAdvancedProperties.tooltip", //$NON-NLS-1$
+ "PropertyContentMergeViewer.showAdvancedProperties.tooltip"); //$NON-NLS-1$
+ toolBarManager.add(new ActionContributionItem(showAdvancedProperties));
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation is specialized to {@link #buildProperties(Object) build the properties} for each of
+ * the three sides of the input.
+ * </p>
+ */
+ @Override
+ public void setInput(Object input) {
+ buildProperties(input);
+ super.setInput(input);
+ }
+
+ /**
+ * Builds the properties corresponding to the input.
+ *
+ * @param input
+ * the {@link #setInput(Object) input}.
+ */
+ private void buildProperties(Object input) {
+ if (input instanceof CompareInputAdapter) {
+ CompareInputAdapter compareInputAdapter = (CompareInputAdapter)input;
+ Notifier target = compareInputAdapter.getTarget();
+ if (target instanceof TreeNode) {
+ TreeNode treeNode = (TreeNode)target;
+ buildPropertiesFromTreeNode(treeNode, true);
+ return;
+ }
+ }
+ buildPropertiesFromSides(null, null, null);
+ }
+
+ /**
+ * Builds the properties corresponding to the tree node of the input. This is only called if the
+ * {@link #setInput(Object) input} is a {@link CompareInputAdapter} whose
+ * {@link CompareInputAdapter#getTarget() target} is {@link TreeNode}.
+ *
+ * @param treeNode
+ * the tree node.
+ * @param visitParent
+ * whether to visit the parent while visiting this tree node.
+ */
+ @SuppressWarnings("restriction")
+ private void buildPropertiesFromTreeNode(TreeNode treeNode, boolean visitParent) {
+ EObject data = treeNode.getData();
+
+ if (treeNode instanceof DiffNode) {
+ // If there is a refined diff, build the properties using that, but of course don't visit the
+ // parent while doing so, or we'd end up with a stack overflow.
+ TreeNode refinedDiff = getFirst(((DiffNode)treeNode).getRefinedDiffs(), null);
+ if (refinedDiff != null) {
+ // Then ensures that, for example, when a shape is moved, we see the properties for the object
+ // on which the coordinates are changing.
+ buildPropertiesFromTreeNode(refinedDiff, false);
+ setInitialItemsForProperties(data);
+ return;
+ }
+
+ if (visitParent) {
+ TreeNode parent = treeNode.getParent();
+ if (parent != null) {
+ buildPropertiesFromTreeNode(parent, true);
+ setInitialItemsForProperties(data);
+ return;
+ }
+ }
+ }
+
+ buildPropertiesFromEObject(data);
+ setInitialItemsForProperties(data);
+ }
+
+ /**
+ * Builds the properties corresponding to a tree node's {@link TreeNode#getData() data}.
+ *
+ * @param eObject
+ * the data object.
+ * @see #buildPropertiesFromSides(Object, Object, Object)
+ */
+ private void buildPropertiesFromEObject(EObject eObject) {
+ if (eObject instanceof Match) {
+ // If it's a match, get the sides from the match.
+ Match match = (Match)eObject;
+ EObject effectiveAncestor = match.getOrigin();
+ EObject effectiveLeft = match.getLeft();
+ EObject effectiveRight = match.getRight();
+
+ // If they're all null, and there is a containing match, populate using that instead.
+ if (effectiveAncestor == null && effectiveLeft == null && effectiveRight == null) {
+ EObject eContainer = match.eContainer();
+ if (eContainer instanceof Match) {
+ buildPropertiesFromEObject(eContainer);
+ }
+ } else {
+ // Otherwise build using the tree sides.
+ buildPropertiesFromSides(effectiveAncestor, effectiveLeft, effectiveRight);
+ }
+ } else if (eObject instanceof Diff) {
+ // If it's a diff, build using the containing match.
+ Diff diff = (Diff)eObject;
+ buildPropertiesFromEObject(diff.getMatch());
+ setInitialItemsForProperties(diff);
+ } else if (eObject instanceof MatchResource) {
+ // If it's a match resource, build directly from the three side resources.
+ MatchResource matchResource = (MatchResource)eObject;
+ buildPropertiesFromSides(matchResource.getOrigin(), matchResource.getLeft(),
+ matchResource.getRight());
+ } else if (eObject instanceof Equivalence) {
+ // If it's an equivalence, use the first diff.
+ Equivalence equivalence = (Equivalence)eObject;
+ EList<Diff> differences = equivalence.getDifferences();
+ Diff first = Iterables.getFirst(differences, null);
+ buildPropertiesFromEObject(first);
+ } else if (eObject instanceof Conflict) {
+ // If it's a conflict, use the first diff.
+ Conflict conflict = (Conflict)eObject;
+ EList<Diff> differences = conflict.getDifferences();
+ Diff first = Iterables.getFirst(differences, null);
+ buildPropertiesFromEObject(first);
+ }
+ }
+
+ /**
+ * Builds the {@link #ancestorPropertyAccessor}, {@link #leftPropertyAccessor}, and
+ * {@link #rightPropertyAccessor} from the given sides.
+ *
+ * @param origin
+ * the origin.
+ * @param left
+ * the left.
+ * @param right
+ * the right.
+ */
+ private void buildPropertiesFromSides(Object origin, Object left, Object right) {
+ EMFCompareConfiguration configuration = getCompareConfiguration();
+ ancestorPropertyAccessor = new PropertyAccessor(configuration, origin, MergeViewerSide.ANCESTOR);
+ leftPropertyAccessor = new PropertyAccessor(configuration, left,
+ getEffectiveSide(MergeViewerSide.LEFT));
+ rightPropertyAccessor = new PropertyAccessor(configuration, right,
+ getEffectiveSide(MergeViewerSide.RIGHT));
+
+ ancestorPropertyAccessor.rootPropertyItem.reconcile(leftPropertyAccessor.rootPropertyItem,
+ rightPropertyAccessor.rootPropertyItem);
+ }
+
+ /**
+ * Updates the {@link PropertyAccessor#setInitialItem(Diff) initial item} based on the given object. If
+ * the object is a {@link Diff} that is used, otherwise {@code null} is used.
+ *
+ * @param eObject
+ * the object.
+ */
+ private void setInitialItemsForProperties(EObject eObject) {
+ Diff diff = null;
+ if (eObject instanceof Diff) {
+ diff = (Diff)eObject;
+ }
+ ancestorPropertyAccessor.setInitialItem(diff);
+ leftPropertyAccessor.setInitialItem(diff);
+ rightPropertyAccessor.setInitialItem(diff);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation {@link #buildProperties(Object) re-builds the properties}, and attempts to preserve
+ * the selection before calling {@code super.refresh()}.
+ * </p>
+ */
+ @Override
+ public void refresh() {
+ buildProperties(getInput());
+ for (MergeViewerSide side : MERGE_VIEWER_SIDES) {
+ PropertyTreeMergeViewer propertyMergeViewer = getPropertyMergeViewer(side);
+ ISelection selection = propertyMergeViewer.getSelection();
+ PropertyItem selectedItem = null;
+ if (!selection.isEmpty()) {
+ selectedItem = (PropertyItem)((IStructuredSelection)selection).getFirstElement();
+ }
+ switch (side) {
+ case ANCESTOR:
+ ancestorPropertyAccessor.setInitialItem(selectedItem);
+ break;
+ case LEFT:
+ leftPropertyAccessor.setInitialItem(selectedItem);
+ break;
+ case RIGHT:
+ rightPropertyAccessor.setInitialItem(selectedItem);
+ break;
+ }
+ }
+ super.refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation substitutes {@link #ancestorPropertyAccessor}, {@link #leftPropertyAccessor}, and
+ * {@link #rightPropertyAccessor} in place of {@code ancestor}, {@code left}, and {@code right}. It also
+ * switches the left and right if {@link IEMFCompareConfiguration#isMirrored() mirrored}.
+ * </p>
+ */
+ @Override
+ protected void updateContent(Object ancestor, Object left, Object right) {
+ if (getCompareConfiguration().isMirrored()) {
+ super.updateContent(ancestorPropertyAccessor, rightPropertyAccessor, leftPropertyAccessor);
+ } else {
+ super.updateContent(ancestorPropertyAccessor, leftPropertyAccessor, rightPropertyAccessor);
+ }
+ }
+
+ private final class PropertyContentProvider implements IMergeViewerContentProvider {
+ private final IMergeViewerContentProvider mergeViewerContentProvider;
+
+ private PropertyContentProvider(IMergeViewerContentProvider mergeViewerContentProvider) {
+ this.mergeViewerContentProvider = mergeViewerContentProvider;
+ }
+
+ public void dispose() {
+ mergeViewerContentProvider.dispose();
+ }
+
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ mergeViewerContentProvider.inputChanged(viewer, oldInput, newInput);
+ }
+
+ private String getSideLabel(String baseLabel, MergeViewerSide side) {
+ // Augment the base label with the label of the root object
+ PropertyTreeMergeViewer propertyMergeViewer = getPropertyMergeViewer(side);
+ if (propertyMergeViewer != null && propertyMergeViewer.getRootPropertyItem() != null) {
+ String text = propertyMergeViewer.getRootPropertyItem().getText();
+ if (!text.isEmpty()) {
+ return text + " - " + baseLabel; //$NON-NLS-1$
+ }
+ }
+ return baseLabel;
+ }
+
+ private Image getSideImage(Image baseImage, MergeViewerSide side) {
+ // Substitute the base image with the image of the root object
+ PropertyTreeMergeViewer propertyMergeViewer = getPropertyMergeViewer(side);
+ if (propertyMergeViewer != null && propertyMergeViewer.getRootPropertyItem() != null) {
+ Object image = propertyMergeViewer.getRootPropertyItem().getImage();
+ if (image != null) {
+ return ExtendedImageRegistry.INSTANCE.getImage(image);
+ }
+ }
+ return baseImage;
+ }
+
+ public String getAncestorLabel(Object input) {
+ return getSideLabel(mergeViewerContentProvider.getAncestorLabel(input), MergeViewerSide.ANCESTOR);
+ }
+
+ public Image getAncestorImage(Object input) {
+ return getSideImage(mergeViewerContentProvider.getAncestorImage(input), MergeViewerSide.ANCESTOR);
+ }
+
+ public Object getAncestorContent(Object input) {
+ return mergeViewerContentProvider.getAncestorContent(input);
+ }
+
+ public boolean showAncestor(Object input) {
+ return mergeViewerContentProvider.showAncestor(input);
+ }
+
+ public String getLeftLabel(Object input) {
+ return getSideLabel(mergeViewerContentProvider.getLeftLabel(input), MergeViewerSide.LEFT);
+ }
+
+ public Image getLeftImage(Object input) {
+ return getSideImage(mergeViewerContentProvider.getLeftImage(input), MergeViewerSide.LEFT);
+ }
+
+ public Object getLeftContent(Object input) {
+ return mergeViewerContentProvider.getLeftContent(input);
+ }
+
+ public boolean isLeftEditable(Object input) {
+ return mergeViewerContentProvider.isLeftEditable(input);
+ }
+
+ public void saveLeftContent(Object input, byte[] bytes) {
+ mergeViewerContentProvider.saveLeftContent(input, bytes);
+ }
+
+ public String getRightLabel(Object input) {
+ return getSideLabel(mergeViewerContentProvider.getRightLabel(input), MergeViewerSide.RIGHT);
+ }
+
+ public Image getRightImage(Object input) {
+ return getSideImage(mergeViewerContentProvider.getRightImage(input), MergeViewerSide.RIGHT);
+ }
+
+ public Object getRightContent(Object input) {
+ return mergeViewerContentProvider.getRightContent(input);
+ }
+
+ public boolean isRightEditable(Object input) {
+ return mergeViewerContentProvider.isRightEditable(input);
+ }
+
+ public void saveRightContent(Object input, byte[] bytes) {
+ mergeViewerContentProvider.saveRightContent(input, bytes);
+ }
+ }
+
+ private final class ToggleAction extends Action {
+ private final String propertyKey;
+
+ private final String checkedTooltip;
+
+ private final String uncheckedTooltip;
+
+ public ToggleAction(String propertyKey, boolean propertyDefaultValue, String imageLocation,
+ String checkedTooltipKey, String uncheckedTooltipKey) {
+ this.propertyKey = propertyKey;
+ checkedTooltip = EMFCompareIDEUIMessages.getString(checkedTooltipKey);
+ uncheckedTooltip = EMFCompareIDEUIMessages.getString(uncheckedTooltipKey);
+ setChecked(getCompareConfiguration().getBooleanProperty(propertyKey, propertyDefaultValue));
+ setImageDescriptor(EMFCompareIDEUIPlugin.getImageDescriptor(imageLocation));
+ updateToolTipText();
+ }
+
+ @Override
+ public void run() {
+ getCompareConfiguration().setProperty(propertyKey, Boolean.valueOf(isChecked()));
+ refresh();
+ updateToolTipText();
+ }
+
+ private void updateToolTipText() {
+ final String toolTipText;
+ if (isChecked()) {
+ toolTipText = checkedTooltip;
+ } else {
+ toolTipText = uncheckedTooltip;
+ }
+ setToolTipText(toolTipText);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.properties b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.properties
new file mode 100644
index 000000000..c8517af96
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.properties
@@ -0,0 +1,67 @@
+###############################################################################
+# 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
+###############################################################################
+
+title= Properties Compare
+
+saveDialog.title= Save Resource
+saveDialog.message= Resource has been modified. Save changes?
+
+compareProgressTask.title= Computing Differences...
+
+tooComplexError.title= Error
+tooComplexError.message= Too many differences. Turn on the 'Ignore White Space' option or do a structure compare first.
+
+UnkownResource = Unknown Resource
+
+#####################################################
+# Toolbar actions
+#####################################################
+#
+# Default labels, tooltips and images for toolbar actions are inherited from the Eclipse Compare TextMergeViewer using the
+# EMFCompareContentMergeViewerResourceBundle class in EMFCompareContentMergeViewer. The default values may be overridden here.
+#
+
+#####################################################
+# Context menu actions
+#####################################################
+
+action.undo.label=&Undo
+action.undo.tooltip=Undo Last Operation
+
+action.redo.label=&Redo
+action.redo.tooltip=Redo Last Operation
+
+action.cut.label=Cu&t
+action.cut.tooltip=Cut Text Selection to Clipboard
+
+action.copy.label=&Copy
+action.copy.tooltip=Copy Text Selection to Clipboard
+
+action.paste.label=&Paste
+action.paste.tooltip=Replace Text Selection with Clipboard Contents
+
+action.delete.label=&Delete
+action.delete.tooltip=Delete Current Text Selection
+
+action.find.label=&Find...
+action.find.tooltip=Find Occurrence
+
+action.selectAll.label=Select &All
+action.selectAll.tooltip=Select All Changes
+
+Editor.FindReplace.label=&Find/Replace...
+Editor.FindReplace.tooltip=Find/Replace
+Editor.FindReplace.image=
+Editor.FindReplace.description=Find/Replace
+
+action.IgnoreWhiteSpace.label=&Ignore White Space
+action.IgnoreWhiteSpace.tooltip=Ignore White Space Where Applicable
+
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewerCreator.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewerCreator.java
new file mode 100644
index 000000000..aa396c893
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewerCreator.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.IViewerCreator;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeNodeCompareInput;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeNodeCompareInputLabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * An {@link IViewerCreator} that creates a {@link PropertyContentMergeViewer}.
+ */
+public class PropertyContentMergeViewerCreator implements IViewerCreator {
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation creates a {@link PropertyContentMergeViewer}.
+ * </p>
+ *
+ * @see org.eclipse.compare.IViewerCreator#createViewer(Composite, CompareConfiguration)
+ */
+ public Viewer createViewer(Composite parent, CompareConfiguration configuration) {
+ final EMFCompareConfiguration emfCompareConfiguration = new EMFCompareConfiguration(configuration);
+ emfCompareConfiguration.setLabelProvider(TreeNodeCompareInput.class,
+ new TreeNodeCompareInputLabelProvider());
+ return new PropertyContentMergeViewer(parent, emfCompareConfiguration);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyDescriptorItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyDescriptorItem.java
new file mode 100644
index 000000000..fe913a1d9
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyDescriptorItem.java
@@ -0,0 +1,423 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.size;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.internal.utils.DiffUtil;
+import org.eclipse.emf.compare.rcp.ui.internal.util.MergeViewerUtil;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.IMergeViewerItem;
+import org.eclipse.emf.compare.utils.IEqualityHelper;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+import org.eclipse.emf.edit.provider.IItemPropertySource;
+import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.TreeItem;
+
+class PropertyDescriptorItem extends PropertyItem {
+
+ final private Object object;
+
+ private Diff propertyDiff;
+
+ final private Object editableValue;
+
+ private List<?> listValue;
+
+ final private Object propertyValue;
+
+ final Multimap<Object, Diff> diffs;
+
+ final private IItemPropertyDescriptor itemPropertyDescriptor;
+
+ private boolean hasCheckedForChildren;
+
+ private boolean needsReconcile;
+
+ public PropertyDescriptorItem(EMFCompareConfiguration configuration, final Object object,
+ final Multimap<Object, Diff> diffs, IItemPropertyDescriptor itemPropertyDescriptor,
+ final MergeViewerSide side) {
+ super(configuration, null, itemPropertyDescriptor.getDisplayName(object), side);
+
+ this.itemPropertyDescriptor = itemPropertyDescriptor;
+ this.object = object;
+ this.diffs = diffs;
+
+ propertyValue = itemPropertyDescriptor.getPropertyValue(object);
+ if (propertyValue instanceof IItemPropertySource) {
+ IItemPropertySource itemPropertySource = (IItemPropertySource)propertyValue;
+ editableValue = itemPropertySource.getEditableValue(object);
+ } else {
+ editableValue = propertyValue;
+ }
+
+ // List handling is used even for feature's that aren't multi-valued but whose value is a
+ // list.
+ if (editableValue instanceof List<?>) {
+ listValue = (List<?>)editableValue;
+ } else {
+ listValue = null;
+ }
+
+ // Determine the side of the object from its match.
+ MergeViewerSide matchSide = getSide(getMatch(configuration, object));
+
+ // If it's not a list, or it's a value for a feature that isn't multi-valued...
+ Object feature = itemPropertyDescriptor.getFeature(object);
+ if (listValue == null) {
+ // Consume the diff(s) for the overall property.
+ propertyDiff = getDiff(editableValue, matchSide);
+ } else if (feature instanceof EStructuralFeature && !((EStructuralFeature)feature).isMany()) {
+ // Consume the diff(s) for the overall property rather than using any diffs for the
+ // value items in the list.
+ if (diffs != null && !diffs.keySet().isEmpty()) {
+ propertyDiff = getDiff(diffs.keySet().iterator().next(), matchSide);
+ }
+ }
+
+ initializeListOfValueChildren();
+ }
+
+ private void initializeListOfValueChildren() {
+ if (getListValue() == null) {
+ return;
+ }
+
+ EList<PropertyItem> propertyItems = getPropertyItems();
+ for (Object value : getListValue()) {
+ Diff diff = getDiff(value, getSide());
+ propertyItems.add(new PropertyListElementItem(getConfiguration(), getLabelProvider(), diff, value,
+ getSide()));
+ }
+
+ if (haveDiffs()) {
+ createPlaceholders(propertyItems);
+ }
+ }
+
+ private void createPlaceholders(EList<PropertyItem> propertyItems) {
+ // We only create placeholder for the left and right sides...
+ if (getSide() == MergeViewerSide.LEFT || getSide() == MergeViewerSide.RIGHT) {
+ // We only want to use each diff once.
+ Set<Diff> usedDiffs = Sets.newHashSet();
+ Comparison comparison = getConfiguration().getComparison();
+
+ // Iterate over the entries...
+ for (Map.Entry<Object, Diff> entry : diffs.entries()) {
+ // Fetch the value and diff, checking if we haven't already used it...
+ Object value = entry.getKey();
+ Diff diff = entry.getValue();
+ if (usedDiffs.add(diff)) {
+ // Determine the affected feature (generally that should be the
+ // feature from above)...
+ EStructuralFeature affectedFeature = MergeViewerUtil.getAffectedFeature(diff);
+ // If there is an affected feature that's multi-valued.
+ if (affectedFeature != null && affectedFeature.isMany()) {
+ // Determine the insertion index...
+ int insertionIndex = DiffUtil.findInsertionIndex(comparison, diff,
+ getSide() == MergeViewerSide.LEFT);
+
+ // Correct the index based on how many placeholders are already
+ // earlier in the list of children.
+ List<PropertyItem> subList = propertyItems.subList(0, insertionIndex);
+ final int count = size(filter(subList, IMergeViewerItem.IS_INSERTION_POINT));
+ int index = Math.min(insertionIndex + count, propertyItems.size());
+
+ // Create the placeholder and insert it at the appropriate
+ // place in the list.
+ PropertyValuePlaceholderItem placeholderItem = new PropertyValuePlaceholderItem(
+ getConfiguration(), diff, value, getSide());
+ propertyItems.add(index, placeholderItem);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean haveDiffs() {
+ return diffs != null && !diffs.isEmpty();
+ }
+
+ private IItemLabelProvider getLabelProvider() {
+ return itemPropertyDescriptor.getLabelProvider(object);
+ }
+
+ /**
+ * Returns the side of the object within the match.
+ *
+ * @param match
+ * the match used to determine the side.
+ * @return the side of the object within the match.
+ */
+ private MergeViewerSide getSide(Match match) {
+ if (match == null) {
+ return null;
+ } else if (match.getOrigin() == object) {
+ return MergeViewerSide.ANCESTOR;
+ } else if (match.getLeft() == object) {
+ return MergeViewerSide.LEFT;
+ } else if (match.getRight() == object) {
+ return MergeViewerSide.RIGHT;
+ } else {
+ return null;
+ }
+ }
+
+ private Diff getDiff(Object value, MergeViewerSide preferredSide) {
+ if (!haveDiffs() || diffs.get(value) == null || diffs.get(value).isEmpty()) {
+ return null;
+ }
+
+ Diff result = null;
+ Collection<Diff> diffCandidates = diffs.get(value);
+ if (preferredSide != null) {
+ Iterator<Diff> diffIterator = diffCandidates.iterator();
+ while (result == null && diffIterator.hasNext()) {
+ Diff candidate = diffIterator.next();
+ if (isDiffOnSide(candidate, preferredSide)) {
+ result = candidate;
+ }
+ }
+ }
+ if (result == null) {
+ result = diffCandidates.iterator().next();
+ }
+
+ // Clear all the entries that use the resulting diff.
+ diffs.values().removeAll(Collections.singleton(result));
+ return result;
+ }
+
+ private boolean isDiffOnSide(Diff diff, MergeViewerSide side) {
+ return (MergeViewerSide.LEFT == side && diff.getSource() == DifferenceSource.LEFT)
+ || (MergeViewerSide.RIGHT == side && diff.getSource() == DifferenceSource.RIGHT);
+ }
+
+ @Override
+ protected Object getObject() {
+ return propertyValue;
+ }
+
+ @Override
+ protected boolean isMatchingItem(PropertyItem propertyItem) {
+ return propertyItem instanceof PropertyDescriptorItem
+ && itemPropertyDescriptor.getDisplayName(object).equals(propertyItem.getText());
+ }
+
+ @Override
+ protected boolean isModified() {
+ if (haveDiffs()) {
+ return true;
+ }
+
+ // Even if it's not directly known to be modified, if any of the other side values are
+ // different, from this one, still consider it to be modified.
+ boolean isList = isList();
+ boolean result = false;
+ for (int i = 0; i < PropertyContentMergeViewer.MERGE_VIEWER_SIDES.length && !result; i++) {
+ MergeViewerSide otherSide = PropertyContentMergeViewer.MERGE_VIEWER_SIDES[i];
+ if (otherSide != getSide()) {
+ PropertyDescriptorItem sidePropertyItem = getSide(otherSide);
+ if (sidePropertyItem != null) {
+ if (isList) {
+ result = !equivalentLists(getListValue(), sidePropertyItem.getListValue());
+ } else if (!isList) {
+ result = !Objects.equals(getPropertyText(), sidePropertyItem.getPropertyText());
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private boolean equivalentLists(List<?> list, List<?> otherList) {
+ if (list == null) {
+ return otherList != null;
+ } else if (otherList == null) {
+ return false;
+ } else if (list.size() != otherList.size()) {
+ return false;
+ } else {
+ IEqualityHelper equalityHelper = getConfiguration().getComparison().getEqualityHelper();
+ for (int i = 0, size = list.size(); i < size; ++i) {
+ if (!equalityHelper.matchingValues(list.get(i), otherList.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ protected List<?> getListValue() {
+ return listValue;
+ }
+
+ protected Object getEditableValue() {
+ return editableValue;
+ }
+
+ protected boolean isList() {
+ // Note that even single-valued features are treated as lists if the single value is a
+ // list. This logic checks if all the side values are lists...
+ boolean allNull = true;
+ boolean anyNonList = false;
+ for (MergeViewerSide otherSide : PropertyContentMergeViewer.MERGE_VIEWER_SIDES) {
+ PropertyDescriptorItem sidePropertyItem = getSide(otherSide);
+ if (sidePropertyItem != null) {
+ if (sidePropertyItem.getEditableValue() != null) {
+ allNull = false;
+ if (sidePropertyItem.getListValue() == null) {
+ anyNonList = true;
+ }
+ }
+ }
+ }
+ // If none are not a list and they're not all null, then it's a list-type value.
+ return !anyNonList && !allNull;
+ }
+
+ @Override
+ public PropertyDescriptorItem getSide(MergeViewerSide anySide) {
+ return (PropertyDescriptorItem)super.getSide(anySide);
+ }
+
+ @Override
+ protected Object getPropertyImage() {
+ return getLabelProvider().getImage(editableValue);
+ }
+
+ @Override
+ protected String getPropertyText() {
+ if (isList()) {
+ // If it's list type property descriptor, compute the label from the size of the list.
+ if (getListValue() == null || getListValue().isEmpty()) {
+ return ""; //$NON-NLS-1$
+ } else {
+ return "..." + listValue.size(); //$NON-NLS-1$
+ }
+ } else {
+ return getLabelProvider().getText(editableValue);
+ }
+ }
+
+ @Override
+ public void update(TreeItem treeItem, boolean expanded) {
+ // This is called when the item property descriptor is expanded and collapsed.
+ // For lists it's designed to hide the property image and property text while the list is
+ // expanded, showing it again when it's collapsed.
+ if (getListValue() != null && !getListValue().isEmpty()) {
+ if (expanded) {
+ treeItem.setImage(1, (Image)null);
+ treeItem.setText(1, ""); //$NON-NLS-1$
+ } else {
+ treeItem.setImage(1, ExtendedImageRegistry.getInstance().getImage(getPropertyImage()));
+ treeItem.setText(1, getPropertyText());
+ }
+ }
+ }
+
+ /**
+ * Returns whether this property item has children.
+ * <p>
+ * This implementation is specialized to handle the case that the {@link #getObject() property value} is
+ * an {@link IItemPropertySource}.
+ * <p>
+ *
+ * @param thisObject
+ * this object is generally ignored.
+ * @return whether this property item has children.
+ */
+ @Override
+ public boolean hasChildren(Object thisObject) {
+ boolean hasChildren = super.hasChildren(thisObject);
+ // If there aren't currently any children and we haven't checked if the property value is
+ // a property source...
+ if (!hasChildren && !hasCheckedForChildren) {
+ // Now we've check, or rather are about to check.
+ hasCheckedForChildren = true;
+
+ // If the property value is a property source.
+ if (propertyValue instanceof IItemPropertySource) {
+ // Create the property item for it.
+ PropertyItem propertyItem = createPropertyItem(getConfiguration(), propertyValue, getSide());
+
+ // If this property item has children...
+ EList<PropertyItem> propertyChildren = propertyItem.getPropertyItems();
+ if (!propertyChildren.isEmpty()) {
+ // We'll need to reconcile this property item against the other sides.
+ needsReconcile = true;
+
+ // Transfer the children.
+ children.addAll(propertyChildren);
+
+ // In this case of course we have children.
+ hasChildren = true;
+ }
+ }
+
+ // Do the same check on all three sides.
+ if (ancestor != null && ancestor != this) {
+ ancestor.hasChildren(ancestor);
+ }
+ if (left != null && left != this) {
+ left.hasChildren(left);
+ }
+ if (right != null && right != this) {
+ right.hasChildren(right);
+ }
+
+ // Reconcile all three sides.
+ PropertyDescriptorItem ancestorPropertyDescriptorItem = (PropertyDescriptorItem)ancestor;
+ if (ancestorPropertyDescriptorItem != null && ancestorPropertyDescriptorItem.needsReconcile) {
+ ancestorPropertyDescriptorItem.reconcile();
+ ancestorPropertyDescriptorItem.needsReconcile = false;
+ }
+ PropertyDescriptorItem leftPropertyDescriptorItem = (PropertyDescriptorItem)left;
+ if (leftPropertyDescriptorItem != null && leftPropertyDescriptorItem.needsReconcile) {
+ leftPropertyDescriptorItem.reconcile();
+ leftPropertyDescriptorItem.needsReconcile = false;
+ }
+ PropertyDescriptorItem rightPropertyDescriptorItem = (PropertyDescriptorItem)right;
+ if (rightPropertyDescriptorItem != null && rightPropertyDescriptorItem.needsReconcile) {
+ rightPropertyDescriptorItem.reconcile();
+ rightPropertyDescriptorItem.needsReconcile = false;
+ }
+ }
+ return hasChildren;
+ }
+
+ @Override
+ public Diff getDiff() {
+ return propertyDiff;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyItem.java
new file mode 100644
index 000000000..49f99b553
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyItem.java
@@ -0,0 +1,775 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.internal.util.MergeViewerUtil;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.IMergeViewerItem;
+import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider;
+import org.eclipse.emf.compare.utils.IEqualityHelper;
+import org.eclipse.emf.compare.utils.MatchUtil;
+import org.eclipse.emf.compare.utils.ReferenceUtil;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
+import org.eclipse.emf.edit.provider.IItemFontProvider;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+import org.eclipse.emf.edit.provider.IItemPropertySource;
+import org.eclipse.emf.edit.provider.ITableItemFontProvider;
+import org.eclipse.emf.edit.provider.ITableItemLabelProvider;
+import org.eclipse.emf.edit.provider.ItemProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * An {@link ItemProvider} used to represent each item in the property tree. It implements
+ * {@link IMergeViewerItem} to integrate with the underlying base framework. It is generally intended to work
+ * in a {@link TreeViewer tree}, with two columns, a 'Property' column and a 'Value' column, supporting an
+ * {@link #getColumnImage(Object, int) image} and {@link #getColumnText(Object, int) label} for each.
+ */
+@SuppressWarnings("deprecation")
+abstract class PropertyItem extends ItemProvider implements ITableItemLabelProvider, ITableItemFontProvider, IMergeViewerItem.Container {
+
+ /** This is the special category name for property descriptors without a category. */
+ private static final String MISC_CATEGORY = EMFCompareIDEUIMessages
+ .getString("PropertyContentMergeViewer.miscCategory.label"); //$NON-NLS-1$
+
+ private static final String EXPERT_VIEW_FILTER_FLAG = "org.eclipse.ui.views.properties.expert"; //$NON-NLS-1$
+
+ /**
+ * The configuration used by the property item. It is used to know the
+ * {@link EMFCompareConfiguration#getComparison() comparison} and to get
+ * {@link EMFCompareConfiguration#getBooleanProperty(String, boolean) properties}.
+ */
+ private EMFCompareConfiguration configuration;
+
+ /**
+ * The {@link #getSide() side} of this property item.
+ */
+ private MergeViewerSide side;
+
+ /**
+ * The property item corresponding to the ancestor.
+ */
+ protected PropertyItem ancestor;
+
+ /**
+ * The property item corresponding to the left.
+ */
+ protected PropertyItem left;
+
+ /**
+ * The property item corresponding to the right.
+ */
+ protected PropertyItem right;
+
+ /**
+ * Creates a root property item.
+ * <p>
+ * Builds a property item for the given object on the given side. This is used both to create a
+ * {@link #getRootItem() root} item and to build the children a property value that implements
+ * {@link IItemPropertySource}.
+ * </p>
+ *
+ * @param configuration
+ * the compare configuration of the root property item.
+ * @param object
+ * the side object for the root property item.
+ * @param side
+ * the side of this root property item.
+ * @return a new root property item.
+ */
+ public static PropertyItem createPropertyItem(final EMFCompareConfiguration configuration,
+ final Object object, final MergeViewerSide side) {
+ final AdapterFactoryItemDelegator itemDelegator = new AdapterFactoryItemDelegator(
+ configuration.getAdapterFactory());
+
+ PropertyItem rootItem = new RootPropertyItem(configuration, itemDelegator, object, side);
+ List<IItemPropertyDescriptor> propertyDescriptors = getPropertyDescriptors(object, itemDelegator);
+ populateRootPropertyItem(rootItem, propertyDescriptors, object, configuration, side);
+
+ return rootItem;
+ }
+
+ private static List<IItemPropertyDescriptor> getPropertyDescriptors(final Object object,
+ final AdapterFactoryItemDelegator itemDelegator) {
+ List<IItemPropertyDescriptor> propertyDescriptors;
+ if (object instanceof Resource) {
+ // Special case for resources, because those generally have no property descriptors
+ propertyDescriptors = Collections
+ .singletonList(new ResourcePropertyDescriptor((Resource)object, itemDelegator));
+ } else {
+ propertyDescriptors = itemDelegator.getPropertyDescriptors(object);
+ }
+ return propertyDescriptors;
+ }
+
+ private static void populateRootPropertyItem(final PropertyItem rootItem,
+ final List<IItemPropertyDescriptor> propertyDescriptors, final Object object,
+ final EMFCompareConfiguration configuration, final MergeViewerSide side) {
+ if (propertyDescriptors == null) {
+ return;
+ }
+
+ Map<EStructuralFeature, Multimap<Object, Diff>> featureDiffs = buildFeatureToDiffMap(object,
+ configuration);
+
+ // A map from category name to a map from property name to the property descriptor item with
+ // that name. These both use tree maps to sort the categories and the property descriptors.
+ Map<String, Map<String, PropertyItem>> categories = Maps.newTreeMap();
+ for (IItemPropertyDescriptor propertyDescriptor : propertyDescriptors) {
+ addChildPropertyItem(categories, propertyDescriptor, object, configuration, featureDiffs, side);
+ }
+
+ // Compose the results into the children, do so with or without categories, as appropriate.
+ EList<PropertyItem> children = rootItem.getPropertyItems();
+ // If we're showing categories and there are categories, other than only the misc category...
+ if (shouldShowCategories(configuration) && (categories.size() > 1
+ || categories.size() == 1 && categories.get(MISC_CATEGORY) == null)) {
+ // Build a category item for each category, adding it to the children, and add the
+ // property items as children of that category item.
+ for (Map.Entry<String, Map<String, PropertyItem>> entry : categories.entrySet()) {
+ PropertyItem categoryItem = new PropertyCategoryItem(configuration, entry.getKey(), side);
+ children.add(categoryItem);
+ categoryItem.getChildren().addAll(entry.getValue().values());
+ }
+ } else {
+ // Otherwise, compose all the categories into a single map and use those sorted property
+ // descriptor items as the children.
+ Map<String, PropertyItem> sortedItems = Maps.newTreeMap();
+ for (Map<String, PropertyItem> items : categories.values()) {
+ sortedItems.putAll(items);
+ }
+ children.addAll(sortedItems.values());
+ }
+ }
+
+ private static void addChildPropertyItem(Map<String, Map<String, PropertyItem>> categories,
+ IItemPropertyDescriptor itemPropertyDescriptor, Object object,
+ EMFCompareConfiguration configuration,
+ Map<EStructuralFeature, Multimap<Object, Diff>> featureDiffs, MergeViewerSide side) {
+ // If we're not showing advanced properties, skip the property descriptors flagged as
+ // expert properties.
+ if (!shouldShowAdvancedProperties(configuration)) {
+ String[] filterFlags = itemPropertyDescriptor.getFilterFlags(object);
+ if (filterFlags != null) {
+ for (String filterFlag : filterFlags) {
+ if (EXPERT_VIEW_FILTER_FLAG.equals(filterFlag)) {
+ return;
+ }
+ }
+ }
+ }
+
+ // Get the feature of the property fetch and its corresponding diffs multi-map.
+ Object feature = itemPropertyDescriptor.getFeature(object);
+ Multimap<Object, Diff> diffs = featureDiffs.remove(feature);
+ PropertyItem childItem = new PropertyDescriptorItem(configuration, object, diffs,
+ itemPropertyDescriptor, side);
+
+ // Fetch the map for the category, creating one if necessary.
+ String category = determineCategory(object, itemPropertyDescriptor);
+ Map<String, PropertyItem> items = categories.get(category);
+ if (items == null) {
+ items = Maps.newTreeMap();
+ categories.put(category, items);
+ }
+
+ // Put the item in the sorted map.
+ items.put(itemPropertyDescriptor.getDisplayName(object), childItem);
+ }
+
+ private static String determineCategory(Object object, IItemPropertyDescriptor itemPropertyDescriptor) {
+ // Determine the category, using misc if there isn't one.
+ String category = itemPropertyDescriptor.getCategory(object);
+ if (category == null) {
+ category = MISC_CATEGORY;
+ }
+ return category;
+ }
+
+ private static boolean shouldShowCategories(EMFCompareConfiguration configuration) {
+ return configuration.getBooleanProperty(PropertyContentMergeViewer.SHOW_CATEGORIES, true);
+ }
+
+ public static Match getMatch(EMFCompareConfiguration configuration, Object object) {
+ Match match = null;
+ if (object instanceof EObject) {
+ EObject eObject = (EObject)object;
+ match = configuration.getComparison().getMatch(eObject);
+ }
+ return match;
+ }
+
+ private static boolean shouldShowAdvancedProperties(EMFCompareConfiguration configuration) {
+ return configuration.getBooleanProperty(PropertyContentMergeViewer.SHOW_ADVANCED_PROPERTIES, false);
+ }
+
+ /**
+ * Builds map from each feature to a multi-map of each side value to its corresponding diff.
+ * <p>
+ * We can only do this if object is an {@link EObject} and if <code>match</code> isn't <code>null</code>.
+ * </p>
+ *
+ * @param object
+ * The object to build the featureToDiff map for.
+ * @param match
+ * the match of the object.
+ * @param comparison
+ * The comparison.
+ * @return map from each feature to a multi-map of each side value to its corresponding diff.
+ */
+ private static Map<EStructuralFeature, Multimap<Object, Diff>> buildFeatureToDiffMap(Object object,
+ EMFCompareConfiguration configuration) {
+ final Match match = getMatch(configuration, object);
+ if (match == null || !(object instanceof EObject)) {
+ return Maps.newHashMap();
+ }
+
+ final Map<EStructuralFeature, Multimap<Object, Diff>> featureDiffs = Maps.newHashMap();
+ for (Diff diff : match.getDifferences()) {
+ // If that diff affects a specific feature...
+ EStructuralFeature eStructuralFeature = MergeViewerUtil.getAffectedFeature(diff);
+ if (eStructuralFeature != null) {
+ // Get the multi-map for that feature, creating a new one if necessary.
+ Multimap<Object, Diff> diffs = featureDiffs.get(eStructuralFeature);
+ if (diffs == null) {
+ diffs = HashMultimap.create();
+ featureDiffs.put(eStructuralFeature, diffs);
+ }
+
+ // Get the primary value of this diff and then iterate over the sides.
+ Object value = MatchUtil.getValue(diff);
+ for (MergeViewerSide valueSide : PropertyContentMergeViewer.MERGE_VIEWER_SIDES) {
+ // If there is a corresponding side value for the match...
+ EObject sideEObject = MergeViewerUtil.getEObject(match, valueSide);
+ if (sideEObject != null) {
+ // Get the corresponding value of that feature on that side.
+ List<Object> sideValues = ReferenceUtil.getAsList(sideEObject, eStructuralFeature);
+ // If the feature is multi-valued...
+ if (eStructuralFeature.isMany()) {
+ // Find the corresponding side-value of the value on those side values.
+ Object sideValue = MergeViewerUtil.matchingValue(value,
+ configuration.getComparison(), sideValues);
+ if (sideValue != null) {
+ diffs.put(sideValue, diff);
+ }
+ } else if (sideValues.isEmpty()) {
+ // Otherwise, directly use what's typically the one value in the side values.
+ diffs.put(null, diff);
+ } else {
+ diffs.put(sideValues.get(0), diff);
+ }
+ }
+ }
+ }
+ }
+ return featureDiffs;
+ }
+
+ /**
+ * Creates an instance of a property item.
+ *
+ * @param configuration
+ * the compare configuration.
+ * @param image
+ * the image of this property item.
+ * @param text
+ * the text of this property item.
+ * @param side
+ * the side of this property item.
+ */
+ public PropertyItem(EMFCompareConfiguration configuration, Object image, String text,
+ MergeViewerSide side) {
+ super(text, image);
+ this.configuration = configuration;
+ this.side = side;
+ setSidePropertyItem(side, this);
+ }
+
+ /**
+ * Returns the corresponding property item for the specified side.
+ *
+ * @param anySide
+ * the side of the desired property item.
+ * @return the corresponding property item for the specified side.
+ */
+ public PropertyItem getSide(MergeViewerSide anySide) {
+ switch (anySide) {
+ case ANCESTOR:
+ return ancestor;
+ case LEFT:
+ return left;
+ case RIGHT:
+ default:
+ return right;
+ }
+ }
+
+ /**
+ * This is called on a {@link #createPropertyItem(EMFCompareConfiguration, Object, MergeViewerSide) root}
+ * item by {@link PropertyContentMergeViewer#buildPropertiesFromSides(Object, Object, Object)} once it has
+ * built all three sides.
+ *
+ * @param newLeftSide
+ * the corresponding left-side root property item.
+ * @param newRightSide
+ * the corresponding right-side root property item.
+ */
+ public void reconcile(PropertyItem newLeftSide, PropertyItem newRightSide) {
+ associate(MergeViewerSide.LEFT, newLeftSide);
+ associate(MergeViewerSide.RIGHT, newRightSide);
+
+ if (newLeftSide != null) {
+ newLeftSide.associate(MergeViewerSide.RIGHT, newRightSide);
+ reconcile(newLeftSide.getPropertyItems());
+ }
+
+ if (newRightSide != null) {
+ reconcile(newRightSide.getPropertyItems());
+ }
+
+ if (newLeftSide != null && newRightSide != null) {
+ newLeftSide.reconcile(newRightSide.getPropertyItems());
+ }
+
+ for (PropertyItem propertyItem : getPropertyItems()) {
+ propertyItem.reconcile();
+ }
+
+ if (newLeftSide != null) {
+ for (PropertyItem propertyItem : newLeftSide.getPropertyItems()) {
+ propertyItem.reconcile();
+ }
+ }
+
+ if (newRightSide != null) {
+ for (PropertyItem propertyItem : newRightSide.getPropertyItems()) {
+ propertyItem.reconcile();
+ }
+ }
+ }
+
+ /**
+ * Set the appropriate bidirectional side associations.
+ *
+ * @param otherSide
+ * the side of that other property item.
+ * @param propertyItem
+ * the other property item.
+ */
+ private void associate(MergeViewerSide otherSide, PropertyItem propertyItem) {
+ setSidePropertyItem(side, this);
+ setSidePropertyItem(otherSide, propertyItem);
+ if (propertyItem != null) {
+ propertyItem.setSidePropertyItem(side, this);
+ propertyItem.setSidePropertyItem(otherSide, propertyItem);
+ }
+ }
+
+ /**
+ * Set the value of the appropriate side's field.
+ *
+ * @param otherSide
+ * the side to set.
+ * @param propertyItem
+ * the value to which to set it.
+ */
+ private void setSidePropertyItem(MergeViewerSide otherSide, PropertyItem propertyItem) {
+ switch (otherSide) {
+ case ANCESTOR:
+ ancestor = propertyItem;
+ break;
+ case LEFT:
+ left = propertyItem;
+ break;
+ case RIGHT:
+ right = propertyItem;
+ break;
+ }
+ }
+
+ /**
+ * Reconcile's this side's properties against the other side property items.
+ *
+ * @param otherPropertyItems
+ * the other side's property items.
+ */
+ private void reconcile(EList<PropertyItem> otherPropertyItems) {
+ EList<PropertyItem> propertyItems = getPropertyItems();
+ List<PropertyItem> remainingOtherPropertyItems = Lists.newArrayList(otherPropertyItems);
+ for (PropertyItem propertyItem : propertyItems) {
+ // This will associate the items, removing them once associated.
+ propertyItem.findMatchingItem(remainingOtherPropertyItems, true);
+ }
+ }
+
+ /**
+ * Reconcile the properties items of the sides against each other, and then recursively reconcile all the
+ * property items of each side.
+ */
+ protected void reconcile() {
+ switch (side) {
+ case ANCESTOR:
+ if (left != null) {
+ reconcile(left.getPropertyItems());
+ }
+ if (right != null) {
+ reconcile(right.getPropertyItems());
+ }
+ break;
+ case LEFT:
+ if (right != null) {
+ left.reconcile(right.getPropertyItems());
+ }
+ break;
+ }
+
+ for (PropertyItem propertyItem : getPropertyItems()) {
+ propertyItem.reconcile();
+ }
+ }
+
+ /**
+ * Finds a matching item in the property items.
+ *
+ * @param propertyItem
+ * the item to find.
+ * @param propertyItems
+ * the items in which to find it.
+ * @param associate
+ * whether to associate the matching item and to remove it from the property items.
+ * @return the matching item.
+ */
+ private PropertyItem findMatchingItem(List<? extends PropertyItem> propertyItems, boolean associate) {
+ for (PropertyItem otherPropertyItem : propertyItems) {
+ if (isMatchingItem(otherPropertyItem)) {
+ if (associate) {
+ associate(otherPropertyItem.side, otherPropertyItem);
+ propertyItems.remove(otherPropertyItem);
+ }
+ return otherPropertyItem;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether this property item matches the specified property item.
+ *
+ * @param propertyItem
+ * the property item against which to match.
+ * @return whether this property item matches the specified property item.
+ */
+ protected abstract boolean isMatchingItem(PropertyItem propertyItem);
+
+ /**
+ * Determines if the two values {@link IEqualityHelper#matchingValues(Object, Object) match} using the
+ * comparison's equality helper.
+ *
+ * @param value1
+ * the first value.
+ * @param value2
+ * the second value.
+ * @return whether the two values match.
+ */
+ protected boolean isMatchingValue(Object value1, Object value2) {
+ IEqualityHelper equalityHelper = configuration.getComparison().getEqualityHelper();
+ return equalityHelper.matchingValues(value1, value2);
+ }
+
+ /**
+ * Finds the corresponding property item of the specified property item somewhere within the receiver
+ * property item.
+ *
+ * @param propertyItem
+ * the property item to find.
+ * @return the corresponding property item or the deepest property item in the tree along the path of the
+ * specified property item.
+ */
+ public PropertyItem findItem(PropertyItem propertyItem) {
+ PropertyItem propertyItemParent = propertyItem.getParent();
+ if (propertyItemParent == null) {
+ return this;
+ } else {
+ PropertyItem foundParent = findItem(propertyItemParent);
+
+ PropertyItem findMatchingItem = propertyItem.findMatchingItem(foundParent.getPropertyItems(),
+ false);
+
+ if (findMatchingItem == null) {
+ return this;
+ } else {
+ return findMatchingItem;
+ }
+ }
+ }
+
+ /**
+ * Returns the children, which must be property items.
+ *
+ * @return the children.
+ */
+ @SuppressWarnings("unchecked")
+ public EList<PropertyItem> getPropertyItems() {
+ return (EList<PropertyItem>)(EList<?>)children;
+ }
+
+ protected boolean isModified() {
+ return false;
+ }
+
+ /**
+ * Returns the parent, which must be a property item.
+ *
+ * @return the parent.
+ */
+ @Override
+ public PropertyItem getParent() {
+ return (PropertyItem)super.getParent();
+ }
+
+ /**
+ * Returns the primary object of this property item.
+ *
+ * @return the primary object of this property item.
+ */
+ protected abstract Object getObject();
+
+ /**
+ * Returns the root property item.
+ *
+ * @return the root property item.
+ */
+ public PropertyItem getRootItem() {
+ PropertyItem rootItem = this;
+ while (rootItem.getParent() != null) {
+ rootItem = rootItem.getParent();
+ }
+ return rootItem;
+ }
+
+ /**
+ * This must be called when the item property descriptor is expanded and collapsed. For lists it's
+ * designed to hide the property image and property text while the list is expanded, showing it again when
+ * it's collapsed.
+ *
+ * @param treeItem
+ * the item being expanded or collapsed.
+ * @param expanded
+ * whether the item is expanded as opposite to collapsed.
+ */
+ public void update(TreeItem treeItem, boolean expanded) {
+ }
+
+ /**
+ * Returns the text for the value column of the property item.
+ *
+ * @return the text for the value column of the property item.
+ */
+ protected String getPropertyText() {
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the image for the value column of the property item.
+ *
+ * @return the image for the value column of the property item.
+ */
+ protected Object getPropertyImage() {
+ return null;
+ }
+
+ /**
+ * Returns the text for the property column or value column.
+ *
+ * @param object
+ * the object which is generally ignored.
+ * @param columnIndex
+ * either {@code 0} or {@code 1}, for the property column or value column respectively.
+ * @return the text for the property column or value column.
+ */
+ public String getColumnText(Object object, int columnIndex) {
+ if (columnIndex == 0) {
+ return getText(object);
+ } else {
+ return getPropertyText();
+ }
+ }
+
+ /**
+ * Returns the image for the property column or value column.
+ *
+ * @param object
+ * the object which is generally ignored.
+ * @param columnIndex
+ * either {@code 0} or {@code 1}, for the property column or value column respectively.
+ * @return the image for the property column or value column.
+ */
+ public Object getColumnImage(Object object, int columnIndex) {
+ if (columnIndex == 0) {
+ return getImage(object);
+ } else {
+ return getPropertyImage();
+ }
+ }
+
+ /**
+ * Returns the font for the property column or value column. {@link #isModified() Modified} property items
+ * will be shown in bold font.
+ *
+ * @param object
+ * the object, which is ignored.
+ * @param columnIndex
+ * either {@code 0} or {@code 1}, for the property column or value column respectively.
+ * @return the font for the property column or value column.
+ */
+ public Object getFont(Object object, int columnIndex) {
+ if (isModified()) {
+ return IItemFontProvider.BOLD_FONT;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the diff associated with this property item.
+ *
+ * @return the diff associated with this property item.
+ */
+ @Override
+ public Diff getDiff() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getLeft() {
+ if (left != null) {
+ return left.getObject();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getRight() {
+ if (right != null) {
+ return right.getObject();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getAncestor() {
+ if (ancestor != null) {
+ return ancestor.getObject();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object getSideValue(MergeViewerSide anySide) {
+ switch (anySide) {
+ case ANCESTOR:
+ return getAncestor();
+ case LEFT:
+ return getLeft();
+ case RIGHT:
+ default:
+ return getRight();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MergeViewerSide getSide() {
+ return side;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isInsertionPoint() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void notifyChanged(Notification notification) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Notifier getTarget() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setTarget(Notifier newTarget) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isAdapterForType(Object type) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasChildren(IDifferenceGroupProvider group, Predicate<? super EObject> predicate) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public IMergeViewerItem[] getChildren(IDifferenceGroupProvider group,
+ Predicate<? super EObject> predicate) {
+ return null;
+ }
+
+ public EMFCompareConfiguration getConfiguration() {
+ return configuration;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyListElementItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyListElementItem.java
new file mode 100644
index 000000000..dbcb26263
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyListElementItem.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2018 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+
+class PropertyListElementItem extends DiffPropertyItem {
+
+ PropertyListElementItem(EMFCompareConfiguration configuration, IItemLabelProvider itemLabelProvider,
+ Diff diff, Object value, MergeViewerSide side) {
+ super(configuration, itemLabelProvider, diff, value, side);
+ }
+
+ @Override
+ protected boolean isModified() {
+ if (getDiff() != null) {
+ // We won't mark it as modified because then it will be bold, but there will be a box
+ // around it so we don't want that.
+ return false;
+ } else {
+ // Otherwise, it's modified if the other side is missing.
+ switch (getSide()) {
+ case LEFT:
+ return getRight() == null;
+ case RIGHT:
+ return getLeft() == null;
+ default:
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeMergeViewer.java
new file mode 100644
index 000000000..f3aacae90
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeMergeViewer.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.common.notify.AdapterFactory;
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeAction;
+import org.eclipse.emf.compare.internal.merge.MergeMode;
+import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
+import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.impl.TreeMergeViewer;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.ICompareColor;
+import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
+import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.TreeEvent;
+import org.eclipse.swt.events.TreeListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * A specialized {@link TreeMergeViewer tree merge viewer} that can display {@link PropertyItem property
+ * items}.
+ */
+class PropertyTreeMergeViewer extends TreeMergeViewer {
+
+ /**
+ * The handler for resizing columns to their packed size.
+ */
+ final ColumnResizer.Handler columnResizer;
+
+ /**
+ * The root property item input for this viewer.
+ */
+ private PropertyItem rootPropertyItem;
+
+ /**
+ * Creates an instance with the specified parent, for the specified side, with specified color provider,
+ * for the specified compare configuration.
+ *
+ * @param parent
+ * the parent composite for this viewer's control.
+ * @param side
+ * the side of this viewer.
+ * @param colorProvider
+ * the color provider for drawing/highlighting property items with an associated diff.
+ * @param configuration
+ * the compare configuration
+ */
+ PropertyTreeMergeViewer(Composite parent, MergeViewerSide side, ICompareColor.Provider colorProvider,
+ final EMFCompareConfiguration configuration) {
+ super(parent, side, colorProvider, configuration);
+
+ // Create a simple content provider from the adapter factory of the configuration.
+ // Note that we want to ignore notifications because a property item will not update based on
+ // notifications.
+ AdapterFactory adapterFactory = configuration.getAdapterFactory();
+ AdapterFactoryContentProvider adapterFactoryContentProvider = new AdapterFactoryContentProvider(
+ adapterFactory) {
+ @Override
+ public void notifyChanged(Notification notification) {
+ }
+ };
+ setContentProvider(adapterFactoryContentProvider);
+
+ // Create a label provider supporting fonts because we draw modified property items with a bold font.
+ setLabelProvider(new AdapterFactoryLabelProvider.FontProvider(adapterFactory, this));
+
+ // Limit expansion the expansion to 10 levels. It's unlikely any properties nest deeper than this.
+ TreeViewer treeViewer = getStructuredViewer();
+ treeViewer.setAutoExpandLevel(10);
+
+ // Set up the two columns, Property and Value, for the tree.
+ final Tree tree = treeViewer.getTree();
+ TreeColumn propColumn = new TreeColumn(tree, SWT.LEFT, 0);
+ propColumn.setText(EMFCompareIDEUIMessages.getString("PropertyContentMergeViewer.property.label")); //$NON-NLS-1$
+ TreeColumn valueColumn = new TreeColumn(tree, SWT.LEFT, 1);
+ valueColumn.setText(EMFCompareIDEUIMessages.getString("PropertyContentMergeViewer.value.label")); //$NON-NLS-1$
+
+ // Show the header of the columns and make the lines of the "table" visible.
+ tree.setHeaderVisible(true);
+ tree.setLinesVisible(true);
+
+ // Attached the resize handler for packing columns to their minimal size.
+ columnResizer = ColumnResizer.addColumnResizer(tree);
+
+ // Listen for expand and collapse events so that we can update tree item.
+ // This is important for list-type properties which should show the size of the list when
+ // collapsed but be blank when expanded.
+ tree.addTreeListener(new TreeListener() {
+ public void treeExpanded(TreeEvent event) {
+ update((TreeItem)event.item, true);
+ }
+
+ public void treeCollapsed(TreeEvent event) {
+ update((TreeItem)event.item, false);
+ }
+
+ private void update(TreeItem treeItem, boolean expanded) {
+ PropertyItem propertyItem = (PropertyItem)treeItem.getData();
+ propertyItem.update(treeItem, expanded);
+ }
+ });
+
+ // Listen for double click events in order to expand and collapse property items via double click.
+ tree.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseDoubleClick(MouseEvent event) {
+ TreeItem treeItem = tree.getItem(new Point(event.x, event.y));
+ if (treeItem != null && treeItem.getItemCount() > 0) {
+ boolean expanded = !treeItem.getExpanded();
+ treeItem.setExpanded(expanded);
+ PropertyItem propertyItem = (PropertyItem)treeItem.getData();
+ propertyItem.update(treeItem, expanded);
+ }
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation creates an {@link MergeAction} with the specified diff as the selection..
+ * </p>
+ */
+ @Override
+ protected IAction createAction(MergeMode mode, Diff diff) {
+ return new MergeAction(getCompareConfiguration(),
+ EMFCompareRCPPlugin.getDefault().getMergerRegistry(), mode, null,
+ new StructuredSelection(diff));
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation fetches the {@link PropertyAccessor#getRootPropertyItem() root property item} of
+ * the input, which must be an instance of {@link PropertyAccessor} and not be {@code null}, using that as
+ * the input for the {@linked #getStructuredViewer() structured viewer}. It then {@link #columnResizer
+ * packs} the columns.
+ * </p>
+ */
+ @Override
+ protected void inputChanged(Object input, Object oldInput) {
+ rootPropertyItem = ((PropertyAccessor)input).getRootPropertyItem();
+
+ TreeViewer treeViewer = getStructuredViewer();
+ treeViewer.setSelection(null);
+ treeViewer.setInput(rootPropertyItem);
+
+ columnResizer.resizeColumns();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * </p>
+ */
+ @Override
+ protected TreeViewer createTreeViewer(Composite parent) {
+ return new PropertyTreeViewer(parent, getRootPropertyItem(), SWT.FULL_SELECTION | SWT.SINGLE
+ | SWT.HIDE_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+ }
+
+ /**
+ * Returns the root property item.
+ *
+ * @return the root property item.
+ */
+ public PropertyItem getRootPropertyItem() {
+ return rootPropertyItem;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeViewer.java
new file mode 100644
index 000000000..de0f9cca1
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeViewer.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Item;
+import org.eclipse.swt.widgets.TreeItem;
+
+/**
+ * Specialized tree viewer with the appropriate style for showing two-column properties, appropriate
+ * {@link TreeViewer#setSelection(ISelection) selection handling} and
+ * {@link TreeViewer#setExpandedState(Object, boolean) expansion handling}, and without a
+ * {@link TreeViewer#setComparer(IElementComparer) comparer}.
+ */
+final class PropertyTreeViewer extends TreeViewer {
+
+ private PropertyItem rootPropertyItem;
+
+ PropertyTreeViewer(Composite parent, PropertyItem propertyItem, int style) {
+ super(parent, style);
+ this.rootPropertyItem = propertyItem;
+ }
+
+ @Override
+ public void setSelection(ISelection selection, boolean reveal) {
+ // Specialize selection so that it finds the appropriate property item for the side.
+ if (rootPropertyItem != null && selection != null && !selection.isEmpty()) {
+ PropertyItem propertyItem = (PropertyItem)((IStructuredSelection)selection).getFirstElement();
+ // Get the item for this viewer's side.
+ PropertyItem sidePropertyItem = propertyItem.getSide(rootPropertyItem.getSide());
+ // If there isn't one...
+ if (sidePropertyItem == null) {
+ // Clear the selection.
+ super.setSelection(new StructuredSelection(), reveal);
+ } else {
+ // If it isn't an item in this viewer's root property item.
+ if (sidePropertyItem.getRootItem() != rootPropertyItem) {
+ // Find the corresponding item in the root property item.
+ // This will always be non-nul.
+ sidePropertyItem = rootPropertyItem.findItem(sidePropertyItem);
+ }
+ // Set the appropriate selection.
+ super.setSelection(new StructuredSelection(sidePropertyItem), reveal);
+ }
+ } else {
+ // Let the viewer do it's normal thing, which generally will clear the selection.
+ super.setSelection(selection, reveal);
+ }
+ }
+
+ @Override
+ public void setComparer(IElementComparer comparer) {
+ // We don't want to use the comparer set by the base class during creation because it's
+ // specialized for a different implement of IMergeViewerItem than the one implemented by
+ // PropertyItem.
+ }
+
+ @Override
+ protected void setExpanded(Item item, boolean expand) {
+ // Also update the property item when this occurs.
+ super.setExpanded(item, expand);
+ PropertyItem propertyItem = (PropertyItem)item.getData();
+ propertyItem.update((TreeItem)item, expand);
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyValuePlaceholderItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyValuePlaceholderItem.java
new file mode 100644
index 000000000..be1f4b365
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyValuePlaceholderItem.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2018 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+
+class PropertyValuePlaceholderItem extends DiffPropertyItem {
+
+ PropertyValuePlaceholderItem(EMFCompareConfiguration configuration, Diff diff, Object value,
+ MergeViewerSide side) {
+ super(configuration, diff, value, side);
+ }
+
+ @Override
+ public boolean isInsertionPoint() {
+ return true;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ResourcePropertyDescriptor.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ResourcePropertyDescriptor.java
new file mode 100644
index 000000000..c5f531ee8
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ResourcePropertyDescriptor.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2017 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import java.util.Collection;
+
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.edit.provider.AdapterFactoryItemDelegator;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
+
+final class ResourcePropertyDescriptor implements IItemPropertyDescriptor {
+
+ private final Resource resource;
+
+ private final AdapterFactoryItemDelegator itemDelegator;
+
+ ResourcePropertyDescriptor(Resource resource, AdapterFactoryItemDelegator itemDelegator) {
+ this.resource = resource;
+ this.itemDelegator = itemDelegator;
+ }
+
+ public void setPropertyValue(Object obj, Object value) {
+ }
+
+ public void resetPropertyValue(Object obj) {
+ }
+
+ public boolean isSortChoices(Object obj) {
+ return false;
+ }
+
+ public boolean isPropertySet(Object obj) {
+ return true;
+ }
+
+ public boolean isMultiLine(Object obj) {
+ return false;
+ }
+
+ public boolean isMany(Object obj) {
+ return true;
+ }
+
+ public boolean isCompatibleWith(Object obj, Object anotherObject,
+ IItemPropertyDescriptor anotherPropertyDescriptor) {
+ return false;
+ }
+
+ public Object getPropertyValue(Object obj) {
+ return resource.getContents();
+ }
+
+ public IItemLabelProvider getLabelProvider(Object obj) {
+ return new IItemLabelProvider() {
+
+ public String getText(Object theObject) {
+ return itemDelegator.getText(theObject);
+ }
+
+ public Object getImage(Object theObject) {
+ return itemDelegator.getImage(theObject);
+ }
+ };
+ }
+
+ public String getId(Object obj) {
+ return getDisplayName(obj);
+ }
+
+ public Object getHelpContextIds(Object obj) {
+ return null;
+ }
+
+ public String[] getFilterFlags(Object obj) {
+ return null;
+ }
+
+ public Object getFeature(Object obj) {
+ return Integer.valueOf(Resource.RESOURCE__CONTENTS);
+ }
+
+ public String getDisplayName(Object obj) {
+ return EMFCompareIDEUIMessages.getString("PropertyContentMergeViewer.resourceContentsProperty.label"); //$NON-NLS-1$
+ }
+
+ public String getDescription(Object obj) {
+ return EMFCompareIDEUIMessages
+ .getString("PropertyContentMergeViewer.resourceContentsProperty.description"); //$NON-NLS-1$
+ }
+
+ public Collection<?> getChoiceOfValues(Object obj) {
+ return null;
+ }
+
+ public String getCategory(Object obj) {
+ return null;
+ }
+
+ public boolean canSetProperty(Object obj) {
+ return false;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/RootPropertyItem.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/RootPropertyItem.java
new file mode 100644
index 000000000..fc30618b3
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/RootPropertyItem.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2018 EclipseSource Services GmbH and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property;
+
+import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.emf.edit.provider.IItemLabelProvider;
+
+class RootPropertyItem extends PropertyItem {
+
+ private Object object;
+
+ RootPropertyItem(EMFCompareConfiguration configuration, IItemLabelProvider itemLabelProvider,
+ Object object, MergeViewerSide side) {
+ super(configuration, itemLabelProvider.getImage(object), itemLabelProvider.getText(object), side);
+ this.object = object;
+ }
+
+ @Override
+ protected Object getObject() {
+ return object;
+ }
+
+ @Override
+ protected boolean isMatchingItem(PropertyItem propertyItem) {
+ // A root item always matches another root item.
+ return getClass().isInstance(propertyItem);
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
index 161ea0178..f5cce4f7e 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
@@ -7,7 +7,7 @@
#
# Contributors:
# Obeo - initial API and implementation
-# Philip Langer - log msgs, bugs 462884, 508855, 521948, 522372, 508526
+# Philip Langer - log msgs, bugs 462884, 508855, 521948, 522372, 508526, 527567
# Stefan Dirix - bug 456699, 474723
# Michael Borkowski - bug 467191
################################################################################
@@ -166,4 +166,14 @@ TextFallbackCompareViewer.local.title = Local: {0}
TextFallbackCompareViewer.preview.title = Preview: {0}
TextFallbackCompareViewer.dirty.title = * {0}
+PropertyContentMergeViewer.hideAdvancedProperties.tooltip = Hide Advanced Properties
+PropertyContentMergeViewer.showAdvancedProperties.tooltip = Show Advanced Properties
+PropertyContentMergeViewer.hideCategories.tooltip = Hide Categories
+PropertyContentMergeViewer.showCategories.tooltip = Show Categories
+PropertyContentMergeViewer.property.label = Property
+PropertyContentMergeViewer.value.label = Value
+PropertyContentMergeViewer.resourceContentsProperty.label = Contents
+PropertyContentMergeViewer.resourceContentsProperty.description = The contents of the resource
+PropertyContentMergeViewer.miscCategory.label = Misc
+

Back to the top