Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/org.eclipse.emf.compare.ide.ui/src/org')
-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
16 files changed, 3014 insertions, 1 deletions
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