blob: c95fcfe121d7f220420704ae52b9835a4fa9ad07 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2016 Obeo and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
* Stefan Dirix - bug 487595
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree.provider.TreeContentMergeViewerItemContentProvider;
import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree.provider.TreeContentMergeViewerItemLabelProvider;
import org.eclipse.emf.compare.match.impl.NotLoadedFragmentMatch;
import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.impl.AbstractMergeViewer;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.impl.TreeMergeViewer;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.item.impl.MergeViewerItem;
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.ecore.EObject;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
/**
* Specialized {@link org.eclipse.compare.contentmergeviewer.ContentMergeViewer} that uses
* {@link org.eclipse.jface.viewers.TreeViewer} to display left, right and ancestor {@link EObject}.
*
* @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
*/
public class TreeContentMergeViewer extends EMFCompareContentMergeViewer {
/**
* Bundle name of the property file containing all displayed strings.
*/
private static final String BUNDLE_NAME = TreeContentMergeViewer.class.getName();
/**
* The {@link org.eclipse.emf.common.notify.AdapterFactory} used to create
* {@link AdapterFactoryContentProvider} and {@link AdapterFactoryLabelProvider} for ancestor, left and
* right {@link org.eclipse.jface.viewers.TreeViewer}.
*/
private final ComposedAdapterFactory fAdapterFactory;
private AtomicBoolean fSyncExpandedState;
private double[] fBasicCenterCurve;
/**
* Creates a new {@link TreeContentMergeViewer} by calling the super constructor with the given
* parameters.
* <p>
* It calls {@link #buildControl(Composite)} as stated in its javadoc.
* <p>
* It sets a {@link TreeContentMergeViewerContentProvider specific}
* {@link #setContentProvider(org.eclipse.jface.viewers.IContentProvider) content provider} to properly
* display ancestor, left and right parts.
*
* @param style
* the style indicator for the parent
* @param bundle
* the {@link ResourceBundle} for localization
* @param parent
* the parent composite to build the UI in
* @param config
* the {@link CompareConfiguration}
*/
public TreeContentMergeViewer(int style, ResourceBundle bundle, Composite parent,
EMFCompareConfiguration config) {
super(style, bundle, config);
fAdapterFactory = new ComposedAdapterFactory(
EMFCompareRCPPlugin.getDefault().createFilteredAdapterFactoryRegistry());
fAdapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
fAdapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
fSyncExpandedState = new AtomicBoolean();
buildControl(parent);
setContentProvider(new TreeContentMergeViewerContentProvider(config));
}
protected ComposedAdapterFactory getAdapterFactory() {
return fAdapterFactory;
}
/**
* Creates a new {@link TreeContentMergeViewer} by calling the super constructor with the given
* parameters.
* <p>
* It calls {@link #buildControl(Composite)} as stated in its javadoc.
* <p>
* It sets a {@link TreeContentMergeViewerContentProvider specific}
* {@link #setContentProvider(org.eclipse.jface.viewers.IContentProvider) content provider} to properly
* display ancestor, left and right parts.
*
* @param parent
* the parent composite to build the UI in
* @param config
* the {@link CompareConfiguration}
*/
public TreeContentMergeViewer(Composite parent, EMFCompareConfiguration config) {
this(SWT.NONE, ResourceBundle.getBundle(BUNDLE_NAME), parent, config);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#handleDispose(org.eclipse.swt.events.DisposeEvent)
*/
@Override
protected void handleDispose(DisposeEvent event) {
fAdapterFactory.dispose();
super.handleDispose(event);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getAncestorMergeViewer()
*/
// see createMergeViewer() to see it is safe
@Override
protected TreeMergeViewer getAncestorMergeViewer() {
return (TreeMergeViewer)super.getAncestorMergeViewer();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getLeftMergeViewer()
*/
// see createMergeViewer() to see it is safe
@Override
protected TreeMergeViewer getLeftMergeViewer() {
return (TreeMergeViewer)super.getLeftMergeViewer();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getRightMergeViewer()
*/
// see createMergeViewer() to see it is safe
@Override
protected TreeMergeViewer getRightMergeViewer() {
return (TreeMergeViewer)super.getRightMergeViewer();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#getContents(boolean)
*/
@Override
protected byte[] getContents(boolean left) {
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#createMergeViewer(org.eclipse.swt.widgets.Composite)
*/
@Override
protected AbstractMergeViewer createMergeViewer(final Composite parent, final MergeViewerSide side) {
final TreeMergeViewer mergeTreeViewer = new TreeMergeViewer(parent, side, this,
getCompareConfiguration());
IContentProvider contentProvider = new TreeContentMergeViewerItemContentProvider(fAdapterFactory,
getDifferenceGroupProvider(), getDifferenceFilterPredicate());
mergeTreeViewer.setContentProvider(contentProvider);
AdapterFactoryLabelProvider labelProvider = new TreeContentMergeViewerItemLabelProvider(
getResourceBundle(), fAdapterFactory, side);
mergeTreeViewer.setLabelProvider(labelProvider);
hookListeners(mergeTreeViewer);
return mergeTreeViewer;
}
/**
* Adds all required listeners to the given {@link TreeMergeViewer}.
*
* @param treeMergeViewer
* the {@link TreeMergeViewer}.
*/
protected void hookListeners(TreeMergeViewer treeMergeViewer) {
treeMergeViewer.getStructuredViewer().getTree().addListener(SWT.Collapse,
new ExpandCollapseListener(treeMergeViewer, false));
treeMergeViewer.getStructuredViewer().getTree().addListener(SWT.Expand,
new ExpandCollapseListener(treeMergeViewer, true));
treeMergeViewer.getStructuredViewer().getTree().getVerticalBar().addListener(SWT.Selection,
new Listener() {
public void handleEvent(Event event) {
redrawCenterControl();
}
});
treeMergeViewer.getStructuredViewer().getTree().addMouseWheelListener(new MouseWheelListener() {
public void mouseScrolled(MouseEvent e) {
redrawCenterControl();
}
});
treeMergeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
redrawCenterControl();
}
});
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#paintCenter(org.eclipse.swt.widgets.Canvas,
* org.eclipse.swt.graphics.GC)
*/
@Override
protected void paintCenter(GC g) {
TreeMergeViewer leftMergeViewer = getLeftMergeViewer();
TreeMergeViewer rightMergeViewer = getRightMergeViewer();
Tree leftTree = leftMergeViewer.getStructuredViewer().getTree();
Tree rightTree = rightMergeViewer.getStructuredViewer().getTree();
Rectangle leftClientArea = leftTree.getClientArea();
Rectangle rightClientArea = rightTree.getClientArea();
final List<TreeItem> leftItems = getExpandedTreeItems(leftTree);
final List<TreeItem> rightItems = getExpandedTreeItems(rightTree);
final ImmutableSet<TreeItem> selection = ImmutableSet.copyOf(leftTree.getSelection());
for (TreeItem leftItem : leftItems) {
final boolean selected = Iterables.any(selection, equalTo(leftItem));
IMergeViewerItem leftData = (IMergeViewerItem)leftItem.getData();
final Diff leftDiff = leftData.getDiff();
if (leftDiff != null) {
if (MergeViewerUtil.isVisibleInMergeViewer(leftDiff, getDifferenceGroupProvider(),
getDifferenceFilterPredicate())
&& !MergeViewerUtil.isMarkAsMerged(leftDiff, leftData, getCompareConfiguration())) {
TreeItem rightItem = findRightTreeItemFromLeftDiff(rightItems, leftDiff, leftData);
if (rightItem != null) {
final Color strokeColor = getCompareColor().getStrokeColor(leftDiff, isThreeWay(),
false, selected);
g.setForeground(strokeColor);
drawCenterLine(g, leftClientArea, rightClientArea, leftItem, rightItem);
}
}
}
}
}
private List<TreeItem> getExpandedTreeItems(Tree tree) {
return getExpandedTreeItems(tree.getItems());
}
/**
* @param items
* @return
*/
private List<TreeItem> getExpandedTreeItems(TreeItem[] items) {
List<TreeItem> ret = newArrayList();
for (TreeItem item : items) {
ret.add(item);
if (!item.getExpanded()) {
continue;
}
ret.addAll(getExpandedTreeItems(item.getItems()));
}
return ret;
}
private void drawCenterLine(GC g, Rectangle leftClientArea, Rectangle rightClientArea, TreeItem leftItem,
TreeItem rightItem) {
Control control = getCenterControl();
Point from = new Point(0, 0);
Point to = new Point(0, 0);
Rectangle leftBounds = leftItem.getBounds();
Rectangle rightBounds = rightItem.getBounds();
from.y = leftBounds.y + (leftBounds.height / 2) - leftClientArea.y;
if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$
from.y -= 1;
} else if ("win32".equals(SWT.getPlatform())) { //$NON-NLS-1$
from.y += 1;
}
to.x = control.getBounds().width;
to.y = rightBounds.y + (rightBounds.height / 2) - rightClientArea.y;
if ("gtk".equals(SWT.getPlatform())) { //$NON-NLS-1$
to.y -= 1;
} else if ("win32".equals(SWT.getPlatform())) { //$NON-NLS-1$
to.y += 1;
}
int[] points = getCenterCurvePoints(from, to);
for (int i = 1; i < points.length; i++) {
g.drawLine(from.x + i - 1, points[i - 1], i, points[i]);
}
}
private TreeItem findRightTreeItemFromLeftDiff(List<TreeItem> rightItems, Diff leftDiff,
IMergeViewerItem leftData) {
TreeItem ret = null;
for (TreeItem rightItem : rightItems) {
IMergeViewerItem rightData = (IMergeViewerItem)rightItem.getData();
final Diff rightDiff = rightData.getDiff();
if (leftDiff == rightDiff) {
return rightItem;
} else if (rightData.getAncestor() == leftData.getAncestor()
&& rightData.getRight() == leftData.getRight()
&& rightData.getLeft() == leftData.getLeft()) {
ret = rightItem;
}
}
return ret;
}
private int[] getCenterCurvePoints(Point from, Point to) {
int startx = from.x;
int starty = from.y;
int endx = to.x;
int endy = to.y;
if (fBasicCenterCurve == null) {
buildBaseCenterCurve(endx - startx);
}
double height = endy - starty;
height = height / 2;
int width = endx - startx;
int[] points = new int[width];
for (int i = 0; i < width; i++) {
points[i] = (int)(-height * fBasicCenterCurve[i] + height + starty);
}
return points;
}
private void buildBaseCenterCurve(int w) {
double width = w;
fBasicCenterCurve = new double[getCenterWidth()];
for (int i = 0; i < getCenterWidth(); i++) {
double r = i / width;
fBasicCenterCurve[i] = Math.cos(Math.PI * r);
}
}
/**
* @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
*/
protected final class ExpandCollapseListener implements Listener {
/**
*
*/
private final TreeMergeViewer mergeTreeViewer;
private boolean expanded;
/**
* @param mergeTreeViewer
*/
public ExpandCollapseListener(TreeMergeViewer mergeTreeViewer, boolean expanded) {
this.mergeTreeViewer = mergeTreeViewer;
this.expanded = expanded;
}
public void handleEvent(Event e) {
Object data = e.item.getData();
final List<Object> toBeExpanded = newArrayList();
toBeExpanded.add(data);
final Object parent;
if (getLeftMergeViewer() == mergeTreeViewer) {
parent = ((IMergeViewerItem)data).getLeft();
} else if (getRightMergeViewer() == mergeTreeViewer) {
parent = ((IMergeViewerItem)data).getRight();
} else {
parent = ((IMergeViewerItem)data).getAncestor();
}
Comparison comparison = getCompareConfiguration().getComparison();
if (parent instanceof NotLoadedFragmentMatch) {
IMergeViewerItem.Container left = new MergeViewerItem.Container(comparison, null,
(Match)parent, MergeViewerSide.LEFT, fAdapterFactory);
IMergeViewerItem.Container right = new MergeViewerItem.Container(comparison, null,
(Match)parent, MergeViewerSide.RIGHT, fAdapterFactory);
toBeExpanded.add(left);
toBeExpanded.add(right);
} else if (parent instanceof EObject) {
Match match = comparison.getMatch((EObject)parent);
if (match != null) {
// We get all move differencies in order to detect the move of an element with an original
// position outside the actual match of the diff. We have to do that since move
// differencies are registered only under one container (left or right depending on the
// situation)
for (Diff referenceChange : filter(comparison.getDifferences(),
and(instanceOf(ReferenceChange.class), ofKind(DifferenceKind.MOVE)))) {
Match matchOfValue = comparison
.getMatch(((ReferenceChange)referenceChange).getValue());
if (matchOfValue != null) {
Match leftContainerMatch = getContainerMatch(comparison, matchOfValue.getLeft());
Match rightContainerMatch = getContainerMatch(comparison,
matchOfValue.getRight());
Match originContainerMatch = getContainerMatch(comparison,
matchOfValue.getOrigin());
// if one of the container match is equal to the diff match, then the move have is
// origin or destination in the eContainer of the diff. We have to expend the
// eContainer of the diff too
if (leftContainerMatch == match || rightContainerMatch == match
|| originContainerMatch == match) {
if (leftContainerMatch != null && leftContainerMatch != match) {
IMergeViewerItem.Container container = new MergeViewerItem.Container(
comparison, null, leftContainerMatch, MergeViewerSide.LEFT,
fAdapterFactory);
toBeExpanded.add(container);
}
if (rightContainerMatch != null && rightContainerMatch != match) {
IMergeViewerItem.Container container = new MergeViewerItem.Container(
comparison, null, rightContainerMatch, MergeViewerSide.RIGHT,
fAdapterFactory);
toBeExpanded.add(container);
}
if (originContainerMatch != null && originContainerMatch != match) {
IMergeViewerItem.Container container = new MergeViewerItem.Container(
comparison, null, originContainerMatch, MergeViewerSide.ANCESTOR,
fAdapterFactory);
toBeExpanded.add(container);
}
}
}
}
}
}
try {
if (fSyncExpandedState.compareAndSet(false, true)) {
for (Object object : toBeExpanded) {
getLeftMergeViewer().setExpandedState(object, expanded);
getRightMergeViewer().setExpandedState(object, expanded);
getAncestorMergeViewer().setExpandedState(object, expanded);
}
}
} finally {
getCenterControl().redraw();
fSyncExpandedState.set(false);
}
}
}
/**
* Return the eContainer match of the given eObject.
*
* @param comparison
* The actual comparison
* @param value
* The element of which we want the container match
* @return the match if found, null otherwise
*/
private Match getContainerMatch(Comparison comparison, EObject value) {
EObject eContainer; // XXX: use itemProvider.getParent().
if (value != null) {
eContainer = value.eContainer();
return comparison.getMatch(eContainer);
}
return null;
}
}