blob: b6775046a403a12f6eee19a8324f0d9b145f76a1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram;
import com.google.common.base.Predicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.figures.DecoratorFigure;
import org.eclipse.emf.compare.diagram.internal.extensions.DiagramDiff;
import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
import org.eclipse.emf.compare.rcp.ui.mergeviewer.ICompareColor;
import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
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.View;
/**
* 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>
*/
public 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>
*/
public 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;
}
}
/**
* The compare configuration of the viewer.
*/
private EMFCompareConfiguration fCompareConfiguration;
/**
* The left area of the viewer.
*/
private DiagramMergeViewer fLeft;
/**
* The right area of the viewer.
*/
private DiagramMergeViewer fRight;
/**
* The ancestor area of the viewer.
*/
private DiagramMergeViewer fAncestor;
/**
* The color of the difference.
*/
private ICompareColor fColor;
/**
* Constructor.
*
* @param compareConfiguration
* The compare configuration of the viewer.
* @param left
* The left area of the viewer.
* @param right
* The right area of the viewer.
* @param ancestor
* The ancestor area of the viewer.
* @param color
* The color of the difference.
*/
protected AbstractDecoratorManager(EMFCompareConfiguration compareConfiguration, DiagramMergeViewer left,
DiagramMergeViewer right, DiagramMergeViewer ancestor, ICompareColor color) {
fCompareConfiguration = compareConfiguration;
fLeft = left;
fRight = right;
fAncestor = ancestor;
fColor = color;
}
/**
* Returns the compare configuration of this viewer.
*
* @return the compare configuration, never <code>null</code>
*/
protected EMFCompareConfiguration getCompareConfiguration() {
return fCompareConfiguration;
}
/**
* Returns the left area of the viewer.
*
* @return the fLeft
*/
protected DiagramMergeViewer getLeftMergeViewer() {
return fLeft;
}
/**
* Returns the right area of the viewer.
*
* @return the fRight
*/
protected DiagramMergeViewer getRightMergeViewer() {
return fRight;
}
/**
* Returns the ancestor area of the viewer.
*
* @return the fAncestor
*/
protected DiagramMergeViewer getAncestorMergeViewer() {
return fAncestor;
}
/**
* Return whether the input is a three-way comparison.
*
* @return whether the input is a three-way comparison
*/
protected boolean isThreeWay() {
return getCompareConfiguration().getComparison().isThreeWay();
}
/**
* Returns the ICompareColor.
*
* @return the ICompareColor.
*/
public ICompareColor getCompareColor() {
return fColor;
}
/**
* 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.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.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 {@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, getEffectiveSide(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;
}
/**
* Utility method to retrieve the {@link DiagramMergeViewer} from the given side.
*
* @param side
* The side to focus.
* @return The viewer.
*/
public 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.
*/
public 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 getEffectiveSide(result);
}
/**
* Returns the effective side taking into account {@link CompareConfiguration#isMirrored()} to switch left
* and right.
*
* @param side
* The side where the potential matching object has to be retrieved.
* @return the effective side with respect to mirroring.
*/
protected MergeViewerSide getEffectiveSide(MergeViewerSide side) {
if (side != null && getCompareConfiguration().isMirrored()) {
return side.opposite();
}
return side;
}
/**
* 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.
*/
public 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;
}
/**
* 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);
}