/**
* <copyright>
*
* Copyright (c) 2010-2016 Thales Global Services S.A.S.
* 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:
* Thales Global Services S.A.S. - initial API and implementation
*
* </copyright>
*/
package org.eclipse.emf.diffmerge.ui.viewers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.diffmerge.api.IComparison;
import org.eclipse.emf.diffmerge.api.IMatch;
import org.eclipse.emf.diffmerge.api.IPureMatch;
import org.eclipse.emf.diffmerge.api.Role;
import org.eclipse.emf.diffmerge.diffdata.EMatch;
import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin;
import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin.DifferenceColorKind;
import org.eclipse.emf.diffmerge.ui.Messages;
import org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider;
import org.eclipse.emf.diffmerge.ui.util.DifferenceKind;
import org.eclipse.emf.diffmerge.ui.util.UIUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ITreePathContentProvider;
import org.eclipse.jface.viewers.ITreePathLabelProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerLabel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
/**
* A viewer which provides a representation of the model tree of a given comparison.
* Input: EMFDiffNode ; Elements: IMatch.
* @author Olivier Constant
*/
public class ComparisonTreeViewer extends TreeViewer {
/**
* Constructor
* @param parent_p a non-null composite
*/
public ComparisonTreeViewer(Composite parent_p) {
this(parent_p, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
}
/**
* Constructor
* @param parent_p a non-null composite
* @param style_p a style for the tree
*/
public ComparisonTreeViewer(Composite parent_p, int style_p) {
super(parent_p, style_p);
setContentProvider(new ContentProvider());
setLabelProvider(new LabelProvider());
ColumnViewerToolTipSupport.enableFor(this);
getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
}
/**
* @see org.eclipse.jface.viewers.ContentViewer#getContentProvider()
*/
@Override
public ITreePathContentProvider getContentProvider() {
return (ITreePathContentProvider)super.getContentProvider();
}
/**
* Return the role that drives the representation of the comparison
* @return a non-null role
*/
protected Role getDrivingRole() {
return getInput().getDrivingRole();
}
/**
* @see org.eclipse.jface.viewers.ContentViewer#getInput()
*/
@Override
public EMFDiffNode getInput() {
return (EMFDiffNode)super.getInput();
}
/**
* Return the first path whose father is the given one
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path which is not path_p
*/
protected TreePath getFirstIn(TreePath path_p) {
TreePath result = null;
Object[] children = getSortedChildren(path_p);
if (children.length > 0)
result = path_p.createChildPath(children[0]);
return result;
}
/**
* Return the last path whose father is the given one
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path which is not path_p
*/
protected TreePath getLastIn(TreePath path_p) {
TreePath result = null;
Object[] children = getSortedChildren(path_p);
if (children.length > 0) {
result = path_p;
// Go down to last child until we reach a leaf
while (children.length > 0) {
Object last = children[children.length-1];
result = result.createChildPath(last);
children = getSortedChildren(result);
}
}
return result;
}
/**
* Return the successor of the given path in depth-first search (graphical order).
* The successor of the empty path is the first path, if any.
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
protected TreePath getNextOf(TreePath path_p) {
// Check first child
TreePath result = getFirstIn(path_p);
if (result == null && path_p.getSegmentCount() > 0) {
// Try next sibling, if any
result = getNextSiblingOf(path_p);
if (result == null) {
// Go up until a next sibling is found
TreePath parentPath = path_p.getParentPath(); // not null since path_p is not empty
while (result == null && parentPath.getSegmentCount() > 0) {
result = getNextSiblingOf(parentPath);
parentPath = parentPath.getParentPath();
}
}
}
return result;
}
/**
* Return the path which is the next sibling to the given one, if any
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
protected TreePath getNextSiblingOf(TreePath path_p) {
TreePath result = null;
Object end = path_p.getLastSegment();
if (end != null) {
// Check siblings of last segment
TreePath parentPath = path_p.getParentPath();
Object[] siblingsArray = getSortedChildren(parentPath);
List<?> siblings = Arrays.asList(siblingsArray);
int nextPos = siblings.indexOf(end) + 1;
if (nextPos < siblings.size())
// Take next sibling
result = parentPath.createChildPath(siblings.get(nextPos));
}
return result;
}
/**
* Return the successor of the given path in depth-first search (graphical order)
* which has user-level differences, if any
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
public TreePath getNextUserDifference(TreePath path_p) {
TreePath result = null;
TreePath next = getNextOf(path_p);
while (result == null && next != null) {
if (getInput().getCategoryManager().representAsUserDifference(next))
result = next;
else
next = getNextOf(next);
}
return result;
}
/**
* Return a list of lists representing the possible paths to the given match.
* @param match_p a potentially null match
* @param parentsOnly_p whether the given match must be excluded from the paths
* @param coverOppositeSide_p whether paths in the non-driving side must be covered too
* @return a non-null list
*/
protected List<List<IMatch>> getPathsFor(IMatch match_p, boolean parentsOnly_p,
boolean coverOppositeSide_p) {
List<List<IMatch>> result;
EMFDiffNode input = getInput();
IComparison comparison = (input != null)? input.getActualComparison(): null;
if (match_p == null || comparison == null) {
// Cannot be computed: return the empty set
result = new ArrayList<List<IMatch>>();
} else {
// Comparison and match are well-defined
Role drivingRole = getDrivingRole();
// Must the element be represented as a root?
boolean isRoot = comparison.getContents(drivingRole).contains(match_p) ||
match_p.getUncoveredRole() == drivingRole &&
comparison.getContents(drivingRole.opposite()).contains(match_p);
if (isRoot) {
result = new ArrayList<List<IMatch>>();
result.add(new ArrayList<IMatch>());
} else {
// We only consider paths to containers on the driving side because
// the children of a move origin are not provided by the content provider
// in order to prevent infinite recursion cases due to "cyclic" moves
final boolean coverContainerOppositeSide = false;
// Get the paths of the driving container
IMatch drivingContainer = comparison.getContainerOf(match_p, drivingRole);
result = getPathsFor(drivingContainer, false, coverContainerOppositeSide);
// Consider the non-driving container if required
if (coverOppositeSide_p) {
IMatch oppositeContainer = comparison.getContainerOf(
match_p, drivingRole.opposite());
if (oppositeContainer != null && oppositeContainer != drivingContainer) {
List<List<IMatch>> oppositePaths =
getPathsFor(oppositeContainer, false, coverContainerOppositeSide);
CategoryManager categoryManager = getInput().getCategoryManager();
for (List<IMatch> oppositePath : oppositePaths) {
if (!categoryManager.representAsMoveOrigin(UIUtil.toTreePath(oppositePath)))
result.add(oppositePath);
}
}
}
}
if (!parentsOnly_p) {
for (List<IMatch> path : result) {
path.add(match_p);
}
}
}
return result;
}
/**
* Return the predecessor of the given path in depth-first search (graphical order).
* The predecessor of the empty path is the last path, if any.
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
protected TreePath getPreviousOf(TreePath path_p) {
TreePath result = null;
if (path_p.getSegmentCount() == 0) {
// Empty path: take last path
result = getLastIn(path_p);
} else {
TreePath siblingPath = getPreviousSiblingOf(path_p);
if (siblingPath != null) {
// Try last path in previous sibling
result = getLastIn(siblingPath);
if (result == null)
// None: take previous sibling
result = siblingPath;
} else {
// No previous sibling: take parent unless empty
TreePath parentPath = path_p.getParentPath();
if (parentPath.getSegmentCount() > 0)
result = parentPath;
}
}
return result;
}
/**
* Return the path which is the previous sibling to the given one, if any
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
protected TreePath getPreviousSiblingOf(TreePath path_p) {
TreePath result = null;
Object end = path_p.getLastSegment();
if (end != null) {
// Check siblings of last segment
TreePath parentPath = path_p.getParentPath();
Object[] siblingsArray = getSortedChildren(parentPath);
List<?> siblings = Arrays.asList(siblingsArray);
int prevPos = siblings.indexOf(end) - 1;
if (prevPos >= 0)
// Take previous sibling
result = parentPath.createChildPath(siblings.get(prevPos));
}
return result;
}
/**
* Return the predecessor of the given path in depth-first search (graphical order)
* which has user-level differences, if any
* @param path_p a non-null path that belongs to the tree
* @return a potentially null path
*/
public TreePath getPreviousUserDifference(TreePath path_p) {
TreePath result = null;
TreePath previous = getPreviousOf(path_p);
while (result == null && previous != null) {
if (getInput().getCategoryManager().representAsUserDifference(previous))
result = previous;
else
previous = getPreviousOf(previous);
}
return result;
}
/**
* @see org.eclipse.jface.viewers.TreeViewer#getRawChildren(java.lang.Object)
*/
@Override
protected Object[] getRawChildren(Object parent_p) {
Object[] result;
if (parent_p instanceof TreePath && ((TreePath)parent_p).getSegmentCount() == 0) {
result = getContentProvider().getElements(getInput());
} else {
result = super.getRawChildren(parent_p);
}
return result;
}
/**
* Return the resource manager for this viewer
* @return a resource manager which is non-null iff getInput() is not null
*/
protected ComparisonResourceManager getResourceManager() {
return getInput() == null? null: getInput().getResourceManager();
}
/**
* @see org.eclipse.jface.viewers.AbstractTreeViewer#getSelection()
*/
@Override
public ITreeSelection getSelection() {
return (ITreeSelection)super.getSelection();
}
/**
* @see org.eclipse.jface.viewers.AbstractTreeViewer#getSortedChildren(java.lang.Object)
*/
@Override
public Object[] getSortedChildren(Object parentElementOrTreePath_p) {
// Increase visibility of this method
return super.getSortedChildren(parentElementOrTreePath_p);
}
/**
* The content provider for this viewer.
*/
protected class ContentProvider implements ITreePathContentProvider {
/**
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public void dispose() {
// Nothing needed
}
/**
* @see org.eclipse.jface.viewers.ITreePathContentProvider#getChildren(org.eclipse.jface.viewers.TreePath)
*/
public Object[] getChildren(TreePath parentPath_p) {
IMatch end = (IMatch)parentPath_p.getLastSegment();
List<IMatch> result;
if (end == null)
result = getInput().getActualComparison().getContents();
else if (getInput().getCategoryManager().representAsMoveOrigin(parentPath_p))
result = Collections.emptyList();
else
result = getInput().getActualComparison().getContentsOf(end);
return result.toArray();
}
/**
* @see org.eclipse.jface.viewers.ITreeContentProvider#getElements(java.lang.Object)
*/
public Object[] getElements(Object inputElement_p) {
EMFDiffNode input = (EMFDiffNode)inputElement_p;
List<IMatch> result = input.getActualComparison().getContents();
return result.toArray();
}
/**
* @see org.eclipse.jface.viewers.ITreePathContentProvider#getParents(java.lang.Object)
*/
public TreePath[] getParents(Object element_p) {
List<List<IMatch>> resultAsList = getPathsFor((IMatch)element_p, true, true);
return UIUtil.toTreePaths(resultAsList);
}
/**
* @see org.eclipse.jface.viewers.ITreePathContentProvider#hasChildren(org.eclipse.jface.viewers.TreePath)
*/
public boolean hasChildren(TreePath path_p) {
return getSortedChildren(path_p).length > 0;
}
/**
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
public void inputChanged(Viewer viewer_p, Object oldInput_p, Object newInput_p) {
// Nothing needed
}
}
/**
* The label provider for this viewer.
*/
protected class LabelProvider extends DelegatingLabelProvider implements ITreePathLabelProvider {
/**
* Return the element to represent for the given match
* @param match_p a non-null match
* @return a non-null element
*/
private EObject getElementToRepresent(IMatch match_p) {
EObject result;
Role drivingRole = getDrivingRole();
if (match_p.getUncoveredRole() == drivingRole)
result = match_p.get(drivingRole.opposite());
else
result = match_p.get(drivingRole);
return result;
}
/**
* @see org.eclipse.jface.viewers.IFontProvider#getFont(java.lang.Object)
*/
@Override
public Font getFont(Object element_p) {
IMatch match = (IMatch)element_p;
Font result = getControl().getFont();
if (getInput().getCategoryManager().representAsUserDifference(match))
result = UIUtil.getBold(result);
return result;
}
/**
* @see org.eclipse.jface.viewers.IColorProvider#getForeground(java.lang.Object)
*/
@Override
public Color getForeground(Object element_p) {
EMatch match = (EMatch)element_p;
DifferenceKind kind = getInput().getCategoryManager().getDifferenceKind(match);
DifferenceColorKind colorKind =
EMFDiffMergeUIPlugin.getDefault().getDifferenceColorKind(kind);
return getInput().getDifferenceColor(colorKind);
}
/**
* @see org.eclipse.jface.viewers.LabelProvider#getImage(java.lang.Object)
*/
@Override
public Image getImage(Object element_p) {
IMatch match = (IMatch)element_p;
Image result = getDelegate().getImage(getElementToRepresent(match));
if (result != null && getInput().usesCustomIcons()) {
DifferenceKind kind = getInput().getCategoryManager().getDifferenceKind(match);
result = getResourceManager().adaptImage(result, kind);
}
return result;
}
/**
* Return a font for the given tree path
* @param path_p a non-null path
* @return a potentially null font
*/
private Font getPathFont(TreePath path_p) {
Font result = getControl().getFont();
Object last = path_p.getLastSegment();
if (last != null && !getInput().getCategoryManager().representAsMoveOrigin(path_p))
result = getFont(last);
return result;
}
/**
* Return an image for the given tree path
* @param path_p a non-null path
* @return a potentially null image
*/
private Image getPathImage(TreePath path_p) {
Image result = null;
IMatch last = (IMatch)path_p.getLastSegment();
if (last != null) {
result = getDelegate().getImage(getElementToRepresent(last));
if (getInput().getCategoryManager().representAsMoveOrigin(path_p) && result != null)
result = getResourceManager().getDisabledVersion(result);
if (result != null && getInput().usesCustomIcons()) {
DifferenceKind kind = getInput().getCategoryManager().getDifferenceKind(last);
result = getResourceManager().adaptImage(result, kind);
}
}
return result;
}
/**
* Return a label for the given tree path
* @param path_p a non-null path
* @return a potentially null string
*/
private String getPathText(TreePath path_p) {
String result = null;
IMatch last = (IMatch)path_p.getLastSegment();
if (last != null)
result = getText(last);
return result;
}
/**
* @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
*/
@Override
public String getText(Object element_p) {
EMatch match = (EMatch)element_p;
String result = getDelegate().getText(getElementToRepresent(match));
if (getInput().usesCustomLabels()) {
DifferenceKind kind = getInput().getCategoryManager().getDifferenceKind(match);
String prefix = EMFDiffMergeUIPlugin.getDefault().getDifferencePrefix(kind);
result = prefix + result;
}
int nb = getInput().getCategoryManager().getUIDifferenceNumber(match);
if (nb > 0)
result = result + " (" + nb + ")"; //$NON-NLS-1$ //$NON-NLS-2$
return result;
}
/**
* @see org.eclipse.jface.viewers.CellLabelProvider#getToolTipText(java.lang.Object)
*/
@Override
public String getToolTipText(Object element_p) {
String result = null;
if (element_p instanceof IPureMatch) {
Object matchID = ((IPureMatch)element_p).getMatchID();
if (matchID != null) {
String matchIDText = matchID.toString();
result = Messages.ComparisonTreeViewer_MatchIDTooltip + matchIDText;
}
}
return result;
}
/**
* @see org.eclipse.jface.viewers.ITreePathLabelProvider#updateLabel(org.eclipse.jface.viewers.ViewerLabel, org.eclipse.jface.viewers.TreePath)
*/
public void updateLabel(ViewerLabel label_p, TreePath elementPath_p) {
String text = getPathText(elementPath_p);
label_p.setText(text);
Object element = elementPath_p.getLastSegment();
label_p.setImage(getPathImage(elementPath_p));
label_p.setBackground(getBackground(element));
label_p.setForeground(getForeground(element));
label_p.setFont(getPathFont(elementPath_p));
}
}
}