/******************************************************************************* * Copyright (c) 2013, 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 * Philip Langer - adaptation for refactoring regarding SizeChange * Simon Delisle - bug 511047 *******************************************************************************/ package org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.base.Predicates.or; import static org.eclipse.emf.compare.merge.AbstractMerger.isInTerminalState; import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind; import static org.eclipse.emf.compare.utils.EMFComparePredicates.onFeature; import static org.eclipse.emf.compare.utils.EMFComparePredicates.valueIs; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.Collection; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Polyline; import org.eclipse.draw2d.PolylineConnection; import org.eclipse.draw2d.RectangleFigure; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CommandStack; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceKind; import org.eclipse.emf.compare.DifferenceState; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.command.impl.CopyCommand; import org.eclipse.emf.compare.diagram.ide.ui.internal.accessor.IDiagramDiffAccessor; import org.eclipse.emf.compare.diagram.ide.ui.internal.accessor.IDiagramNodeAccessor; import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.figures.DecoratorFigure; import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.figures.EdgeFigure; import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.figures.NodeFigure; import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.figures.NodeListFigure; import org.eclipse.emf.compare.diagram.internal.extensions.CoordinatesChange; import org.eclipse.emf.compare.diagram.internal.extensions.DiagramDiff; import org.eclipse.emf.compare.diagram.internal.extensions.Hide; import org.eclipse.emf.compare.diagram.internal.extensions.Show; 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.TreeContentMergeViewerContentProvider; import org.eclipse.emf.compare.internal.utils.DiffUtil; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide; import org.eclipse.emf.compare.utils.ReferenceUtil; import org.eclipse.emf.ecore.EObject; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.EditPart; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.LayerConstants; import org.eclipse.gef.editparts.AbstractGraphicalEditPart; import org.eclipse.gef.editparts.LayerManager; import org.eclipse.gmf.runtime.diagram.ui.editparts.ListCompartmentEditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.ListItemEditPart; import org.eclipse.gmf.runtime.notation.Diagram; import org.eclipse.gmf.runtime.notation.Edge; import org.eclipse.gmf.runtime.notation.NotationPackage; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.widgets.Composite; /** * 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:cedric.notot@obeo.fr">Cedric Notot</a> */ @SuppressWarnings("restriction") public class DiagramContentMergeViewer extends EMFCompareContentMergeViewer { /** * Interface for the management of decorators. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private interface IDecoratorManager { /** * It hides the revealed decorators. */ void hideAll(); /** * From a given difference, it hides the related decorators. * * @param difference * The difference. */ void hideDecorators(Diff difference); /** * From a given difference, it reveals the related decorators. * * @param difference * The difference. */ void revealDecorators(Diff difference); /** * From a given difference, it removes the related decorators from cash. * * @param difference * The difference. */ void removeDecorators(Diff difference); /** * It removes all the displayed decorators from cache. */ void removeAll(); } /** * Decorator manager to create, hide or reveal decorator figures related to deleted or added graphical * objects. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private abstract class AbstractDecoratorManager implements IDecoratorManager { /** * Decorator represented by a <code>figure</code> on a <code>layer</code>, from the given * <code>side</code> of the merge viewer. An edit part may be linked to the <code>figure</code> in * some cases.<br> * The decorator is related to a <code>difference</code> and it is binded with the reference view and * figure. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ protected abstract class AbstractDecorator { /** The reference <code>View</code> for this decorator. */ protected View fOriginView; /** The reference <code>IFigure</code> for this decorator. */ protected IFigure fOriginFigure; /** The <code>IFigure</code> representing this decorator. */ protected IFigure fFigure; /** * The <code>DecoratorFigure</code> representing this decorator. It references * {@link AbstractDecorator#fFigure} through the accessor {@link DecoratorFigure#getMainFigure()}. */ protected DecoratorFigure fDecoratorFigure; /** The layer on which the <code>figure</code> has to be drawn. */ protected IFigure fLayer; /** The side of the merge viewer on which the <code>figure</code> has to be drawn. */ protected MergeViewerSide fSide; /** The difference related to this phantom. */ protected Diff fDifference; /** The edit part of the figure representing this phantom. May be null. */ protected EditPart fEditPart; /** * Getter. * * @return the originView {@link AbstractDecorator#fOriginView}. */ public View getOriginView() { return fOriginView; } /** * Setter. * * @param originView * {@link AbstractDecorator#fOriginView}. */ public void setOriginView(View originView) { this.fOriginView = originView; } /** * Getter. * * @return the originFigure {@link AbstractDecorator#fOriginFigure}. */ public IFigure getOriginFigure() { return fOriginFigure; } /** * Setter. * * @param originFigure * {@link AbstractDecorator#fOriginFigure}. */ public void setOriginFigure(IFigure originFigure) { this.fOriginFigure = originFigure; } /** * Getter. * * @return the figure {@link AbstractDecorator#fFigure}. */ public IFigure getFigure() { return fFigure; } /** * Getter. * * @return the decorator figure {@link AbstractDecorator#fDecoratorFigure}. */ public DecoratorFigure getDecoratorFigure() { return fDecoratorFigure; } /** * Setter. * * @param figure * {@link AbstractDecorator#fFigure}. */ public void setFigure(IFigure figure) { this.fFigure = figure; } /** * Setter. * * @param figure * {@link AbstractDecorator#fFigure}. */ public void setDecoratorFigure(DecoratorFigure figure) { this.fDecoratorFigure = figure; setFigure(figure.getMainFigure()); } /** * Getter. * * @return the layer {@link AbstractDecorator#fLayer}. */ public IFigure getLayer() { return fLayer; } /** * Setter. * * @param layer * {@link AbstractDecorator#fLayer}. */ public void setLayer(IFigure layer) { this.fLayer = layer; } /** * Getter. * * @return the side {@link AbstractDecorator#fSide}. */ public MergeViewerSide getSide() { return fSide; } /** * Setter. * * @param side * {@link AbstractDecorator#fSide}. */ public void setSide(MergeViewerSide side) { this.fSide = side; } /** * Getter. * * @return the difference {@link AbstractDecorator#fDifference}. */ public Diff getDifference() { return fDifference; } /** * Setter. * * @param difference * {@link AbstractDecorator#fDifference}. */ public void setDifference(Diff difference) { this.fDifference = difference; } /** * Getter. * * @return the editPart {@link AbstractDecorator#fEditPart}. */ public EditPart getEditPart() { return fEditPart; } /** * Setter. * * @param editPart * {@link AbstractDecorator#fEditPart}. */ public void setEditPart(EditPart editPart) { this.fEditPart = editPart; } } /** * From a given difference, it hides the related decorators. * * @param difference * The difference. */ public void hideDecorators(Diff difference) { Collection<? extends AbstractDecorator> oldDecorators = getDecorators(difference); if (oldDecorators != null && !oldDecorators.isEmpty() && getCompareConfiguration().getComparison() != null) { handleDecorators(oldDecorators, false, true); } } /** * From a given difference, it reveals the related decorators. * * @param difference * The difference. */ public void revealDecorators(Diff difference) { Collection<? super AbstractDecorator> decorators = (Collection<? super AbstractDecorator>)getDecorators( difference); // Create decorators only if they do not already exist and if the selected difference is a good // candidate for that. if ((decorators == null || decorators.isEmpty()) && isGoodCandidate(difference)) { DiagramDiff diagramDiff = (DiagramDiff)difference; List<View> referenveViews = getReferenceViews(diagramDiff); for (View referenceView : referenveViews) { IFigure referenceFigure = getFigure(referenceView); if (referenceFigure != null) { MergeViewerSide targetSide = getTargetSide( getCompareConfiguration().getComparison().getMatch(referenceView), referenceView); if (decorators == null) { decorators = new ArrayList<AbstractDecorator>(); } AbstractDecorator decorator = createAndRegisterDecorator(difference, referenceView, referenceFigure, targetSide); if (decorator != null) { decorators.add(decorator); } } } } // The selected difference is a good candidate and decorators exist for it if (decorators != null && !decorators.isEmpty()) { revealDecorators((Collection<? extends AbstractDecorator>)decorators); } } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#removeDecorators(org.eclipse.emf.compare.Diff) */ public abstract void removeDecorators(Diff difference); /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#removeAll() */ public abstract void removeAll(); /** * It reveals the given decorators. * * @param decorators * The main decorators. */ protected void revealDecorators(Collection<? extends AbstractDecorator> decorators) { handleDecorators(decorators, true, true); } /** * Get the figure related to the given view. * * @param view * The view. * @return the figure. */ protected IFigure getFigure(View view) { MergeViewerSide side = getSide(view); GraphicalEditPart originEditPart = (GraphicalEditPart)getViewer(side).getEditPart(view); if (originEditPart != null) { return originEditPart.getFigure(); } return null; } /** * It manages the display of the given decorators.<br> * * @param decorators * The decorators to handle. * @param isAdd * True if it has to be revealed, False otherwise. * @param areMain * It indicates if the given decorators to handle are considered as the main ones (the ones * directly linked to the selected difference). */ protected void handleDecorators(Collection<? extends AbstractDecorator> decorators, boolean isAdd, boolean areMain) { for (AbstractDecorator decorator : decorators) { handleDecorator(decorator, isAdd, areMain); } } /** * It manages the display of the given decorator. * * @param decorator * The decorator to handle. * @param isAdd * True if it has to be revealed, False otherwise. * @param isMain * It indicates if the given decorator to handle is considered as the main one (the one * directly linked to the selected difference). */ protected void handleDecorator(AbstractDecorator decorator, boolean isAdd, boolean isMain) { IFigure layer = decorator.getLayer(); IFigure figure = decorator.getFigure(); if (isAdd) { handleAddDecorator(decorator, layer, figure, isMain); } else if (layer.getChildren().contains(figure)) { handleDeleteDecorator(decorator, layer, figure); } } /** * It manages the reveal of the given decorator. * * @param decorator * The decorator. * @param parent * The parent figure which has to get the figure to reveal (<code>toAdd</code>) * @param toAdd * The figure to reveal. * @param isMain * It indicates if the given decorator to reveal is considered as the main one (the one * directly linked to the selected difference). */ protected void handleAddDecorator(AbstractDecorator decorator, IFigure parent, IFigure toAdd, boolean isMain) { if (decorator.getEditPart() != null) { decorator.getEditPart().activate(); } if (!parent.getChildren().contains(toAdd)) { parent.add(toAdd); } } /** * It manages the hiding of the given decorator. * * @param decorator * The decorator. * @param parent * The parent figure which has to get the figure to hide (<code>toDelete</code>) * @param toDelete * The figure to hide. */ protected void handleDeleteDecorator(AbstractDecorator decorator, IFigure parent, IFigure toDelete) { if (decorator.getEditPart() != null) { decorator.getEditPart().deactivate(); } if (parent.getChildren().contains(toDelete)) { parent.remove(toDelete); } } /** * It checks if the given difference is a good candidate to manage decorators.<br> * * @see {@link PhantomManager#goodCandidate()}. * @param difference * The difference. * @return True if it is a good candidate, False otherwise. */ private boolean isGoodCandidate(Diff difference) { return goodCandidate().apply(difference); } /** * Get the layer on the given side, from the reference view.<br> * * @see @ link PhantomManager#getIDLayer(View)} . * @param referenceView * The reference view. * @param side * The side where the layer has to be found. * @return The layer figure or null if the edit part of the diagram is not found. */ protected IFigure getLayer(View referenceView, MergeViewerSide side) { Diagram referenceDiagram = referenceView.getDiagram(); Diagram targetDiagram = (Diagram)getMatchView(referenceDiagram, side); DiagramMergeViewer targetViewer = getViewer(side); EditPart editPart = targetViewer.getEditPart(targetDiagram); if (editPart != null) { return LayerManager.Helper.find(editPart).getLayer(getIDLayer(referenceView)); } return null; } /** * Get the layer ID to use from the reference view.<br> * If the reference view is an edge, it is the {@link LayerConstants.CONNECTION_LAYER} which is used, * {@link LayerConstants.SCALABLE_LAYERS} otherwise. * * @param referenceView * The reference view. * @return The ID of the layer. */ protected Object getIDLayer(View referenceView) { if (referenceView instanceof Edge) { return LayerConstants.CONNECTION_LAYER; } else { return LayerConstants.SCALABLE_LAYERS; } } /** * It translates the coordinates of the given bounds, from the reference figure and the root of this * one, to absolute coordinates. * * @param referenceFigure * The reference figure. * @param rootReferenceFigure * The root of the reference figure. * @param boundsToTranslate * The bounds to translate. */ protected void translateCoordinates(IFigure referenceFigure, IFigure rootReferenceFigure, Rectangle boundsToTranslate) { IFigure referenceParentFigure = referenceFigure.getParent(); if (referenceParentFigure != null && referenceFigure != rootReferenceFigure) { // rootReferenceFigure may be located to (-x,0)... We consider that the root reference is // always (0,0) if (referenceParentFigure.isCoordinateSystem() && referenceParentFigure != rootReferenceFigure) { boundsToTranslate.x += referenceParentFigure.getBounds().x; boundsToTranslate.y += referenceParentFigure.getBounds().y; } translateCoordinates(referenceParentFigure, rootReferenceFigure, boundsToTranslate); } } /** * It checks that the given view represents an element of a list. * * @param view * The view. * @return True it it is an element of a list. */ protected boolean isNodeList(View view) { DiagramMergeViewer viewer = getViewer(getSide(view)); EditPart part = viewer.getEditPart(view); return isNodeList(part); } /** * It checks that the given part represents an element of a list. * * @param part * The part. * @return True it it represents an element of a list. */ private boolean isNodeList(EditPart part) { return part instanceof ListItemEditPart || isInListContainer(part); } /** * It checks that the given part is in one representing a list container. * * @param part * The part. * @return True it it is in a part representing a list container. */ private boolean isInListContainer(EditPart part) { EditPart parent = part.getParent(); while (parent != null) { if (parent instanceof ListCompartmentEditPart) { return true; } parent = parent.getParent(); } return false; } /** * Get the predicate to know the differences concerned by the display of decorators. * * @return The predicate. */ protected abstract Predicate<Diff> goodCandidate(); /** * Get the views which have to be used as reference to build the related decorators from the given * difference of the value concerned by the difference.<br> * * @param difference * The difference. * @return The list of reference views. */ protected abstract List<View> getReferenceViews(DiagramDiff difference); /** * Get the side where decorators have to be drawn, according to the given reference view and its * match.<br> * * @param match * The match of the reference view. * @param referenceView * The reference view. * @return The side for phantoms. */ protected abstract MergeViewerSide getTargetSide(Match match, View referenceView); /** * It creates new decorators and registers them. * * @param diff * The related difference used as index for the main decorator. * @param referenceView * The reference view as base for creation of the decorator. * @param referenceFigure * The reference figure as base for creation of the decorator. * @param targetSide * The side where the decorator has to be created. * @return The list of main decorators. */ protected abstract AbstractDecorator createAndRegisterDecorator(Diff diff, View referenceView, IFigure referenceFigure, MergeViewerSide targetSide); /** * Get the main decorators related to the given difference. * * @param difference * The difference. * @return The list of main decorators. */ protected abstract Collection<? extends AbstractDecorator> getDecorators(Diff difference); } /** * Phantom manager to create, hide or reveal phantom figures related to deleted or added graphical * objects. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private class PhantomManager extends AbstractDecoratorManager { /** * Phantom represented by a <code>figure</code> on a <code>layer</code>, from the given * <code>side</code> of the merge viewer. An edit part may be linked to the <code>figure</code> in * some cases.<br> * The phantom is related to a <code>difference</code> and it is binded with the reference view and * figure. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private class Phantom extends AbstractDecorator { /** * Constructor. * * @param layer * {@link Phantom#fLayer}. * @param side * {@link Phantom#fSide}. * @param originView * {@link Phantom#fOriginView}. * @param originFigure * {@link Phantom#fOriginFigure}. * @param diff * {@link Phantom#fDifference}. */ Phantom(IFigure layer, MergeViewerSide side, View originView, IFigure originFigure, Diff diff) { setLayer(layer); setSide(side); setOriginView(originView); setOriginFigure(originFigure); setDifference(diff); } /** * Get the decorator dependencies of this one. The dependencies are the decorator ancestors plus * the extremities of an edge decorator.<br> * DO NOT CALL in an iterate of {@link PhantomManager#fPhantomRegistry} * * @return The list of found decorators. */ public List<? extends AbstractDecorator> getDependencies() { List<AbstractDecorator> result = new ArrayList<AbstractDecorator>(); result.addAll(getAncestors()); if (fOriginView instanceof Edge) { View source = ((Edge)fOriginView).getSource(); View target = ((Edge)fOriginView).getTarget(); result.addAll(getOrCreateRelatedPhantoms(source, fSide)); result.addAll(getOrCreateRelatedPhantoms(target, fSide)); } return result; } /** * Get the ancestor decorators of this one. * * @return The list of the ancestors. */ private List<? extends AbstractDecorator> getAncestors() { List<AbstractDecorator> result = new ArrayList<AbstractDecorator>(); EObject parentOriginView = fOriginView.eContainer(); while (parentOriginView != null) { result.addAll(getOrCreateRelatedPhantoms(parentOriginView, fSide)); parentOriginView = parentOriginView.eContainer(); } return result; } } /** Registry of created phantoms, indexed by difference. */ private final Map<Diff, Phantom> fPhantomRegistry = new HashMap<Diff, Phantom>(); /** Predicate witch checks that the given difference is an ADD or DELETE of a graphical object. */ private Predicate<Diff> isAddOrDelete = and(instanceOf(DiagramDiff.class), or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.DELETE))); /** Predicate witch checks that the given difference is a HIDE or REVEAL of a graphical object. */ private Predicate<Diff> isHideOrReveal = or(instanceOf(Show.class), instanceOf(Hide.class)); /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram. * DiagramContentMergeViewer.AbstractDecoratorManager#goodCandidate()<br> * Only the diagram differences ADD/REVEAL or DELETE/HIDE are concerned by this display. */ @Override protected Predicate<Diff> goodCandidate() { return new Predicate<Diff>() { public boolean apply(Diff difference) { return Predicates.or(isAddOrDelete, isHideOrReveal).apply(difference) && difference.getState() == DifferenceState.UNRESOLVED; } }; } /** * From the given view, get or create the related phantoms in the given side. * * @param referenceView * The given view. * @param side * The given side. * @return The list of phantoms. */ private List<Phantom> getOrCreateRelatedPhantoms(EObject referenceView, MergeViewerSide side) { List<Phantom> result = new ArrayList<Phantom>(); Collection<Diff> changes = Collections2.filter( getCompareConfiguration().getComparison().getDifferences(referenceView), goodCandidate()); for (Diff change : changes) { Phantom phantom = fPhantomRegistry.get(change); if (phantom == null) { IFigure referenceFigure = PhantomManager.this.getFigure((View)referenceView); if (referenceFigure != null) { phantom = createAndRegisterDecorator(change, (View)referenceView, referenceFigure, side); } } if (phantom != null) { result.add(phantom); } } return result; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#getReferenceViews(org.eclipse.emf.compare.diagram.DiagramDiff) */ @Override protected List<View> getReferenceViews(DiagramDiff difference) { List<View> result = new ArrayList<View>(); Match match = getCompareConfiguration().getComparison().getMatch(difference.getView()); EObject originObj = match.getOrigin(); EObject leftObj = match.getLeft(); EObject rightObj = match.getRight(); if (leftObj instanceof View || rightObj instanceof View) { View referenceView = getReferenceView((View)originObj, (View)leftObj, (View)rightObj); // It may be null if it is hidden if (referenceView != null) { result.add(referenceView); } } return result; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram. * DiagramContentMergeViewer.AbstractDecoratorManager#getTargetSide(org.eclipse.emf.compare. * Match, org.eclipse.gmf.runtime.notation.View)<br> * If the left object is null, a phantom should be drawn instead. Else, it means that the right * object is null and a phantom should be displayed on the right side. */ @Override protected MergeViewerSide getTargetSide(Match match, View referenceView) { MergeViewerSide targetSide = null; if (!isFigureExist((View)match.getLeft())) { targetSide = MergeViewerSide.LEFT; } else { targetSide = MergeViewerSide.RIGHT; } return targetSide; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#createAndRegisterDecorator(org.eclipse.emf.compare.Diff, * org.eclipse.gmf.runtime.notation.View, org.eclipse.draw2d.IFigure, * org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide) */ @Override protected Phantom createAndRegisterDecorator(Diff diff, View referenceView, IFigure referenceFigure, MergeViewerSide targetSide) { Phantom phantom = createPhantom(diff, referenceView, referenceFigure, targetSide); if (phantom != null) { fPhantomRegistry.put(diff, phantom); } return phantom; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#removeDecorators(org.eclipse.emf.compare.Diff) */ @Override public void removeDecorators(Diff difference) { fPhantomRegistry.remove(difference); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#removeAll() */ @Override public void removeAll() { fPhantomRegistry.clear(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#getDecorators(org.eclipse.emf.compare.Diff) */ @Override protected List<Phantom> getDecorators(Diff difference) { List<Phantom> result = new ArrayList<DiagramContentMergeViewer.PhantomManager.Phantom>(); Phantom phantom = fPhantomRegistry.get(difference); if (phantom != null) { result.add(phantom); } return result; } /** * {@inheritDoc}.<br> * DO NOT CALL on a phantom within an iteration on the phantom registry. * {@link PhantomManager#fPhantomRegistry} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#handleDecorator(org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager.AbstractDecorator, * boolean) */ @Override protected void handleDecorator(AbstractDecorator decorator, boolean isAdd, boolean isMain) { super.handleDecorator(decorator, isAdd, isMain); // Display the dependencies (context) of this decorator for (AbstractDecorator ancestor : ((Phantom)decorator).getDependencies()) { super.handleDecorator(ancestor, isAdd, false); } } @Override protected void handleAddDecorator(AbstractDecorator decorator, IFigure parent, IFigure toAdd, boolean isMain) { super.handleAddDecorator(decorator, parent, toAdd, isMain); // Set the highlight of the figure if (isMain) { decorator.getDecoratorFigure().highlight(); getViewer(decorator.getSide()).getGraphicalViewer().reveal(toAdd); } else { decorator.getDecoratorFigure().unhighlight(); } } /** * It checks that the given view graphically exists. * * @param view * The view. * @return True if it exists. */ private boolean isFigureExist(View view) { return view != null && view.isVisible(); } /** * Get the view which has to be used as reference to build a phantom.<br> * The reference is the non null object among the given objects. In case of delete object, in the * context of three-way comparison, the reference will be the ancestor one (<code>originObj</code>). * * @param originObj * The ancestor object. * @param leftView * The left object. * @param rightView * The right object. * @return The reference object. */ private View getReferenceView(View originObj, View leftView, View rightView) { View referenceView; if (isFigureExist(originObj)) { referenceView = originObj; } else if (isFigureExist(leftView)) { referenceView = leftView; } else { referenceView = rightView; } return referenceView; } /** * It creates a new phantom from the given difference, view and figure. * * @param diff * The related difference used as index for the main phantom. * @param referenceView * The reference view as base for creation of the phantom. * @param referenceFigure * The reference figure as base for creation of the phantom. * @param side * The side where the phantom has to be created. * @return The phantom or null if the target layer is not found. */ private Phantom createPhantom(Diff diff, View referenceView, IFigure referenceFigure, MergeViewerSide side) { IFigure targetLayer = getLayer(referenceView, side); if (targetLayer != null) { MergeViewerSide referenceSide = getSide(referenceView); Rectangle rect = referenceFigure.getBounds().getCopy(); IFigure referenceLayer = getLayer(referenceView, referenceSide); translateCoordinates(referenceFigure, referenceLayer, rect); DecoratorFigure ghost = null; Phantom phantom = new Phantom(targetLayer, side, referenceView, referenceFigure, diff); // Container "list" case if (isNodeList(referenceView)) { int index = getIndex(diff, referenceView, side); IFigure referenceParentFigure = referenceFigure.getParent(); Rectangle referenceParentBounds = referenceParentFigure.getBounds().getCopy(); translateCoordinates(referenceParentFigure, referenceLayer, referenceParentBounds); View parentView = (View)getMatchView(referenceView.eContainer(), side); if (parentView != null) { int nbElements = getVisibleViews(parentView).size(); // CHECKSTYLE:OFF if (index > nbElements) { // CHECKSTYLE:ON index = nbElements; } } // FIXME: The add of decorators modifies the physical coordinates of elements // FIXME: Compute position from the y position of the first child + sum of height of the // children. int pos = rect.height * index + referenceParentBounds.y + 1; Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put(NodeListFigure.PARAM_Y_POS, Integer.valueOf(pos)); ghost = new NodeListFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, rect, true, parameters); // Edge case } else if (referenceView instanceof Edge) { // If the edge phantom ties shapes where their coordinates changed if (hasAnExtremityChange((Edge)referenceView, side)) { EditPart edgeEditPart = createEdgeEditPart((Edge)referenceView, referenceSide, side); // CHECKSTYLE:OFF if (edgeEditPart instanceof GraphicalEditPart) { // CHECKSTYLE:ON phantom.setEditPart(edgeEditPart); IFigure fig = ((GraphicalEditPart)edgeEditPart).getFigure(); fig.getChildren().clear(); ghost = new DecoratorFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, fig, true); } // Else, it creates only a polyline connection figure with the same properties as the // reference } else { if (referenceFigure instanceof PolylineConnection) { ghost = new EdgeFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, rect, true); } } } // Default case: Nodes if (ghost == null) { ghost = new NodeFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, rect, true); } phantom.setDecoratorFigure(ghost); translateWhenInsideContainerChange(phantom); return phantom; } return null; } /** * Get the index of the phantom to draw. * * @param diff * The related difference used as index for the main phantom. * @param referenceView * The reference view to compute the phantom. * @param side * The side where the phantom has to be drawn. * @return The index in the list where the phantom has to be drawn. */ private int getIndex(Diff diff, View referenceView, MergeViewerSide side) { // Case for invisible objects if (diff instanceof Hide || diff instanceof Show) { List<Object> source = null; List<Object> target = null; Object newElement = null; Match match = diff.getMatch(); List<Object> leftList = ReferenceUtil.getAsList(match.getLeft().eContainer(), NotationPackage.Literals.VIEW__PERSISTED_CHILDREN); List<Object> rightList = ReferenceUtil.getAsList(match.getRight().eContainer(), NotationPackage.Literals.VIEW__PERSISTED_CHILDREN); if (diff instanceof Hide) { source = rightList; target = leftList; newElement = diff.getMatch().getRight(); } else { source = leftList; target = rightList; newElement = diff.getMatch().getLeft(); } Iterable<Object> ignoredElements = Iterables.filter(target, new Predicate() { public boolean apply(Object input) { return input instanceof View && !((View)input).isVisible(); } }); return DiffUtil.findInsertionIndex(getCompareConfiguration().getComparison(), ignoredElements, source, target, newElement); } // Case for deleted objects Diff refiningDiff = Iterators.find(diff.getRefinedBy().iterator(), and(valueIs(referenceView), onFeature(NotationPackage.Literals.VIEW__PERSISTED_CHILDREN.getName()))); return DiffUtil.findInsertionIndex(getCompareConfiguration().getComparison(), refiningDiff, side == MergeViewerSide.LEFT); } /** * Get the visible view under the given parent view. * * @param parent * The parent view. * @return The list of views. */ private List<View> getVisibleViews(View parent) { return (List<View>)Lists .newArrayList(Iterators.filter(parent.getChildren().iterator(), new Predicate<Object>() { public boolean apply(Object input) { return input instanceof View && ((View)input).isVisible(); } })); } /** * It translates and resizes the figure of the given phantom when this one is nested in a container * which is subjected to a coordinates change. * * @param phantom * The phantom. */ private void translateWhenInsideContainerChange(Phantom phantom) { boolean isCandidate = false; Diff diff = phantom.getDifference(); if (diff instanceof DiagramDiff) { EObject parent = ((DiagramDiff)diff).getView().eContainer(); while (parent instanceof View && !isCandidate) { isCandidate = Iterables.any( getCompareConfiguration().getComparison().getDifferences(parent), instanceOf(CoordinatesChange.class)); parent = parent.eContainer(); } } if (isCandidate) { View referenceView = phantom.getOriginView(); View parentReferenceView = (View)referenceView.eContainer(); if (parentReferenceView != null) { View parentView = (View)getMatchView(parentReferenceView, phantom.getSide()); IFigure parentFigure = getFigure(parentView); if (parentFigure != null) { Rectangle parentRect = parentFigure.getBounds().getCopy(); translateCoordinates(parentFigure, getLayer(parentReferenceView, getSide(parentView)), parentRect); IFigure parentReferenceFigure = getFigure(parentReferenceView); // CHECKSTYLE:OFF if (parentReferenceFigure != null) { Rectangle parentReferenceRect = parentReferenceFigure.getBounds().getCopy(); translateCoordinates(parentReferenceFigure, getLayer(parentReferenceView, getSide(parentReferenceView)), parentReferenceRect); int deltaX = parentRect.x - parentReferenceRect.x; int deltaY = parentRect.y - parentReferenceRect.y; int deltaWidth = parentRect.width - parentReferenceRect.width; int deltaHeight = parentRect.height - parentReferenceRect.height; IFigure figure = phantom.getFigure(); Rectangle rect = figure.getBounds().getCopy(); rect.x += deltaX; rect.y += deltaY; rect.width += deltaWidth; if (!(figure instanceof Polyline)) { rect.height += deltaHeight; } figure.setBounds(rect); if (figure instanceof Polyline) { Point firstPoint = ((Polyline)figure).getPoints().getFirstPoint().getCopy(); Point lastPoint = ((Polyline)figure).getPoints().getLastPoint().getCopy(); firstPoint.x += deltaX; firstPoint.y += deltaY; lastPoint.x += deltaX + deltaWidth; lastPoint.y += deltaY; ((Polyline)figure).setEndpoints(firstPoint, lastPoint); } } // CHECKSTYLE:ON } } } } /** * It checks that the given edge is linked to graphical objects subjected to coordinate changes, on * the given side. * * @param edge * The edge to check. * @param targetSide * The side to check extremities (side of the phantom). * @return True if an extremity at least changed its location, False otherwise. */ private boolean hasAnExtremityChange(Edge edge, MergeViewerSide targetSide) { View referenceSource = edge.getSource(); View referenceTarget = edge.getTarget(); return hasChange(referenceSource, targetSide) || hasChange(referenceTarget, targetSide); } /** * It checks that the coordinates of the given view changed between left and right, from the given * side. * * @param referenceView * The view to check. * @param targetSide * The side to focus. * @return True if the view changed its location, False otherwise. */ private boolean hasChange(View referenceView, MergeViewerSide targetSide) { View extremity = (View)getMatchView(referenceView, targetSide); // Look for a related change coordinates on the extremity of the edge reference. Collection<Diff> diffs = Collections2.filter( getCompareConfiguration().getComparison().getDifferences(referenceView), instanceOf(CoordinatesChange.class)); if (diffs.isEmpty()) { // Look for a related change coordinates on the matching extremity (other side) of the edge // reference. diffs = Collections2.filter( getCompareConfiguration().getComparison().getDifferences(extremity), instanceOf(CoordinatesChange.class)); } return !diffs.isEmpty(); } /** * It creates and returns a new edit part from the given edge. This edit part listens to the reference * edge but is attached to the controllers of the target (phantom) side. * * @param referenceEdge * The edge as base of the edit part. * @param referenceSide * The side of this edge. * @param targetSide * The side where the edit part has to be created to draw the related phantom. * @return The new edit part. */ private EditPart createEdgeEditPart(Edge referenceEdge, MergeViewerSide referenceSide, MergeViewerSide targetSide) { EditPart edgeEditPartReference = getViewer(referenceSide).getEditPart(referenceEdge); EditPart edgeEditPart = null; if (edgeEditPartReference instanceof ConnectionEditPart) { edgeEditPart = getOrCreatePhantomEditPart(referenceEdge, referenceSide, targetSide); if (edgeEditPart instanceof ConnectionEditPart) { EditPart edgeSourceEp = getOrCreateExtremityPhantomEditPart( ((ConnectionEditPart)edgeEditPartReference).getSource(), referenceSide, targetSide); ((ConnectionEditPart)edgeEditPart).setSource(edgeSourceEp); EditPart edgeTargetEp = getOrCreateExtremityPhantomEditPart( ((ConnectionEditPart)edgeEditPartReference).getTarget(), referenceSide, targetSide); ((ConnectionEditPart)edgeEditPart).setTarget(edgeTargetEp); } } return edgeEditPart; } /** * From the given edit part, it retrieves the matched one, from the given target side. If the * retrieved edit part is not linked to a GMF object, in the target side, a phantom GEF edit part is * returned which will locate a rectangle invisible figure in the same location as the related * phantom. * * @param referenceEdgeExtremityEp * The reference edit part for one of the extremities of an edge. * @param referenceSide * The side of the reference. * @param targetSide * The other side, where the phantom has to be drawn. * @return The phantom edit part used to attach the extremity of an edge phantom. */ private EditPart getOrCreateExtremityPhantomEditPart(EditPart referenceEdgeExtremityEp, MergeViewerSide referenceSide, MergeViewerSide targetSide) { View referenceExtremityView = (View)referenceEdgeExtremityEp.getModel(); EditPart edgeExtremityEp = getOrCreatePhantomEditPart(referenceExtremityView, referenceSide, targetSide); if (isPhantomEditPart((AbstractGraphicalEditPart)edgeExtremityEp)) { final AbstractGraphicalEditPart edgeExtremityEpParent = (AbstractGraphicalEditPart)edgeExtremityEp .getParent(); List<Phantom> phantoms = getOrCreateRelatedPhantoms(referenceExtremityView, targetSide); if (!phantoms.isEmpty()) { Phantom phantomToTarget = phantoms.get(0); final IFigure figureToTarget = phantomToTarget.getFigure(); edgeExtremityEp = new org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart( referenceExtremityView) { @Override protected void createDefaultEditPolicies() { } @Override protected IFigure createFigure() { RectangleFigure fig = new RectangleFigure(); fig.setBounds(figureToTarget.getBounds()); fig.setParent(edgeExtremityEpParent.getFigure()); return fig; } }; edgeExtremityEp.setParent(edgeExtremityEpParent); } ((AbstractGraphicalEditPart)edgeExtremityEp).activate(); ((AbstractGraphicalEditPart)edgeExtremityEp).getFigure(); } return edgeExtremityEp; } /** * It checks if the given edit part is related to a phantom edit part (created for nothing, without * link to a GMF object in the target side). * * @param editPart * The edit part to check. * @return True if it is a phantom edit part, false otherwise. */ private boolean isPhantomEditPart(AbstractGraphicalEditPart editPart) { Rectangle targetBounds = editPart.getFigure().getBounds(); return targetBounds.x == 0 && targetBounds.y == 0 && targetBounds.width == 0 && targetBounds.height == 0; } /** * It creates and returns a new edit part from the given view. This edit part listens the reference * view but is attached to the controllers of the target (phantom) side. * * @param referenceView * The view as base of the edit part. * @param referenceSide * The side of this view. * @param targetSide * The side where the edit part has to be created to draw the related phantom. * @return The new edit part. */ private EditPart getOrCreatePhantomEditPart(EObject referenceView, MergeViewerSide referenceSide, MergeViewerSide targetSide) { EditPart editPartParent = null; EditPart editPart = null; EditPart editPartReference = getViewer(referenceSide).getEditPart(referenceView); EditPart editPartReferenceParent = editPartReference.getParent(); Object referenceViewParent = editPartReferenceParent.getModel(); if (!(referenceViewParent instanceof EObject)) { referenceViewParent = referenceView.eContainer(); } View viewParent = (View)getMatchView((EObject)referenceViewParent, targetSide); if (viewParent != null) { editPartParent = getViewer(targetSide).getEditPart(viewParent); } if (editPartParent == null) { editPartParent = getOrCreatePhantomEditPart((EObject)referenceViewParent, referenceSide, targetSide); } if (editPartParent != null) { View view = (View)getMatchView(referenceView, targetSide); if (view != null) { editPart = getViewer(targetSide).getEditPart(view); } if (editPart == null) { editPart = getViewer(targetSide).getGraphicalViewer().getEditPartFactory() .createEditPart(editPartParent, referenceView); editPart.setParent(editPartParent); getViewer(targetSide).getGraphicalViewer().getEditPartRegistry().put(referenceView, editPart); } } return editPart; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#hideAll() */ public void hideAll() { for (Phantom phantom : fPhantomRegistry.values()) { handleDeleteDecorator(phantom, phantom.getLayer(), phantom.getFigure()); } } } /** * Marker manager to create, hide or reveal marker figures related to deleted or added graphical objects. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private class MarkerManager extends AbstractDecoratorManager { /** * Marker represented by a <code>figure</code> on a <code>layer</code>, from the given * <code>side</code> of the merge viewer. An edit part may be linked to the <code>figure</code> in * some cases.<br> * The marker is related to a <code>difference</code> and it is binded with the reference view and * figure. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private class Marker extends AbstractDecorator { /** * Constructor. * * @param layer * {@link Marker#fLayer}. * @param side * {@link Marker#fSide}. * @param originView * {@link Marker#fOriginView}. * @param originFigure * {@link Marker#fOriginFigure}. * @param diff * {@link Marker#fDifference}. */ Marker(IFigure layer, MergeViewerSide side, View originView, IFigure originFigure, Diff diff) { setLayer(layer); setSide(side); setOriginView(originView); setOriginFigure(originFigure); setDifference(diff); } } /** Registry of created markers, indexed by difference. */ private Multimap<Diff, Marker> fMarkerRegistry = HashMultimap.create(); /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#getReferenceViews(org.eclipse.emf.compare.diagram.DiagramDiff) */ @Override protected List<View> getReferenceViews(DiagramDiff difference) { List<View> result = new ArrayList<View>(); Match matchValue = getCompareConfiguration().getComparison().getMatch(difference.getView()); if (matchValue != null) { if (matchValue.getLeft() != null) { result.add((View)matchValue.getLeft()); } if (matchValue.getRight() != null) { result.add((View)matchValue.getRight()); } if (getCompareConfiguration().getComparison().isThreeWay()) { switch (difference.getKind()) { case DELETE: case CHANGE: case MOVE: result.add((View)matchValue.getOrigin()); break; default: break; } } } return result; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#getTargetSide(org.eclipse.emf.compare.Match, * org.eclipse.gmf.runtime.notation.View) */ @Override protected MergeViewerSide getTargetSide(Match match, View referenceView) { return getSide(referenceView); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#createAndRegisterDecorator(org.eclipse.emf.compare.Diff, * org.eclipse.gmf.runtime.notation.View, org.eclipse.draw2d.IFigure, * org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide) */ @Override protected Marker createAndRegisterDecorator(Diff diff, View referenceView, IFigure referenceFigure, MergeViewerSide targetSide) { Marker marker = createMarker(diff, referenceView, referenceFigure, targetSide); if (marker != null) { fMarkerRegistry.put(diff, marker); } return marker; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#removeDecorators(org.eclipse.emf.compare.Diff) */ @Override public void removeDecorators(Diff difference) { fMarkerRegistry.removeAll(difference); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#removeAll() */ @Override public void removeAll() { fMarkerRegistry.clear(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.AbstractDecoratorManager#getDecorators(org.eclipse.emf.compare.Diff) */ @Override protected Collection<Marker> getDecorators(Diff difference) { return fMarkerRegistry.get(difference); } @Override protected void handleAddDecorator(AbstractDecorator decorator, IFigure parent, IFigure toAdd, boolean isMain) { super.handleAddDecorator(decorator, parent, toAdd, isMain); DiagramMergeViewer viewer = getViewer(decorator.getSide()); EditPart editPart = viewer.getEditPart(decorator.getOriginView()); if (editPart != null) { viewer.getGraphicalViewer().reveal(editPart); } } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram. * DiagramContentMergeViewer.AbstractDecoratorManager#goodCandidate()<br> * All graphical differences are concerned. */ @Override protected Predicate<Diff> goodCandidate() { return new Predicate<Diff>() { public boolean apply(Diff difference) { return instanceOf(DiagramDiff.class).apply(difference); } }; } /** * It creates a new marker from the given difference, view and figure. * * @param diff * The related difference used as index for the main marker. * @param referenceView * The reference view as base for creation of the marker. * @param referenceFigure * The reference figure as base for creation of the marker. * @param side * The side where the marker has to be created. * @return The phantom or null if the target layer is not found. */ private Marker createMarker(Diff diff, View referenceView, IFigure referenceFigure, MergeViewerSide side) { IFigure referenceLayer = getLayer(referenceView, side); if (referenceLayer != null) { Rectangle referenceBounds = referenceFigure.getBounds().getCopy(); translateCoordinates(referenceFigure, referenceLayer, referenceBounds); DecoratorFigure markerFigure = null; Marker marker = new Marker(referenceLayer, side, referenceView, referenceFigure, diff); if (isNodeList(referenceView)) { markerFigure = new NodeListFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, referenceBounds, false); } else if (referenceView instanceof Edge && referenceFigure instanceof PolylineConnection) { markerFigure = new EdgeFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, referenceBounds, false); } // Default case: Nodes if (markerFigure == null) { markerFigure = new NodeFigure(diff, isThreeWay(), getCompareColor(), referenceFigure, referenceBounds, false); } marker.setDecoratorFigure(markerFigure); return marker; } return null; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#hideAll() */ public void hideAll() { for (Marker marker : fMarkerRegistry.values()) { handleDeleteDecorator(marker, marker.getLayer(), marker.getFigure()); } } } /** * Decorator manager to create, hide or reveal all decorator figures related to graphical changes. * * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> */ private class DecoratorsManager implements IDecoratorManager { /** Phantoms manager. */ private IDecoratorManager fPhantomManager = new PhantomManager(); /** Markers manager. */ private IDecoratorManager fMarkerManager = new MarkerManager(); /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#hideDecorators(org.eclipse.emf.compare.Diff) */ public void hideDecorators(Diff difference) { fMarkerManager.hideDecorators(difference); fPhantomManager.hideDecorators(difference); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#revealDecorators(org.eclipse.emf.compare.Diff) */ public void revealDecorators(Diff difference) { fMarkerManager.revealDecorators(difference); fPhantomManager.revealDecorators(difference); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#hideAll() */ public void hideAll() { fMarkerManager.hideAll(); fPhantomManager.hideAll(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#removeDecorators(org.eclipse.emf.compare.Diff) */ public void removeDecorators(Diff difference) { fMarkerManager.removeDecorators(difference); fPhantomManager.removeDecorators(difference); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer.IDecoratorManager#removeAll() */ public void removeAll() { fMarkerManager.removeAll(); fPhantomManager.removeAll(); } } /** * Bundle name of the property file containing all displayed strings. */ private static final String BUNDLE_NAME = DiagramContentMergeViewer.class.getName(); /** The phantom manager to use in the context of this viewer. */ private final DecoratorsManager fDecoratorsManager = new DecoratorsManager(); /** The current "opened" difference. */ private Diff fCurrentSelectedDiff; /** * Creates a new {@link DiagramContentMergeViewer} by calling the super constructor with the given * parameters. * <p> * It calls {@link #buildControl(Composite)} as stated in its javadoc. * <p> * {@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 EMFCompareConfiguration} */ public DiagramContentMergeViewer(Composite parent, EMFCompareConfiguration config) { super(SWT.NONE, ResourceBundle.getBundle(BUNDLE_NAME), config); buildControl(parent); setContentProvider(new TreeContentMergeViewerContentProvider(config)); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getAncestorMergeViewer() */ @SuppressWarnings("unchecked") // see createMergeViewer() to see it is safe @Override public DiagramMergeViewer getAncestorMergeViewer() { return (DiagramMergeViewer)super.getAncestorMergeViewer(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getLeftMergeViewer() */ @SuppressWarnings("unchecked") // see createMergeViewer() to see it is safe @Override public DiagramMergeViewer getLeftMergeViewer() { return (DiagramMergeViewer)super.getLeftMergeViewer(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getRightMergeViewer() */ @SuppressWarnings("unchecked") // see createMergeViewer() to see it is safe @Override public DiagramMergeViewer getRightMergeViewer() { return (DiagramMergeViewer)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.diagram.ide.ui.internal.contentmergeviewer.DiagramCompareContentMergeViewer#createMergeViewer(org.eclipse.swt.widgets.Composite, * org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide) */ @Override protected IMergeViewer createMergeViewer(Composite parent, MergeViewerSide side) { final DiagramMergeViewer diagramMergeViewer = new DiagramMergeViewer(parent, side, getCompareConfiguration()); return diagramMergeViewer; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.DiagramCompareContentMergeViewer#paintCenter(org.eclipse.swt.graphics.GC) */ @Override protected void paintCenter(GC g) { } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.DiagramCompareContentMergeViewer#updateContent(java.lang.Object, * java.lang.Object, java.lang.Object) */ @Override protected void updateContent(Object ancestor, Object left, Object right) { // Delete decorators at each selection of a difference (force the computation) fDecoratorsManager.hideAll(); fDecoratorsManager.removeAll(); super.updateContent(ancestor, left, right); getLeftMergeViewer().getGraphicalViewer().flush(); getRightMergeViewer().getGraphicalViewer().flush(); getAncestorMergeViewer().getGraphicalViewer().flush(); if (left instanceof IDiagramNodeAccessor) { // Compute and display the decorators related to the selected difference (if not merged and // different from the current one) if (left instanceof IDiagramDiffAccessor) { IDiagramDiffAccessor input = (IDiagramDiffAccessor)left; Diff diff = input.getDiff(); // equivalent to getInput().getTarget() if (!isInTerminalState(diff) && diff != fCurrentSelectedDiff) { fDecoratorsManager.revealDecorators(diff); } fCurrentSelectedDiff = diff; } else { fCurrentSelectedDiff = null; } } updateToolItems(); } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#commandStackChanged(java.util.EventObject) */ @Override public void commandStackChanged(EventObject event) { super.commandStackChanged(event); // Delete decorators at each change of the input models (after merging or CTRL-Z, CTRL-Y) Object source = event.getSource(); if (source instanceof CommandStack) { Command command = ((CommandStack)source).getMostRecentCommand(); if (command instanceof CopyCommand) { Iterator<DiagramDiff> diffs = Iterators.filter(command.getAffectedObjects().iterator(), DiagramDiff.class); if (diffs.hasNext()) { // force the computation for the next decorator reveal. fDecoratorsManager.hideAll(); fDecoratorsManager.removeAll(); } } } } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getDiffFrom(org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer) */ @Override protected Diff getDiffFrom(IMergeViewer viewer) { return fCurrentSelectedDiff; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#createControls(org.eclipse.swt.widgets.Composite) */ @Override protected void createControls(Composite composite) { super.createControls(composite); getAncestorMergeViewer().removeSelectionChangedListener(this); getLeftMergeViewer().removeSelectionChangedListener(this); getRightMergeViewer().removeSelectionChangedListener(this); } /** * Utility method to retrieve the {@link DiagramMergeViewer} from the given side. * * @param side * The side to focus. * @return The viewer. */ private DiagramMergeViewer getViewer(MergeViewerSide side) { DiagramMergeViewer result = null; switch (side) { case LEFT: result = getLeftMergeViewer(); break; case RIGHT: result = getRightMergeViewer(); break; case ANCESTOR: result = getAncestorMergeViewer(); break; default: } return result; } /** * Utility method to know the side where is located the given view. * * @param view * The view. * @return The side of the view. */ private MergeViewerSide getSide(View view) { MergeViewerSide result = null; Match match = getCompareConfiguration().getComparison().getMatch(view); if (match.getLeft() == view) { result = MergeViewerSide.LEFT; } else if (match.getRight() == view) { result = MergeViewerSide.RIGHT; } else if (match.getOrigin() == view) { result = MergeViewerSide.ANCESTOR; } return result; } /** * Utility method to get the object matching with the given one, to the given side. * * @param object * The object as base of the lookup. * @param side * The side where the potential matching object has to be retrieved. * @return The matching object. */ private EObject getMatchView(EObject object, MergeViewerSide side) { Match match = getCompareConfiguration().getComparison().getMatch(object); return getMatchView(match, side); } /** * Utility method to get the object in the given side from the given match. * * @param match * The match. * @param side * The side where the potential matching object has to be retrieved. * @return The matching object. */ private EObject getMatchView(Match match, MergeViewerSide side) { EObject result = null; switch (side) { case LEFT: result = match.getLeft(); break; case RIGHT: result = match.getRight(); break; case ANCESTOR: result = match.getOrigin(); break; default: } return result; } }