blob: 51c84c2b781e0cd70865a600bb5a362ed4971789 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}