/******************************************************************************* * Copyright (c) 2012, 2016 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.provider.spec; import static com.google.common.collect.Lists.newArrayList; import static org.eclipse.emf.compare.ConflictKind.REAL; import static org.eclipse.emf.compare.DifferenceState.DISCARDED; import static org.eclipse.emf.compare.DifferenceState.MERGED; import static org.eclipse.emf.compare.utils.EMFComparePredicates.canBeConsideredAsPseudoConflicting; import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasDirectOrIndirectConflict; import java.util.Collection; import java.util.List; import org.eclipse.emf.common.util.ResourceLocator; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceKind; import org.eclipse.emf.compare.DifferenceSource; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.internal.merge.IMergeData; import org.eclipse.emf.compare.internal.utils.ComparisonUtil; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.provider.ComposedImage; /** * Utility class to externalize the retrieval of difference overlay. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ public class OverlayImageProvider { /** The base name of the change (and move) kind of diff overlay. */ private static final String CHG_OV = "chg_ov"; //$NON-NLS-1$ /** The base name of the deletion kind of diff overlay. */ private static final String DEL_OV = "del_ov"; //$NON-NLS-1$ /** The base name of the add kind of diff overlay. */ private static final String ADD_OV = "add_ov"; //$NON-NLS-1$ /** The base name of the merge to right diff overlay. */ private static final String MERGED_TO_RIGHT_OV = "merged_right_ov"; //$NON-NLS-1$ /** The base name of the merge to left diff overlay. */ private static final String MERGED_TO_LEFT_OV = "merged_left_ov"; //$NON-NLS-1$ /** The base name of the rejected diff overlay. */ private static final String REJECTED_OV = "removed_ov"; //$NON-NLS-1$ /** The base name of the accepted diff overlay. */ private static final String ACCEPTED_OV = "merged_ov"; //$NON-NLS-1$ /** The resource locator to use to retrieve the images. */ private final ResourceLocator fResourceLocator; /** * Creates a new instance with the given resource locator. * * @param resourceLocator * the resource locator to use to retrieve images */ public OverlayImageProvider(ResourceLocator resourceLocator) { this.fResourceLocator = resourceLocator; } /** * Returns a composed image with the image of the given diff the appropriate overlay. * * @param diff * the diff for which we have to find an overlay. * @param imageToCompose * the image of the diff to use as base. * @return a composed image with the image of the given diff the appropriate overlay. */ public Object getComposedImage(Diff diff, Object imageToCompose) { String overlay = getImageOverlay(diff); return getComposedImage(imageToCompose, overlay); } /** * Returns a composed image with the image of the given match the appropriate overlay. * * @param match * the match for which we have to find an overlay. * @param imageToCompose * the image of the match to use as base. * @return a composed image with the image of the given match the appropriate overlay. */ public Object getComposedImage(Match match, Object imageToCompose) { String overlay = getImageOverlay(); return getComposedImage(imageToCompose, overlay); } /** * Returns a composed image with the image of the given object and the appropriate overlay. * * @param object * the object for which we have to find an overlay. * @param imageToCompose * the image of the match to use as base. * @return a composed image with the image of the given object and the appropriate overlay. * @since 4.0 */ public Object getComposedImage(Object object, Object imageToCompose) { String overlay = "full/ovr16/match_ov"; //$NON-NLS-1$ return getComposedImage(imageToCompose, overlay); } /** * Returns a composed image with the image of the given match the overlay at the given path. * * @param imageToCompose * the image to use as base. * @param overlayPath * the path to the overlay. * @return a composed image with the image of the given match the overlay at the given path. */ private Object getComposedImage(Object imageToCompose, String overlayPath) { Collection<Object> images = newArrayList(); images.add(imageToCompose); if (overlayPath != null) { Object image = fResourceLocator.getImage(overlayPath); images.add(image); } return new ComposedImageExtension(images); } /** * Returns the path to the image overlay for the given {@code diff}. * * @param diff * the diff we have to find an image for. * @return the path to the image overlay for the given {@code diff}. */ private String getImageOverlay(Diff diff) { final Comparison comparison = ComparisonUtil.getComparison(diff); String path = "full/ovr16/"; //$NON-NLS-1$ if (diff.getState() == MERGED) { path += getMergedOverlay(diff); } else if (diff.getState() == DISCARDED) { path += getDiscardedOverlay(diff); } else if (comparison.isThreeWay()) { path += getThreeWayOverlay(diff); } else { path += getTwoWayOverlay(diff); } return path; } /** * Returns the overlay path for the given unmerged diff. * * @param diff * the diff we have to find an image for. * @return the overlay path for the given unmerged diff. */ private String getThreeWayOverlay(final Diff diff) { DifferenceKind diffKind = diff.getKind(); DifferenceSource source = diff.getSource(); StringBuilder path = new StringBuilder(); if (hasDirectOrIndirectConflict(REAL).apply(diff)) { // The diff or one of its refining diffs are in a pseudo conflict path.append("conf"); //$NON-NLS-1$ if (source == DifferenceSource.RIGHT) { path.append("r_"); //$NON-NLS-1$ } } else if (canBeConsideredAsPseudoConflicting().apply(diff)) { // If the diff is not a refined diff and is in a pseudo conflict // Or if the diff is a refined diff that is not in a direct pseudo conflict, but all its // refining diffs are in pseudo conflicts path.append("pconf"); //$NON-NLS-1$ if (source == DifferenceSource.RIGHT) { path.append("r_"); //$NON-NLS-1$ } } else { switch (source) { case LEFT: path.append("r_out"); //$NON-NLS-1$ break; case RIGHT: path.append("r_in"); //$NON-NLS-1$ break; default: throw new IllegalArgumentException(); } } switch (diffKind) { case ADD: path.append(ADD_OV); break; case DELETE: path.append(DEL_OV); break; case CHANGE: // fallthrough case MOVE: path.append(CHG_OV); break; default: throw new IllegalArgumentException(); } return path.toString(); } /** * Returns the overlay for a diff that's in a 'DISCARDED' state. * * @param diff * The diff * @return The overlay path to use. */ // @CHECKSTYLE:OFF private String getDiscardedOverlay(Diff diff) { DifferenceSource source = diff.getSource(); Comparison comp = ComparisonUtil.getComparison(diff); IMergeData mergeData = (IMergeData)EcoreUtil.getExistingAdapter(comp, IMergeData.class); if (mergeData != null) { switch (source) { case LEFT: if (mergeData.isLeftEditable() && mergeData.isRightEditable()) { return MERGED_TO_LEFT_OV; } else { return REJECTED_OV; } case RIGHT: if (mergeData.isLeftEditable() && mergeData.isRightEditable()) { return MERGED_TO_RIGHT_OV; } else { return REJECTED_OV; } default: throw new IllegalArgumentException(); } } // Cannot determine direction return REJECTED_OV; } // @CHECKSTYLE:ON /** * Returns the overlay for a diff that's in a 'MERGED' state. * * @param diff * The diff * @return The overlay path to use. */ // @CHECKSTYLE:OFF private String getMergedOverlay(Diff diff) { DifferenceSource source = diff.getSource(); Comparison comp = ComparisonUtil.getComparison(diff); IMergeData mergeData = (IMergeData)EcoreUtil.getExistingAdapter(comp, IMergeData.class); if (mergeData != null) { switch (source) { case LEFT: if (mergeData.isLeftEditable() && mergeData.isRightEditable()) { return MERGED_TO_RIGHT_OV; } else { return ACCEPTED_OV; } case RIGHT: if (mergeData.isLeftEditable() && mergeData.isRightEditable()) { return MERGED_TO_LEFT_OV; } else { return ACCEPTED_OV; } default: throw new IllegalArgumentException(); } } // Cannot determine direction return ACCEPTED_OV; } // @CHECKSTYLE:ON /** * Returns the overlay path for the given unmerged diff. * * @param diff * the diff we have to find an image for. * @return the overlay path for the given unmerged diff. */ private String getTwoWayOverlay(Diff diff) { final String path; final DifferenceKind diffKind = diff.getKind(); switch (diffKind) { case ADD: path = ADD_OV; break; case DELETE: path = DEL_OV; break; case CHANGE: // fallthrough case MOVE: path = CHG_OV; break; default: throw new IllegalStateException(); } return path; } /** * Returns the overlay path for the given match. * * @return the overlay path for the given match. */ private String getImageOverlay() { return "full/ovr16/match_ov"; //$NON-NLS-1$ } /** * Extended {@link ComposedImage} that positionned the overlay properly for EMF Compare. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ private static final class ComposedImageExtension extends ComposedImage { /** The offset of the overlays. */ private static final int X_OFFSET = 10; /** * Creates a new instance with the given image list. The second image will be draw at the offset * (X_OFFSET, 2). * * @param images * the images to composed. */ ComposedImageExtension(Collection<?> images) { super(images); } /** * {@inheritDoc} * * @see org.eclipse.emf.edit.provider.ComposedImage#getDrawPoints(org.eclipse.emf.edit.provider.ComposedImage.Size) */ @Override public List<Point> getDrawPoints(Size size) { List<ComposedImage.Point> result = super.getDrawPoints(size); if (result.size() > 1) { result.get(1).x = X_OFFSET; result.get(1).y = 2; } return result; } /** * {@inheritDoc} * * @see org.eclipse.emf.edit.provider.ComposedImage#getSize(java.util.Collection) */ @Override public Size getSize(Collection<? extends Size> sizes) { this.imageSizes = newArrayList(sizes); List<Point> drawPoints = getDrawPoints(null); Size result = new Size(); for (int i = 0; i < sizes.size(); i++) { Size size = this.imageSizes.get(i); Point point = drawPoints.get(i); result.width = Math.max(result.width, size.width + Math.abs(point.x)); result.height = Math.max(result.height, size.height + Math.abs(point.y)); } return result; } } }