From 2f639210b1a82189d84f160e87fc3d9a1e0c250b Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Wed, 22 Nov 2017 14:39:09 +0100 Subject: [PATCH] [527567] Provide support for a general property merge viewer Adds and registers Property Content Merge Viewer for all input types. Bug: 527567 Change-Id: Ic4654bbfaf6557dafc18b3bc8499711439cd314a Signed-off-by: Philip Langer --- .../plugin.xml | 9 +- .../META-INF/MANIFEST.MF | 1 + .../full/toolb16/show_advanced_properties.png | Bin 0 -> 328 bytes .../icons/full/toolb16/show_categories.png | Bin 0 -> 192 bytes plugins/org.eclipse.emf.compare.ide.ui/plugin.xml | 19 +- .../contentmergeviewer/property/ColumnResizer.java | 334 +++++++++ .../property/DiffPropertyItem.java | 73 ++ .../property/PropertyAccessor.java | 186 +++++ .../property/PropertyCategoryItem.java | 36 + .../property/PropertyContentMergeViewer.java | 578 +++++++++++++++ .../property/PropertyContentMergeViewer.properties | 67 ++ .../PropertyContentMergeViewerCreator.java | 40 ++ .../property/PropertyDescriptorItem.java | 423 +++++++++++ .../contentmergeviewer/property/PropertyItem.java | 775 +++++++++++++++++++++ .../property/PropertyListElementItem.java | 44 ++ .../property/PropertyTreeMergeViewer.java | 190 +++++ .../property/PropertyTreeViewer.java | 78 +++ .../property/PropertyValuePlaceholderItem.java | 29 + .../property/ResourcePropertyDescriptor.java | 112 +++ .../property/RootPropertyItem.java | 38 + .../ide/ui/internal/ide_ui_messages.properties | 12 +- 21 files changed, 3040 insertions(+), 4 deletions(-) create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_categories.png create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ColumnResizer.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/DiffPropertyItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyAccessor.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyCategoryItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewer.properties create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyContentMergeViewerCreator.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyDescriptorItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyListElementItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeMergeViewer.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyTreeViewer.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/PropertyValuePlaceholderItem.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/ResourcePropertyDescriptor.java create mode 100644 plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/property/RootPropertyItem.java diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml b/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml index 2b27e1555..3480f5a3d 100644 --- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml +++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/plugin.xml @@ -2,7 +2,7 @@ @@ -56,6 +57,12 @@ id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.TextFallbackCompareViewer" label="Text Compare"> + + diff --git a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF index 229e78c94..7aa16ac77 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.emf.compare.ide.ui/META-INF/MANIFEST.MF @@ -31,6 +31,7 @@ Export-Package: org.eclipse.emf.compare.ide.ui.dependency, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer;x-internal:=true, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.accessor;x-internal:=true, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label;x-internal:=true, + org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.property, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.provider;x-internal:=true, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.table;x-internal:=true, org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.text;x-internal:=true, diff --git a/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png b/plugins/org.eclipse.emf.compare.ide.ui/icons/full/toolb16/show_advanced_properties.png new file mode 100644 index 0000000000000000000000000000000000000000..27c8738a7e7151d8669558041487c84d873c37a2 GIT binary patch literal 328 zcwPZW0k{5%P)XI5@C?_}LYGxDD{@-}~RMbK`%%rltS=YG(fT zEA9OcGQcmR?7weR?0k@#xa=m)-LiYo&fu)Rvyd4B@~3o!;vcW7vU zD|8La0voWkO87qzU*B3PDnVA@f($slC}jWEYT@UbD}^<%D%xBl;sCV?EB^QM{{Nf1 zs}BR&@3&VA+F%&4tx9P5&FuyM|9v@!7vJ7fb`WUDZ%k)a3;W}9!Qc1${y#h2ab{bM z$jfcjLg6Hb!SxL>`$5hJd6nptXyFozhy`E(ZmAN~At@!oqLJ*B2unz0r$lm+GD=Fs amze @@ -113,6 +113,21 @@ id="org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.TextFallbackCompareViewer" label="%emf.compare.fallback.textcompare"> + + 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; + +/** + *

+ * Copied from EMF 2.14's org.eclipse.emf.common.ui.viewer.ColumnResizer. + *

