/** * <copyright> * * Copyright (c) 2010-2016 Thales Global Services S.A.S 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: * Thales Global Services S.A.S. - initial API and implementation * Stephane Bouchet (Intel Corporation) - [439901] support multi-line in table item. * Stephane Bouchet (Intel Corporation) - Bug #489137 : delegate tooltip of values viewer to proper label provider * * </copyright> */ package org.eclipse.emf.diffmerge.ui.viewers; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.emf.diffmerge.api.IComparison; import org.eclipse.emf.diffmerge.api.IMatch; import org.eclipse.emf.diffmerge.api.Role; import org.eclipse.emf.diffmerge.api.diff.IAttributeValuePresence; import org.eclipse.emf.diffmerge.api.diff.IDifference; import org.eclipse.emf.diffmerge.api.diff.IReferenceValuePresence; import org.eclipse.emf.diffmerge.api.diff.IValuePresence; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin.DifferenceColorKind; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin.ImageID; import org.eclipse.emf.diffmerge.ui.Messages; import org.eclipse.emf.diffmerge.ui.diffuidata.MatchAndFeature; 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.EAttribute; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.window.ToolTip; 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.graphics.Point; import org.eclipse.swt.layout.GridData; 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.TableItem; /** * A viewer which provides a representation of the values of a feature on a match. * Input: ValuesViewer.ValuesInput ; * Elements: * if !showAllValues: [IValuePresence] * if showAllValues: if feature instanceof EAttribute: [Object] * if feature instanceof EReference: [IMatch]. * @author Olivier Constant */ public class ValuesViewer extends TableViewer implements IComparisonSideViewer, IDifferenceRelatedViewer { /** * A simple structure for defining inputs for this viewer. */ public static class ValuesInput { /** The non-null comparison context */ private final EMFDiffNode _context; /** The non-null specific part */ private final MatchAndFeature _matchAndFeature; /** * Constructor * @param context_p a non-null object * @param matchAndFeature_p a non-null object */ public ValuesInput(EMFDiffNode context_p, MatchAndFeature matchAndFeature_p) { _context = context_p; _matchAndFeature = matchAndFeature_p; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object object_p) { boolean result = false; if (object_p instanceof ValuesInput) { ValuesInput peer = (ValuesInput)object_p; result = _context == peer.getContext() && _matchAndFeature.getFeature() == peer.getMatchAndFeature().getFeature() && _matchAndFeature.getMatch().equals(peer.getMatchAndFeature().getMatch()); } return result; } /** * Return the comparison context * @return a non-null object */ public EMFDiffNode getContext() { return _context; } /** * Return the match and feature * @return a non-null object */ public MatchAndFeature getMatchAndFeature() { return _matchAndFeature; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return _context.hashCode() + _matchAndFeature.getMatch().hashCode() + _matchAndFeature.getFeature().hashCode(); } } /** Whether the side of the viewer is left or right */ private final boolean _sideIsLeft; /** Whether all values must be shown, including those not related to a difference */ private boolean _showAllValues; /** * Constructor * @param parent_p a non-null composite * @param sideIsLeft_p whether the side is left or right */ public ValuesViewer(Composite parent_p, boolean sideIsLeft_p) { this(parent_p, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, sideIsLeft_p); } /** * Constructor * @param parent_p a non-null composite * @param style_p a style for the tree * @param sideIsLeft_p whether the side is left or right */ public ValuesViewer(Composite parent_p, int style_p, boolean sideIsLeft_p) { super(parent_p, style_p); setContentProvider(new ContentProvider()); setLabelProvider(new LabelProvider()); _sideIsLeft = sideIsLeft_p; _showAllValues = false; getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); setMultilineSupport(getControl()); getTable().setToolTipText(""); //$NON-NLS-1$ ColumnViewerToolTipSupport.enableFor(this, ToolTip.NO_RECREATE); } /** * Return the model element that corresponds to the given viewer value, if applicable * @param viewerValueElement_p a potentially null object * @return a potentially null element */ public EObject getElementForValue(Object viewerValueElement_p) { EObject result; if (viewerValueElement_p instanceof IReferenceValuePresence) result = (EObject)getValueToRepresent((IValuePresence)viewerValueElement_p); else if (viewerValueElement_p instanceof IMatch) result = ((IMatch)viewerValueElement_p).get(getSideRole()); else if (viewerValueElement_p instanceof EObject && !(viewerValueElement_p instanceof IDifference)) result = (EObject)viewerValueElement_p; else result = null; return result; } /** * @see org.eclipse.jface.viewers.ContentViewer#getInput() */ @Override public ValuesInput getInput() { return (ValuesInput)super.getInput(); } /** * Return the resource manager for this viewer * @return a resource manager which is non-null iff input is not null */ protected ComparisonResourceManager getResourceManager() { return getInput() == null? null: getInput().getContext().getResourceManager(); } /** * Return the role that corresponds to the values being represented * @return a role which is null if and only if the input is null */ protected Role getSideRole() { return getInput() == null? null: getInput().getContext().getRoleForSide(isLeftSide()); } /** * @see org.eclipse.jface.viewers.StructuredViewer#getSelection() */ @Override public IStructuredSelection getSelection() { return (IStructuredSelection)super.getSelection(); } /** * Return the value object of the given value presence that should be represented * @param presence_p a non-null value presence * @return a non-null object */ protected Object getValueToRepresent(IValuePresence presence_p) { Object result; if (presence_p.getFeature() instanceof EAttribute) { result = presence_p.getValue(); } else { IReferenceValuePresence presence = (IReferenceValuePresence)presence_p; Role presenceRole = presence.getPresenceRole(); if (isOwnership(getInput().getMatchAndFeature())) { result = presence.getElementMatch().get(presenceRole); } else { result = presence.getValue(); } } return result; } /** * Return whether the given input object represents a containment * @param object_p a potentially null object */ protected boolean isContainment(Object object_p) { boolean result = false; if (object_p instanceof MatchAndFeature) { EStructuralFeature feature = ((MatchAndFeature)object_p).getFeature(); result = feature instanceof EReference && ((EReference)feature).isContainment(); } return result; } /** * @see org.eclipse.emf.diffmerge.ui.viewers.IDifferenceRelatedViewer#isDifferenceAgnostic() */ public boolean isDifferenceAgnostic() { return _showAllValues; } /** * @see org.eclipse.emf.diffmerge.ui.viewers.IComparisonSideViewer#isLeftSide() */ public boolean isLeftSide() { return _sideIsLeft; } /** * Return whether the given input object represents the virtual ownership feature * @param object_p a potentially null object */ protected boolean isOwnership(Object object_p) { boolean result = false; Object object = object_p; if (object instanceof ValuesInput) object = ((ValuesInput)object).getMatchAndFeature(); if (object instanceof MatchAndFeature) { EStructuralFeature feature = ((MatchAndFeature)object).getFeature(); result = EMFDiffMergeUIPlugin.getDefault().getOwnershipFeature().equals(feature); } return result; } /** * @see org.eclipse.emf.diffmerge.ui.viewers.IDifferenceRelatedViewer#setDifferenceAgnostic(boolean) */ public void setDifferenceAgnostic(boolean agnostic_p) { if (agnostic_p != isDifferenceAgnostic()) { _showAllValues = agnostic_p; refresh(false); } } /** * Add multi-line support for long strings to the table of this viewer. * See http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet231.java for reference. * @param control_p the non-null control (presumably a table) to which multi-line support must be added */ protected void setMultilineSupport(Control control_p) { control_p.addListener(SWT.MeasureItem, new Listener() { /** * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event_p) { TableItem item = (TableItem)event_p.item; if (item != null) { String text = item.getText(event_p.index); Point size = event_p.gc.textExtent(text); event_p.width = size.x + item.getImageBounds(0).width + 4; event_p.height = size.y; } } }); control_p.addListener(SWT.EraseItem, new Listener() { /** * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event_p) { event_p.detail &= ~SWT.FOREGROUND; } }); control_p.addListener(SWT.PaintItem, new Listener() { /** * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event_p) { TableItem item = (TableItem)event_p.item; if (item != null) { String text = item.getText(event_p.index); Point size = (text != null)? event_p.gc.textExtent(text): new Point(0,0); event_p.width = size.x + 8; event_p.height = Math.max(event_p.height, size.y); // Based on the offset before the image, draw image and text int offset = event_p.x; Image image = item.getImage(); if (image != null) event_p.gc.drawImage(image, offset, event_p.y); if (text != null) event_p.gc.drawText(text, offset + item.getImageBounds(0).width + 4, event_p.y, true); } } }); } /** * Return whether the given object must be represented as a difference * @param element_p a potentially null object */ protected boolean showAsDifference(Object element_p) { return element_p instanceof IValuePresence && !getInput().getContext().getCategoryManager().isFiltered((IDifference)element_p); } /** * The content provider for this viewer. */ protected class ContentProvider implements IStructuredContentProvider { /** * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) */ public Object[] getElements(Object inputElement_p) { MatchAndFeature input = ((ValuesInput)inputElement_p).getMatchAndFeature(); Collection<Object> result = new ArrayList<Object>(); if (isOwnership(input)) { // Ownership IReferenceValuePresence ownership = input.getMatch().getOwnershipDifference(getSideRole()); if (ownership != null) result.add(ownership); } else { // Order IValuePresence orderDifference = input.getMatch().getOrderDifference( input.getFeature(), getSideRole()); if (orderDifference != null) result.add(orderDifference); // Only show values if no containment if (!isContainment(input)) { if (isDifferenceAgnostic()) { // All values if (input.getFeature() instanceof EAttribute) { // All attribute values EAttribute attribute = (EAttribute)input.getFeature(); IComparison comparison = input.getMatch().getMapping().getComparison(); IMatch match = input.getMatch(); EObject source = match.get(getSideRole()); if (source != null) { List<Object> values = comparison.getScope(getSideRole()).get(source, attribute); for (Object value : values) { IAttributeValuePresence presence = match.getAttributeValueDifference(attribute, value); if (presence != null) result.add(presence); else result.add(value); } } } else { // All reference values EReference reference = (EReference)input.getFeature(); IComparison comparison = input.getMatch().getMapping().getComparison(); IMatch match = input.getMatch(); EObject source = match.get(getSideRole()); if (source != null) { List<EObject> values = comparison.getScope(getSideRole()).get(source, reference); for (EObject value : values) { IReferenceValuePresence presence = match.getReferenceValueDifference(reference, value); if (presence != null) result.add(presence); else result.add(value); } } } } else { // Only differences Collection<? extends IValuePresence> bothSides; if (input.getFeature() instanceof EAttribute) bothSides = input.getMatch().getAttributeDifferences((EAttribute)input.getFeature()); else bothSides = input.getMatch().getReferenceDifferences((EReference)input.getFeature()); for (IValuePresence presence : bothSides) { if (!presence.isOrder() && presence.getPresenceRole() == getSideRole() && presence.getMergeDestination() != getSideRole() || !presence.isOrder() && presence.getPresenceRole() == getSideRole().opposite() && presence.getMergeDestination() == getSideRole()) result.add(presence); } } } } return result.toArray(); } /** * @see org.eclipse.jface.viewers.IContentProvider#dispose() */ public void dispose() { // Nothing needed } /** * @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 { /** * Adapt the given label that describes the cross-reference of the given value * @param initialLabel_p a potentially null string * @param value_p a non-null element * @return a potentially null string */ private String formatCrossReferenceValue(String initialLabel_p, EObject value_p) { StringBuilder builder = new StringBuilder(); if (initialLabel_p != null) builder.append(initialLabel_p); EObject container = value_p.eContainer(); String containerLabel = container == null? null: getDelegate().getText(container); if (containerLabel != null) { builder.append(' '); builder.append(String.format(Messages.ValuesViewer_ContainerLabel, containerLabel)); } return builder.toString(); } /** * Adapt the given label of the owner of the given reference value presence so that it conveniently * describes a containment * @param ownerLabel_p a potentially null string * @param presence_p a non-null reference value presence * @return a potentially null string */ private String formatOwnershipValue(String ownerLabel_p, IReferenceValuePresence presence_p) { StringBuilder builder = new StringBuilder(); if (ownerLabel_p != null) builder.append(ownerLabel_p); EReference containment = presence_p.getFeature(); if (containment != null) { builder.append(' '); String containmentText = UIUtil.getFormattedFeatureText(containment); builder.append(String.format(Messages.ValuesViewer_FeatureLabel, containmentText)); } return builder.toString(); } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getFont(java.lang.Object) */ @Override public Font getFont(Object element_p) { Font result = getControl().getFont(); if (showAsDifference(element_p)) result = UIUtil.getBold(result); return result; } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getForeground(java.lang.Object) */ @Override public Color getForeground(Object element_p) { DifferenceColorKind result; if (showAsDifference(element_p)) { result = (getSideRole() == getInput().getContext().getDrivingRole())? DifferenceColorKind.LEFT: DifferenceColorKind.RIGHT; } else { result = DifferenceColorKind.DEFAULT; } return getInput().getContext().getDifferenceColor(result); } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getImage(java.lang.Object) */ @Override public Image getImage(Object element_p) { Image result; if (element_p instanceof IValuePresence) { IValuePresence presence = (IValuePresence)element_p; if (presence.isOrder()) { result = EMFDiffMergeUIPlugin.getDefault().getImage(ImageID.SORT); } else { Object toRepresent = getValueToRepresent(presence); result = getDelegate().getImage(toRepresent); } if (getInput().getContext().usesCustomIcons()) { DifferenceKind kind; if (isOwnership(getInput().getMatchAndFeature()) && getInput().getContext().getReferenceRole() == null) { kind = DifferenceKind.MODIFIED; } else { kind = getInput().getContext().getCategoryManager().getDifferenceKind(presence); } result = getResourceManager().adaptImage(result, kind); } } else if (element_p instanceof IMatch) { result = getDelegate().getImage(((IMatch)element_p).get(getSideRole())); } else { result = getDelegate().getImage(element_p); } return result; } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getText(java.lang.Object) */ @Override public String getText(Object element_p) { String result; if (element_p instanceof IValuePresence) { IValuePresence presence = (IValuePresence)element_p; if (presence.isOrder()) { result = Messages.ValuesViewer_OrderLabel; } else { Object toRepresent = getValueToRepresent(presence); result = getDelegate().getText(toRepresent); if (isOwnership(getInput())) result = formatOwnershipValue(result, (IReferenceValuePresence)presence); else if (toRepresent instanceof EObject) result = formatCrossReferenceValue(result, (EObject)toRepresent); } if (getInput().getContext().usesCustomLabels()) { DifferenceKind kind = getInput().getContext().getCategoryManager().getDifferenceKind(presence); String prefix = EMFDiffMergeUIPlugin.getDefault().getDifferencePrefix(kind); result = prefix + result; } } else if (element_p instanceof IMatch) { result = getDelegate().getText(((IMatch)element_p).get(getSideRole())); } else { result = getDelegate().getText(element_p); } return result; } /** * @see org.eclipse.jface.viewers.CellLabelProvider#getToolTipText(java.lang.Object) */ @Override public String getToolTipText(Object element_p) { return getText(element_p); } } }