/*******************************************************************************
* Copyright (c) 2010-2015 Henshin developers. 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:
* TU Berlin, University of Luxembourg, SES S.A.
*******************************************************************************/
package de.tub.tfs.muvitor.animation;
import org.eclipse.core.runtime.Assert;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
/**
* <p>
* Data class describing a location (possibly indirect through a model element)
* and the size factor an {@link AnimatedElement} should be set to when reaching
* this {@link Localizer} while following its path. <br>
* Some methods allow retrieving absolute values respecting a viewport's offset
* and zoom scale. <br>
* A Localizer may need interpolation before resolving it's location or size
* factor, serving as a place holder in an {@link AnimatedElement}s path.
* </p>
*
* <p>
* This class is not intended for direct usage. It will be used properly by
* {@link AnimatedElement}s.
* </p>
*
* @author Tony Modica
*/
final class Localizer {
/**
* The absolute location to be taken into account when resolving the bounds
* of this Localizer.
*/
private final Point absoluteLocation;
/**
* A target model element specifying a location which will be retrieved in a
* viewer context by it's edit parts.
*/
private final EObject targetModel;
/**
* Transient data field used to store resolved and interpolated results. May
* be null if no editPart for targetModel could be found in viewer. This
* should be accessed only after calling
* {@link #resolveLocation(EditPartViewer)} and, if needed, interpolating in
* AnimatedElement.
*/
Point resolvedLocation;
/**
* The relative size factor to be taken into account when resolving the
* bounds of this Localizer. A -1 will mark it as to be interpolated. This
* field will be overwritten with the appropriate value by the
* interpolation!
*/
double sizeFactor;
/**
* Main constructor for {@link Localizer}s with a relative size
* specification. If the passed object is <code>null</code> this
* {@link Localizer} will need to be interpolated.
*
* @param object
* Object used to resolve location of this Localizer in viewers.
* Use <code>null</code> if location is meant to be interpolated.
* @param sizeFactor
* double scaling factor used to resolve size of this Localizer
* in viewers. Use 1 for no size change and -1 if size is meant
* to be interpolated.
*
*/
protected Localizer(final Object object, final double sizeFactor) {
// to avoid zero-division we do not allow a size factor of 0
if (sizeFactor != 0) {
this.sizeFactor = sizeFactor;
} else {
this.sizeFactor = 0.001;
}
if (object instanceof EObject) {
targetModel = (EObject) object;
absoluteLocation = null;
} else if (object instanceof GraphicalEditPart) {
targetModel = (EObject) ((GraphicalEditPart) object).getModel();
absoluteLocation = null;
} else if (object instanceof Point) {
targetModel = null;
absoluteLocation = (Point) object;
} else if (object == null) {
targetModel = null;
absoluteLocation = null;
} else {
throw new IllegalArgumentException(
"Localizer must be defined with EObject, GraphicalEditPart or Point!");
}
}
@Override
final public String toString() {
final StringBuilder buffer = new StringBuilder();
if (absoluteLocation != null) {
buffer.append("!");
buffer.append(absoluteLocation);
}
if (sizeFactor != -1) {
buffer.append("Size<");
buffer.append(sizeFactor);
buffer.append(">");
}
buffer.append("@");
if (needsInterpolation(true)) {
buffer.append("L?");
}
if (resolvedLocation != null) {
buffer.append(resolvedLocation);
}
if (needsInterpolation(true)) {
buffer.append("S?");
}
if (null != targetModel) {
buffer.append(targetModel);
}
return buffer.toString();
}
/**
* Check if location/size has to be interpolated.
*
* @param forLocation
* switch to choose between interrogation of possible location or
* size interpolation
* @return whether the location/size of this localizer would not be resolved
* completely by {@link #resolveLocation(EditPartViewer)}.
*/
final boolean needsInterpolation(final boolean forLocation) {
if (forLocation) {
return absoluteLocation == null && targetModel == null;
}
return sizeFactor <= 0;
}
/**
* <p>
* Tries to resolve the center location the figure should be set to when
* reaching this Localizer. These bounds will be absolute, i.e. relative to
* a zoom level of 1.0 and a viewport offset of (0,0)!
* </p>
*
* <p>
* This method should be called always when considering a Localizer in a
* viewer context. Afterwards it may still need to be interpolated which can
* be checked with {@link #needsInterpolation(boolean)};
*
* @param viewer
* The viewer context to resolve locations and sizes of the
* actual figures
* @param animatedModel
* The model of the host AnimatedElement. Used for resolving a
* more appropriate location here. This may be null, the animated
* element is a custom figure without hosting edit part then.
* @return The resolved bounds for convenience. May be <code>null</code> if
* no edit part can be found for the target model or if
* interpolation is needed.
*
* @see #needsInterpolation(boolean)
*/
final Point resolveLocation(final EditPartViewer viewer, final EObject animatedModel) {
// shortcut if already resolved
if (resolvedLocation != null) {
return resolvedLocation;
}
Assert.isTrue(viewer != null, "Localizer needs viewer for resolving!");
/*
* if a model has been set as locating object find the figure of its
* editpart and translate the location to absolute
*/
if (targetModel != null) {
final GraphicalEditPart targetEditPart = (GraphicalEditPart) viewer
.getEditPartRegistry().get(targetModel);
if (targetEditPart == null) {
return null;
}
/*
* If target model is the animated model then use models figure's
* bounds, otherwise use the content pane's bounds. This looks much
* better and prevents the figure to hop in the first and last step,
* if it starts/ends on its original positions
*/
final IFigure targetFigure = targetModel == animatedModel ? targetEditPart.getFigure()
: targetEditPart.getContentPane();
resolvedLocation = targetFigure.getBounds().getCenter();
targetFigure.translateToAbsolute(resolvedLocation);
final Point viewportOffset = ((FigureCanvas) viewer.getControl()).getViewport()
.getViewLocation();
resolvedLocation.translate(viewportOffset);
final ScalableFreeformRootEditPart root = (ScalableFreeformRootEditPart) viewer
.getRootEditPart();
final double zoomLevel = root.getZoomManager().getZoom();
resolvedLocation.scale(1 / zoomLevel);
} else if (absoluteLocation != null) {
resolvedLocation = absoluteLocation;
}
return resolvedLocation;
}
}