+ * 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 extends Handler implements ControlListener { + private final T control; + + private final ParentHandler parentHandler; + + private int clientWidth = -1; + + private List 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 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 getColumnWidths() { + List columns = getColumns(); + List result = new ArrayList(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 columnWdths = getColumnWidths(); + + boolean inputChanged = controlEvent == null; + if (inputChanged || clientWdth != this.clientWidth || this.columnWidths.equals(columnWdths)) { + try { + resizing = true; + ctrl.setRedraw(false); + + List columns = getColumns(); + for (C column : columns) { + pack(column); + } + + List 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 { + 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 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 { + public TableColumnResizeHandler(Table table) { + super(table); + } + + @Override + protected List 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 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 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} + *

+ * This implementation is specialized to set its own specialized content provider during initialization. + * The initialization first happens when the base class' constructor is invoked. + *

+ */ + @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} + *

+ * This implementation creates a {@link PropertyContentMergeViewer}. + *

+ */ + @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} + *

+ * This implementation creates tool items for showing/hiding advanced properties and for showing/hiding + * categories. + *

+ * + * @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} + *

+ * This implementation is specialized to {@link #buildProperties(Object) build the properties} for each of + * the three sides of the input. + *

+ */ + @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 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 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} + *

+ * This implementation {@link #buildProperties(Object) re-builds the properties}, and attempts to preserve + * the selection before calling {@code super.refresh()}. + *

+ */ + @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} + *

+ * 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}. + *

+ */ + @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} + *

+ * This implementation creates a {@link PropertyContentMergeViewer}. + *

+ * + * @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 diffs; + + final private IItemPropertyDescriptor itemPropertyDescriptor; + + private boolean hasCheckedForChildren; + + private boolean needsReconcile; + + public PropertyDescriptorItem(EMFCompareConfiguration configuration, final Object object, + final Multimap 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 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 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 usedDiffs = Sets.newHashSet(); + Comparison comparison = getConfiguration().getComparison(); + + // Iterate over the entries... + for (Map.Entry 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 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 diffCandidates = diffs.get(value); + if (preferredSide != null) { + Iterator 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. + *

+ * This implementation is specialized to handle the case that the {@link #getObject() property value} is + * an {@link IItemPropertySource}. + *

+ * + * @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 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. + *

+ * 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}. + *

+ * + * @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 propertyDescriptors = getPropertyDescriptors(object, itemDelegator); + populateRootPropertyItem(rootItem, propertyDescriptors, object, configuration, side); + + return rootItem; + } + + private static List getPropertyDescriptors(final Object object, + final AdapterFactoryItemDelegator itemDelegator) { + List 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 propertyDescriptors, final Object object, + final EMFCompareConfiguration configuration, final MergeViewerSide side) { + if (propertyDescriptors == null) { + return; + } + + Map> 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> 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 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> 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 sortedItems = Maps.newTreeMap(); + for (Map items : categories.values()) { + sortedItems.putAll(items); + } + children.addAll(sortedItems.values()); + } + } + + private static void addChildPropertyItem(Map> categories, + IItemPropertyDescriptor itemPropertyDescriptor, Object object, + EMFCompareConfiguration configuration, + Map> 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 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 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. + *

+ * We can only do this if object is an {@link EObject} and if match isn't null. + *

+ * + * @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> buildFeatureToDiffMap(Object object, + EMFCompareConfiguration configuration) { + final Match match = getMatch(configuration, object); + if (match == null || !(object instanceof EObject)) { + return Maps.newHashMap(); + } + + final Map> 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 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 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 otherPropertyItems) { + EList propertyItems = getPropertyItems(); + List 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 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 getPropertyItems() { + return (EList)(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 predicate) { + return false; + } + + /** + * {@inheritDoc} + */ + public IMergeViewerItem[] getChildren(IDifferenceGroupProvider group, + Predicate 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} + *

+ * This implementation creates an {@link MergeAction} with the specified diff as the selection.. + *

+ */ + @Override + protected IAction createAction(MergeMode mode, Diff diff) { + return new MergeAction(getCompareConfiguration(), + EMFCompareRCPPlugin.getDefault().getMergerRegistry(), mode, null, + new StructuredSelection(diff)); + } + + /** + * {@inheritDoc} + *

+ * 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. + *

+ */ + @Override + protected void inputChanged(Object input, Object oldInput) { + rootPropertyItem = ((PropertyAccessor)input).getRootPropertyItem(); + + TreeViewer treeViewer = getStructuredViewer(); + treeViewer.setSelection(null); + treeViewer.setInput(rootPropertyItem); + + columnResizer.resizeColumns(); + } + + /** + * {@inheritDoc} + *

+ *

+ */ + @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 + -- 2.11.4.GIT