org.eclipse.emf.compare.ui.viewer.content.part.diff.ModelContentMergeDiffTab.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.ui.viewer.content.part.diff.ModelContentMergeDiffTab.java

Source

/*******************************************************************************
 * Copyright (c) 2006, 2011 Obeo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.compare.ui.viewer.content.part.diff;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.compare.diff.metamodel.ComparisonSnapshot;
import org.eclipse.emf.compare.diff.metamodel.ConflictingDiffElement;
import org.eclipse.emf.compare.diff.metamodel.DiffElement;
import org.eclipse.emf.compare.diff.metamodel.DifferenceKind;
import org.eclipse.emf.compare.diff.metamodel.ModelElementChangeLeftTarget;
import org.eclipse.emf.compare.diff.metamodel.ModelElementChangeRightTarget;
import org.eclipse.emf.compare.diff.metamodel.ResourceDependencyChange;
import org.eclipse.emf.compare.diff.metamodel.util.DiffAdapterFactory;
import org.eclipse.emf.compare.ui.TypedElementWrapper;
import org.eclipse.emf.compare.ui.util.EMFCompareConstants;
import org.eclipse.emf.compare.ui.util.EMFCompareEObjectUtils;
import org.eclipse.emf.compare.ui.viewer.content.ModelContentMergeViewer;
import org.eclipse.emf.compare.ui.viewer.content.part.IModelContentMergeViewerTab;
import org.eclipse.emf.compare.ui.viewer.content.part.ModelContentMergeTabFolder;
import org.eclipse.emf.compare.ui.viewer.content.part.ModelContentMergeTabItem;
import org.eclipse.emf.compare.util.AdapterUtils;
import org.eclipse.emf.compare.util.EMFCompareMap;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl.ContainmentUpdatingFeatureMapEntry;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.provider.DelegatingWrapperItemProvider;
import org.eclipse.emf.edit.provider.FeatureMapEntryWrapperItemProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

/**
 * Represents the tree view under a {@link ModelContentMergeTabFolder}'s diff tab.
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ModelContentMergeDiffTab extends TreeViewer implements IModelContentMergeViewerTab {
    /** Tells the tree viewer it needs to be redrawn. */
    protected boolean needsRedraw = true;

    /** <code>int</code> representing this viewer part side. */
    protected final int partSide;

    /** Caches visible elements. */
    protected final List<Item> visibleItems = new ArrayList<Item>();

    /**
     * Keeps a reference to the containing tab folder.
     * 
     * @since 1.1
     */
    protected final ModelContentMergeTabFolder parent;

    /** Maps TreeItems to their TreePath. */
    private final Map<Item, TreePath> cachedTreePath = new EMFCompareMap<Item, TreePath>();

    /** Maps DiffElements to the TreeItems' data. */
    private final Map<Object, DiffElement> dataToDiff = new EMFCompareMap<Object, DiffElement>();

    /**
     * Maps a TreeItem to its data. We're compelled to map the Tree like this because of EMF's FeatureMapEntry
     * which default TreeViewers cannot handle accurately.
     */
    private final Map<Object, TreeItem> dataToTreeItem = new EMFCompareMap<Object, TreeItem>();

    /** Maps a Diffelement to its UI item. */
    private final Map<DiffElement, ModelContentMergeTabItem> diffToUIItem = new EMFCompareMap<DiffElement, ModelContentMergeTabItem>();

    /**
     * Creates a tree viewer under the given parent control.
     * 
     * @param parentComposite
     *            The parent {@link Composite} for this tree viewer.
     * @param side
     *            Side of this viewer part.
     * @param parentFolder
     *            Parent folder of this tab.
     */
    public ModelContentMergeDiffTab(Composite parentComposite, int side, ModelContentMergeTabFolder parentFolder) {
        super(new Tree(parentComposite, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.DOUBLE_BUFFERED));
        partSide = side;
        parent = parentFolder;

        setUseHashlookup(true);
        setContentProvider(createContentProvider());
        setLabelProvider(createLabelProvider());
        getTree().addPaintListener(new TreePaintListener());

        // The following listeners will be used to invalidate the cache of
        // visible elements
        getTree().addTreeListener(new TreeListener() {
            public void treeCollapsed(TreeEvent e) {
                needsRedraw = true;
            }

            public void treeExpanded(TreeEvent e) {
                needsRedraw = true;
            }
        });
        getTree().getVerticalBar().addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                needsRedraw = true;
                redraw();
            }
        });
    }

    /**
     * Utility function to create a new label provider.
     * 
     * @return the new label provider instance.
     */
    private AdapterFactoryLabelProvider createLabelProvider() {
        return new AdapterFactoryLabelProvider(AdapterUtils.getAdapterFactory());
    }

    /**
     * Utility function to create a new content provider.
     * 
     * @return the new content provider instance.
     */
    private ModelContentMergeDiffTabContentProvider createContentProvider() {
        return new ModelContentMergeDiffTabContentProvider(AdapterUtils.getAdapterFactory());
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ui.viewer.content.part.IModelContentMergeViewerTab#dispose()
     */
    public void dispose() {
        clearCaches();
        cachedTreePath.clear();
        getTree().dispose();
    }

    /**
     * Returns a {@link List} of the selected elements.
     * 
     * @return {@link List} of the selected elements.
     */
    public List<TreeItem> getSelectedElements() {
        return Arrays.asList(getTree().getSelection());
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ui.viewer.content.part.IModelContentMergeViewerTab#getUIItem(org.eclipse.emf.ecore.EObject)
     */
    public ModelContentMergeTabItem getUIItem(EObject data) {
        ModelContentMergeTabItem result = null;
        // If the diff is hidden by another (diff extension), the item won't be
        // returned
        // Same goes for diffs that couldn't be matched
        final DiffElement diff = dataToDiff.get(data);
        if (diff != null && DiffAdapterFactory.shouldBeHidden(diff))
            return result;

        final ModelContentMergeTabItem item = diffToUIItem.get(diff);

        if (item != null) {
            // This is a match, we'll search the first visible element in its
            // tree path
            final Item treeItem = getVisibleAncestorOf(item.getVisibleItem());
            if (treeItem == item.getVisibleItem()) {
                // This is actually a perfect match : the item is visible in the
                // tree and it is the actual
                // item displayed by the diff
                result = item;
            } else {
                // The item corresponding to the diff is not visible. We'll wrap
                // its
                // first visible ancestor.
                result = new ModelContentMergeTabItem(diff, item.getActualItem(), treeItem, item.getCurveColor());
            }
            computeUIInfoFor(result);
        }

        return result;
    }

    /**
     * Returns a list of all the {@link Tree tree}'s visible elements.
     * 
     * @return List containing all the {@link Tree tree}'s visible elements.
     */
    public List<ModelContentMergeTabItem> getVisibleElements() {
        final List<ModelContentMergeTabItem> result = new ArrayList<ModelContentMergeTabItem>();
        // This will happen if the user has "merged all"
        if (parent.getDiffAsList().size() == 0)
            return result;

        final List<Item> items = getVisibleTreeItems();
        final Iterator<DiffElement> differences = diffToUIItem.keySet().iterator();
        while (differences.hasNext()) {
            final DiffElement diff = differences.next();
            final ModelContentMergeTabItem nextItem = diffToUIItem.get(diff);

            // seeks for a perfect match (item is actually visible)
            Item visibleMatch = null;
            for (final Item visible : items) {
                if (nextItem.getActualItem() == visible) {
                    visibleMatch = visible;
                    break;
                }
            }
            // or a regular match (ancestor of the item is visible and collapsed)
            if (visibleMatch == null) {
                for (final Item visible : items) {
                    if (!((TreeItem) visible).getExpanded() && getTreePathFromItem(nextItem.getActualItem())
                            .startsWith(getTreePathFromItem(visible), null)) {
                        visibleMatch = visible;
                        break;
                    }
                }
            }
            if (visibleMatch != null) {
                nextItem.setVisibleItem(visibleMatch);
                computeUIInfoFor(nextItem);
                result.add(nextItem);
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ui.viewer.content.part.IModelContentMergeViewerTab#redraw()
     */
    public void redraw() {
        clearCaches();
        getTree().redraw();
        setupCaches();
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.jface.viewers.ColumnViewer#refresh(java.lang.Object, boolean)
     */
    @Override
    public void refresh(Object element, boolean updateLabels) {
        clearCaches();
        super.refresh(element, updateLabels);
        setupCaches();
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.ui.viewer.content.part.IModelContentMergeViewerTab#setReflectiveInput(java.lang.Object)
     */
    @SuppressWarnings("unchecked")
    public void setReflectiveInput(Object object) {
        // We *need* to invalidate the cache here since setInput() would try to
        // use it otherwise
        clearCaches();

        // setLabelProvider(createLabelProvider()); // already set in constructor
        if (object instanceof EObject) {
            setInput(((EObject) object).eResource());
        } else {
            // may be invoked with a resourceSet, a list of resources, or a single resource
            assert object instanceof Resource || object instanceof List;
            if (object instanceof List) {
                for (Object item : (List) object) {
                    assert item instanceof Resource;
                }
            }
            setInput(object);
        }

        setupCaches();
        needsRedraw = true;
    }

    /**
     * Ensures the first element of the given list of items is visible in the tree, and sets the tree's
     * selection to this list.
     * 
     * @param items
     *            Items to make visible.
     */
    public void showItems(List<DiffElement> items) {
        final List<EObject> datas = new ArrayList<EObject>();
        for (int i = 0; i < items.size(); i++) {
            if (partSide == EMFCompareConstants.ANCESTOR && items.get(i) instanceof ConflictingDiffElement) {
                datas.add(((ConflictingDiffElement) items.get(i)).getOriginElement());
            } else if (partSide == EMFCompareConstants.LEFT) {
                datas.add(EMFCompareEObjectUtils.getLeftElement(items.get(i)));
            } else {
                datas.add(EMFCompareEObjectUtils.getRightElement(items.get(i)));
            }
        }

        // filter null values
        final Iterator<EObject> iterator = datas.iterator();
        while (iterator.hasNext()) {
            if (iterator.next() == null) {
                iterator.remove();
            }
        }

        // expand those being selected first
        for (EObject data : datas) {
            reveal(data);
        }
        setSelection(new StructuredSelection(datas), true);
        needsRedraw = true;
        redraw();
    }

    /**
     * Invalidates all caches of the tab.
     */
    protected final void clearCaches() {
        dataToDiff.clear();
        diffToUIItem.clear();
        dataToTreeItem.clear();
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.jface.viewers.AbstractTreeViewer#doFindItem(java.lang.Object)
     */
    @Override
    protected Widget doFindInputItem(Object element) {
        final Widget res = dataToTreeItem.get(element);
        if (res != null && !res.isDisposed())
            return res;
        else if (res != null) {
            // mapped items are disposed
            clearCaches();
            setupCaches();
            // won't call this recursively since it could eventually lead to
            // stack overflows
            dataToTreeItem.get(element);
        }
        return super.doFindInputItem(element);
    }

    /**
     * Overriden to cache the result.
     * 
     * @param item
     *            The item we seek the TreePath of.
     * @return {@link TreePath} of the given item.
     */
    @Override
    protected TreePath getTreePathFromItem(Item item) {
        TreePath result = cachedTreePath.get(item);
        if (result == null) {
            result = super.getTreePathFromItem(item);
            cachedTreePath.put(item, result);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.jface.viewers.AbstractTreeViewer#inputChanged(java.lang.Object, java.lang.Object)
     */
    @Override
    protected void inputChanged(Object input, Object oldInput) {
        // preserve expansion state
        final TreePath[] expandedTreePaths = getExpandedTreePaths();
        super.inputChanged(input, oldInput);
        setExpandedTreePaths(expandedTreePaths);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(List, boolean)
     */
    @Override
    @SuppressWarnings("unchecked")
    protected void setSelectionToWidget(List l, boolean reveal) {
        // Will expand the treeItem to one level below the current if needed
        final List<TreeItem> newSelection = new ArrayList<TreeItem>();
        for (final Object data : l) {
            Widget widget = null;
            if (data instanceof EObject) {
                widget = findItemForEObject((EObject) data);
            } else if (data instanceof TreePath) {
                final Object target = ((TreePath) data).getLastSegment();
                if (target instanceof EObject) {
                    widget = findItemForEObject((EObject) target);
                } else {
                    widget = findItem(target);
                }
            } else if (data != null) {
                widget = findItem(data);
            }
            if (widget instanceof TreeItem) {
                newSelection.add((TreeItem) widget);
                if (((TreeItem) widget).getExpanded() && getChildren(widget).length > 0) {
                    expandToLevel(data, 1);
                }
            }
        }
        if (newSelection.size() > 0) {
            setSelection(newSelection);
        } else {
            super.setSelectionToWidget(l, reveal);
        }
    }

    /**
     * Sets up all necessary caches for navigation through the differences shown by this tab. This must be
     * called at input setting.
     */
    protected final void setupCaches() {
        mapTreeItems();
        mapDifferences();
        mapTreeItemsToUI();
    }

    /**
     * This will compute the necessary GUI information for the given {@link ModelContentMergeTabItem} given
     * the diff it represents.
     * 
     * @param item
     *            The item which UI information is to be set.
     */
    private void computeUIInfoFor(ModelContentMergeTabItem item) {
        final int curveY;
        final DiffElement diff = item.getDiff();
        if (item.getActualItem() == item.getVisibleItem()) {
            if (partSide == EMFCompareConstants.LEFT && diff instanceof ModelElementChangeRightTarget) {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height;
            } else if (partSide == EMFCompareConstants.RIGHT && diff instanceof ModelElementChangeLeftTarget) {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height;
            } else {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height / 2;
            }
            item.setCurveY(curveY);
        } else {
            if (partSide == EMFCompareConstants.LEFT && diff instanceof ModelElementChangeRightTarget) {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height;
            } else if (partSide == EMFCompareConstants.RIGHT && diff instanceof ModelElementChangeLeftTarget) {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height;
            } else {
                curveY = ((TreeItem) item.getVisibleItem()).getBounds().y
                        + ((TreeItem) item.getVisibleItem()).getBounds().height / 2;
            }
            item.setCurveY(curveY);
        }
        if (getSelectedElements().contains(item.getActualItem())) {
            item.setCurveSize(2);
        } else {
            item.setCurveSize(1);
        }

        final Scrollable scrollable = (Scrollable) getControl();
        int offset = scrollable.getBounds().y + scrollable.getClientArea().height
                - (scrollable.getClientArea().y + scrollable.getBounds().height);

        // if horizontal scrollbar is visible, compensate this as well
        if (scrollable.getClientArea().width < ((TreeItem) item.getActualItem()).getBounds().width) {
            offset += scrollable.getHorizontalBar().getSize().y;
        }
        item.setVerticalOffset(offset);
    }

    /**
     * Returns the Item corresponding to the given EObject or its container if none can be found.
     * 
     * @param element
     *            {@link EObject} to seek in the tree.
     * @return The Item corresponding to the given EObject.
     */
    private Widget findItemForEObject(EObject element) {
        Widget result = super.findItem(element);
        if (result == null) {
            if (element.eContainer() != null) {
                result = findItemForEObject(element.eContainer());
            } else if (getTree().getItemCount() > 0) {
                result = getTree().getItem(0);
            }
        }
        if (result instanceof Tree && getTree().getItemCount() > 0) {
            result = getTree().getItem(0);
        }
        return result;
    }

    /**
     * Returns a visible ancestor of the given item. Will return the first container that is not expanded.
     * 
     * @param item
     *            item we look for a visible ancestor of.
     * @return The first container of <tt>item</tt> that is not expanded, the element itself if its container
     *         is expanded.
     */
    private Item getVisibleAncestorOf(Item item) {
        Item result = item;
        final TreePath path = getTreePathFromItem(item);
        if (path.getSegmentCount() > 1) {
            for (int i = 0; i < path.getSegmentCount(); i++) {
                final TreeItem ancestor = (TreeItem) findItem(path.getSegment(i));
                if (!ancestor.getExpanded()) {
                    result = ancestor;
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Returns a list of all the {@link Tree tree}'s visible elements.
     * 
     * @return List containing all the {@link Tree tree}'s visible elements.
     */
    private List<Item> getVisibleTreeItems() {
        if (needsRedraw) {
            needsRedraw = false;
            visibleItems.clear();
            final TreeItem topItem = getTree().getTopItem();
            if (topItem != null) {
                // We won't go further down than the visible height of the tree,
                // yet we will take the whole width into account when searching
                // for elements.
                final int treeHeight = getTree().getClientArea().height;
                final int treeWidth = getTree().getBounds().width;
                final int itemHeight = topItem.getBounds().height;
                final int itemWidth = topItem.getBounds().width;

                visibleItems.add(topItem);

                // The loop will start at the element directly following the
                // "top" one,
                // And we'll go down to one item more than there are in the tree
                final int loopStart = topItem.getBounds().y + itemHeight + itemHeight / 2;
                final int loopEnd = treeHeight;
                // We can safely assume all items are of the same height
                for (int i = loopStart; i <= loopEnd; i += itemHeight) {
                    TreeItem next = null;
                    // we'll try and seek on all the line, thus increasing x on
                    // each iteration
                    // For this, we will assume items cannot be more than eight
                    // time smaller than the "top"
                    for (int xCoord = topItem.getBounds().x; xCoord < treeWidth; xCoord += itemWidth >> 3) {
                        next = getTree().getItem(new Point(xCoord, i));
                        // We found the item, it is unnecessary to probe any
                        // further on the line
                        if (next != null) {
                            break;
                        }
                    }
                    if (next != null) {
                        visibleItems.add(next);
                    } else {
                        // We did not found an item, it is thus useless to probe
                        // any further down
                        break;
                    }
                }
            }
        }
        return visibleItems;
    }

    /**
     * This will seek out the first value of the given Object that is not instance of either
     * FeatureMapEntryWrapperItemProvider or DelegatingWrapperItemProvider.
     * 
     * @param data
     *            The data we seek the actual value of.
     * @return Actual value of the given TreeItem's data.
     */
    private Object internalFindActualData(Object data) {
        Object actualData = data;
        if (data instanceof FeatureMapEntryWrapperItemProvider) {
            actualData = internalFindActualData(((FeatureMapEntryWrapperItemProvider) data).getValue());
        } else if (data instanceof DelegatingWrapperItemProvider) {
            actualData = internalFindActualData(((DelegatingWrapperItemProvider) data).getValue());
        } else if (data instanceof ContainmentUpdatingFeatureMapEntry) {
            actualData = ((ContainmentUpdatingFeatureMapEntry) data).getValue();
        }
        return actualData;
    }

    /**
     * Maps the children of the given <tt>item</tt>.
     * 
     * @param item
     *            TreeItem which children are to be mapped.
     */
    private void internalMapTreeItems(TreeItem item) {
        for (final TreeItem child : item.getItems()) {
            dataToTreeItem.put(internalFindActualData(child.getData()), child);
            internalMapTreeItems(child);
        }
    }

    /**
     * Maps the input's differences if any.
     */
    private void mapDifferences() {
        dataToDiff.clear();
        final Iterator<DiffElement> diffIterator = parent.getDiffAsList().iterator();
        while (diffIterator.hasNext()) {
            final DiffElement diff = diffIterator.next();
            final EObject data;
            if (partSide == EMFCompareConstants.ANCESTOR && diff instanceof ConflictingDiffElement) {
                data = ((ConflictingDiffElement) diff).getOriginElement();
            } else if (partSide == EMFCompareConstants.LEFT) {
                data = EMFCompareEObjectUtils.getLeftElement(diff);
            } else {
                data = EMFCompareEObjectUtils.getRightElement(diff);
            }
            if (data != null) {
                dataToDiff.put(data, diff);
            } else {
                // TODO for now, we're using the first item's data, we should
                // look for the matchedElement
                dataToDiff.put(getTree().getItems()[0].getData(), diff);
            }
        }
    }

    /**
     * This will map this TreeViewer's TreeItems to their data. We need to do this in order to find the items
     * associated to {@link org.eclipse.emf.ecore.change.FeatureMapEntry}s since default TreeViewers cannot
     * handle such data.
     */
    private void mapTreeItems() {
        dataToTreeItem.clear();
        for (final TreeItem item : getTree().getItems()) {
            dataToTreeItem.put(internalFindActualData(item.getData()), item);
            internalMapTreeItems(item);
        }
    }

    /**
     * This will map all the TreeItems in this TreeViewer that need be taken into account when drawing diff
     * markers to a corresponding ModelContentMergeTabItem. This will allow us to browse everything once and
     * for all.
     */
    private void mapTreeItemsToUI() {
        diffToUIItem.clear();
        for (final Object key : dataToDiff.keySet()) {
            final DiffElement diff = dataToDiff.get(key);
            // Defines the TreeItem corresponding to this difference
            Object data;
            if (diff instanceof ResourceDependencyChange) {
                data = ((ResourceDependencyChange) diff).getRoots().get(0).eResource();
            } else if (partSide == EMFCompareConstants.ANCESTOR && diff instanceof ConflictingDiffElement) {
                data = ((ConflictingDiffElement) diff).getOriginElement();
            } else if (partSide == EMFCompareConstants.LEFT) {
                data = EMFCompareEObjectUtils.getLeftElement(diff);
            } else {
                data = EMFCompareEObjectUtils.getRightElement(diff);
            }
            if (data == null) {
                // TODO for now, we're using the first item's data, we should
                // look for the matchedElement
                data = getTree().getItems()[0].getData();
            }
            final Widget actualWidget = findItem(data);
            if (actualWidget == null) {
                continue;
            }
            if (!(actualWidget instanceof Item)) {
                continue;
            }
            final Item actualItem = (Item) actualWidget;

            Item visibleItem = null;
            if (partSide == EMFCompareConstants.LEFT && diff instanceof ModelElementChangeRightTarget
                    && ((ModelElementChangeRightTarget) diff).getRightElement().eContainer() != null) {
                // in the case of a modelElementChangeRightTarget, we know we
                // can't find
                // the element itself, we'll then search for one with the same
                // index
                final EObject right = ((ModelElementChangeRightTarget) diff).getRightElement();
                final EObject left = ((ModelElementChangeRightTarget) diff).getLeftParent();
                final int rightIndex = right.eContainer().eContents().indexOf(right);
                if (left != null) {
                    // Ensures we cannot trigger ArrayOutOfBounds exeptions
                    final int leftIndex = Math.min(rightIndex - 1, left.eContents().size() - 1);
                    if (left.eContents().size() > 0) {
                        visibleItem = (Item) findItem(left.eContents().get(leftIndex));
                    }
                }
            } else if (partSide == EMFCompareConstants.RIGHT && diff instanceof ModelElementChangeLeftTarget
                    && ((ModelElementChangeLeftTarget) diff).getLeftElement().eContainer() != null) {
                // in the case of a modelElementChangeLeftTarget, we know we
                // can't find
                // the element itself, we'll then search for one with the same
                // index
                final EObject right = ((ModelElementChangeLeftTarget) diff).getRightParent();
                final EObject left = ((ModelElementChangeLeftTarget) diff).getLeftElement();
                final int leftIndex = left.eContainer().eContents().indexOf(left);
                if (right != null) {
                    // Ensures we cannot trigger ArrayOutOfBounds exeptions
                    final int rightIndex = Math.max(0, Math.min(leftIndex - 1, right.eContents().size() - 1));
                    if (right.eContents().size() > 0) {
                        visibleItem = (Item) findItem(right.eContents().get(rightIndex));
                    }
                }
            }

            // and now the color which should be used for this kind of
            // differences
            final String color;
            if (diff.isConflicting()) {
                color = EMFCompareConstants.PREFERENCES_KEY_CONFLICTING_COLOR;
            } else if (diff.getKind() == DifferenceKind.ADDITION) {
                color = EMFCompareConstants.PREFERENCES_KEY_ADDED_COLOR;
            } else if (diff.getKind() == DifferenceKind.DELETION) {
                color = EMFCompareConstants.PREFERENCES_KEY_REMOVED_COLOR;
            } else {
                color = EMFCompareConstants.PREFERENCES_KEY_CHANGED_COLOR;
            }

            final ModelContentMergeTabItem wrappedItem;
            if (visibleItem != null) {
                wrappedItem = new ModelContentMergeTabItem(diff, actualItem, visibleItem, color);
            } else {
                wrappedItem = new ModelContentMergeTabItem(diff, actualItem, color);
            }
            diffToUIItem.put(diff, wrappedItem);
        }
    }

    /**
     * This implementation of {@link PaintListener} handles the drawing of blocks around modified members in
     * the tree tab.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    class TreePaintListener implements PaintListener {
        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events.PaintEvent)
         */
        public void paintControl(PaintEvent event) {
            if (ModelContentMergeViewer.shouldDrawDiffMarkers()) {
                for (final ModelContentMergeTabItem item : getVisibleElements()) {
                    drawRectangle(event.gc, item);
                }
            }
        }

        /**
         * Handles the drawing itself.
         * 
         * @param gc
         *            {@link GC} on which paint operations will take place.
         * @param item
         *            Item that need to be circled and connected to the center part.
         */
        private void drawRectangle(GC gc, ModelContentMergeTabItem item) {
            final Rectangle treeBounds = getTree().getClientArea();
            final Rectangle treeItemBounds = ((TreeItem) item.getVisibleItem()).getBounds();

            // Defines the circling Color
            final RGB color = ModelContentMergeViewer.getColor(item.getCurveColor());

            // We add a margin before the rectangle to circle the "+" as well as
            // the tree line.
            final int margin = 60;

            // Defines all variables needed for drawing the rectangle.
            final int rectangleX = treeItemBounds.x - margin;
            final int rectangleY = treeItemBounds.y;
            final int rectangleWidth = treeItemBounds.width + margin;
            final int rectangleHeight = treeItemBounds.height - 1;
            final int rectangleArcWidth = 5;
            final int rectangleArcHeight = 5;

            // Performs the actual drawing
            gc.setLineWidth(item.getCurveSize());
            gc.setForeground(new Color(getTree().getDisplay(), color));
            if (partSide == EMFCompareConstants.LEFT) {
                if (item.getCurveY() != treeItemBounds.y + treeItemBounds.height / 2) {
                    gc.setLineStyle(SWT.LINE_SOLID);
                    gc.drawLine(rectangleX, item.getCurveY(), treeBounds.width + treeBounds.x, item.getCurveY());
                } else {
                    gc.setLineStyle(SWT.LINE_DASHDOT);
                    gc.drawRoundRectangle(rectangleX, rectangleY, rectangleWidth, rectangleHeight,
                            rectangleArcWidth, rectangleArcHeight);
                    gc.setLineStyle(SWT.LINE_SOLID);
                    gc.drawLine(rectangleX + rectangleWidth, item.getCurveY(), treeBounds.width + treeBounds.x,
                            item.getCurveY());
                }
            } else if (partSide == EMFCompareConstants.RIGHT) {
                if (item.getCurveY() != treeItemBounds.y + treeItemBounds.height / 2) {
                    gc.setLineStyle(SWT.LINE_SOLID);
                    gc.drawLine(rectangleX + rectangleWidth, item.getCurveY(), treeBounds.x, item.getCurveY());
                } else {
                    gc.setLineStyle(SWT.LINE_DASHDOT);
                    gc.drawRoundRectangle(rectangleX, rectangleY, rectangleWidth, rectangleHeight,
                            rectangleArcWidth, rectangleArcHeight);
                    gc.setLineStyle(SWT.LINE_SOLID);
                    gc.drawLine(rectangleX, item.getCurveY(), treeBounds.x, item.getCurveY());
                }
            } else {
                if (item.getCurveY() != treeItemBounds.y + treeItemBounds.height / 2) {
                    gc.setLineStyle(SWT.LINE_SOLID);
                    gc.drawLine(rectangleX + rectangleWidth, item.getCurveY(), rectangleX, item.getCurveY());
                } else {
                    gc.setLineStyle(SWT.LINE_DASHDOT);
                    gc.drawRoundRectangle(rectangleX, rectangleY, rectangleWidth, rectangleHeight,
                            rectangleArcWidth, rectangleArcHeight);
                }
            }
        }
    }

    /**
     * This implementation of an {@link AdapterFactoryContentProvider} will strip ComparisonSnapshots out of
     * the view.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    class ModelContentMergeDiffTabContentProvider extends AdapterFactoryContentProvider {
        /**
         * Default constructor. Delegates to the super implementation.
         * 
         * @param factory
         *            Factory to get labels and icons from.
         */
        public ModelContentMergeDiffTabContentProvider(AdapterFactory factory) {
            super(factory);
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getElements(java.lang.Object)
         */
        @SuppressWarnings("unchecked")
        @Override
        public Object[] getElements(Object object) {
            // overwritten to ensure contents of ResourceSets, List<Resource>, and Resource are correclty
            // returned.
            Object[] result = null;
            if (object instanceof ResourceSet) {
                final List<Resource> resources = ((ResourceSet) object).getResources();
                final List<Resource> elements = new ArrayList<Resource>(resources.size());
                for (final Resource resource : resources) {
                    if (resource.getContents().isEmpty()
                            || !(resource.getContents().get(0) instanceof ComparisonSnapshot)) {
                        elements.add(resource);
                    }
                }
                result = elements.toArray();
            } else if (object instanceof TypedElementWrapper) {
                result = new Object[] { ((EObject) object).eResource(), };
            } else if (object instanceof List) {
                // we may also display a list of resources
                result = ((List) object).toArray();
            } else if (object instanceof Resource) {
                // return contents of resource
                result = ((Resource) object).getContents().toArray();
            } else {
                result = super.getElements(object);
            }
            return result;
        }

        /**
         * {@inheritDoc}
         * 
         * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getChildren(java.lang.Object)
         */
        @Override
        public Object[] getChildren(Object object) {
            if (object instanceof Resource) {
                return ((Resource) object).getContents().toArray();
            }
            return super.getChildren(object);
        }

        /**
         *{@inheritDoc}
         * 
         * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#hasChildren(java.lang.Object)
         */
        @Override
        public boolean hasChildren(Object object) {
            if (object instanceof Resource) {
                return ((Resource) object).getContents().size() > 0;
            }
            return super.hasChildren(object);
        }
    }
}