blob: 9bb2ca4448ddbcc619173a67f56bd0264c57ac99 [file] [log] [blame]
/*******************************************************************************
* <copyright>
*
* Copyright (c) 2005, 2012 SAP AG.
* 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:
* SAP AG - initial API, implementation and documentation
* mwenz - Bug 352440 - Fixed deprecation warnings - contributed by Felix Velasco
* mgorning - Bug 368124 - ConnectionDecorator with Text causes problems
*
* </copyright>
*
*******************************************************************************/
package org.eclipse.graphiti.ui.internal.figures;
import org.eclipse.draw2d.AbstractLocator;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.RotatableDecoration;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
/**
* This is a very flexible Locator, which places a {@link RotatableDecoration}
* or a non-rotatable IFigure on a {@link Connection}. The location is
* determined using a relative distance on the connection (e.g. "0.5" is the
* middle of the connection) and/or using an absolute distance on the connection
* (e.g. "100" is 100 pixel from the anchor).
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class FlexibleRotatableLocator extends AbstractLocator {
/**
* A small data-structure, which contains the important results of the
* calculated location.
*/
private class CalculationResult {
/**
* The calculated location on the connection.
*/
private Point location;
/**
* The start-point of the line-segment of the connection, in which the
* location is placed. It is needed to calculate the angle of the line
* through the location.
*/
private Point segmentStart;
/**
* The end-point of the line-segment of the connection, in which the
* location is placed. It is needed to calculate the angle of the line
* through the location.
*/
private Point segmentEnd;
}
/**
* The connection, as described in {@link #getConnection()}.
*/
private Connection connection;
/**
* If the distance values refer to the start-point or the end-point. See
* {@link #getDistanceToStart()}.
*/
private boolean distanceToStart;
/**
* The relative distance, as described in {@link #getRelativeDistance()}.
*/
private double relativeDistance;
/**
* The absolute distance, as described in {@link #getAbsoluteDistance()}.
*/
private int absoluteDistance;
/**
* The degrees to rotate, as described in {@link #getRotateDegrees()}.
*/
private double rotateDegrees;
/**
* Creates a new FlexibleRotabableLocator.
*
* @param connection
* The connection, as described in {@link #getConnection()}.
* @param distanceToStart
* If the distance values refer to the start-point or the
* end-point. See {@link #getDistanceToStart()}.
* @param relativeDistance
* The relative distance, as described in
* {@link #getRelativeDistance()}.
* @param absoluteDistance
* The absolute distance, as described in
* {@link #getAbsoluteDistance()}.
* @param rotateDegrees
* The degrees to rotate, as described in
* {@link #getRotateDegrees()}.
*/
public FlexibleRotatableLocator(Connection connection, boolean distanceToStart, double relativeDistance,
int absoluteDistance, double rotateDegrees) {
assert (connection != null);
this.connection = connection;
setDistanceToStart(distanceToStart);
setRelativeDistance(relativeDistance);
setAbsoluteDistance(absoluteDistance);
setRotateDegrees(rotateDegrees);
}
/**
* Returns the connection, on which this Locator places the location.
*
* @return The connection, on which this Locator places the location.
*/
protected final Connection getConnection() {
return connection;
}
/**
* Returns true, if the distance values (see {@link #getRelativeDistance()}
* and {@link #getAbsoluteDistance()}) refer to the start point of the
* connection. Returns false, if the distance values refer to the end point
* of the connection. For example if a relative and absolute distance are
* "0", then returning true will place the location on the start point and
* returning false on the end point.
*
* @return If the distance values refer to the start-point or the end-point.
*/
public final boolean getDistanceToStart() {
return distanceToStart;
}
/**
* Sets, If the distance values refer to the start-point or the end-point.
* For details see {@link #getDistanceToStart()}.
*
* @param distanceToStart
* If true, the distance values refer to the start-point.
*/
public final void setDistanceToStart(boolean distanceToStart) {
this.distanceToStart = distanceToStart;
}
/**
* Returns the relative distance of the location to the start/end-point. The
* total distance is calculated by ([relative distance] * [length of
* connection] + [absolute distance]).
*
* @return The relative distance of the location to the start/end-point.
* @see #getAbsoluteDistance()
* @see #getDistanceToStart()
*/
public final double getRelativeDistance() {
return relativeDistance;
}
/**
* Sets the relative distance of the location to the start/end-point. For
* details see {@link #getRelativeDistance()}.
*
* @param relativeDistance
* The relative distance of the location to the start/end-point.
*/
public final void setRelativeDistance(double relativeDistance) {
this.relativeDistance = relativeDistance;
}
/**
* Returns the absolute distance of the location to the start/end-point. The
* total distance is calculated by ([relative distance] * [length of
* connection] + [absolute distance]).
*
* @return The absolute distance of the location to the start/end-point.
* @see #getRelativeDistance()
* @see #getDistanceToStart()
*/
public final int getAbsoluteDistance() {
return absoluteDistance;
}
/**
* Sets the absolute distance of the location to the start/end-point. For
* details see {@link #getAbsoluteDistance()}.
*
* @param absoluteDistance
* The absolute distance of the location to the start/end-point.
*/
public final void setAbsoluteDistance(int absoluteDistance) {
this.absoluteDistance = absoluteDistance;
}
/**
* Returns the degrees, around which the figure shall be rotated, if it is a
* RotatableDecoration. This rotation around a fixed value is done
* additionally to the dynamic rotation depending on the connection.
*
* @return The degrees, around which the figure shall be rotated, if it is a
* RotatableDecoration.
*/
public final double getRotateDegrees() {
return rotateDegrees;
}
/**
* Sets the degrees, around which the figure shall be rotated, if it is a
* RotatableDecoration. For details see {@link #getRotateDegrees()}.
*
* @param rotateDegrees
* The degrees, around which the figure shall be rotated, if it
* is a RotatableDecoration.
*/
public final void setRotateDegrees(double rotateDegrees) {
this.rotateDegrees = rotateDegrees;
}
/**
* Calculates the location based on the current distance values and the
* connection.
*
* @return The location based on the current distance values and the
* connection.
*/
protected CalculationResult calculateLocation() {
PointList pointList = getConnection().getPoints();
assert (pointList.size() >= 2); // all connections must have start point
// and end point
CalculationResult result = new CalculationResult();
// sort the points of the connection (start to end / end to start)
Point allPoints[] = new Point[pointList.size()];
for (int i = 0; i < allPoints.length; i++) {
if (getDistanceToStart()) {
allPoints[i] = pointList.getPoint(i);
} else {
allPoints[(allPoints.length - 1) - i] = pointList.getPoint(i);
}
}
// quick check for improved performance
if (getAbsoluteDistance() == 0 && getRelativeDistance() == 0) {
result.segmentStart = allPoints[0];
result.segmentEnd = allPoints[1];
result.location = result.segmentStart;
return result;
}
if (getAbsoluteDistance() == 0 && getRelativeDistance() == 1) {
result.segmentStart = allPoints[allPoints.length - 2];
result.segmentEnd = allPoints[allPoints.length - 1];
result.location = result.segmentEnd;
return result;
}
// calculate the total length of the connection and of each connection
// segment
double totalLength = 0;
double segmentLength[] = new double[allPoints.length - 1];
for (int i = 0; i < segmentLength.length; i++) {
segmentLength[i] = allPoints[i].getDistance(allPoints[i + 1]);
totalLength += segmentLength[i];
}
// determine total distance
double totalDistance = totalLength * getRelativeDistance() + getAbsoluteDistance();
// determine the target segment
int targetIndex = 0;
double lengthBeforeTargetSegment = 0;
for (targetIndex = 0; targetIndex < segmentLength.length - 1; targetIndex++) {
if (lengthBeforeTargetSegment + segmentLength[targetIndex] < totalDistance) {
lengthBeforeTargetSegment += segmentLength[targetIndex];
} else {
break;
}
}
result.segmentStart = allPoints[targetIndex];
result.segmentEnd = allPoints[targetIndex + 1];
// determine location in target segment
if (segmentLength[targetIndex] == 0) { // both points of segment are
// identical (avoid division by
// zero)
result.location = result.segmentStart;
} else {
double absoluteDistanceInSegment = totalDistance - lengthBeforeTargetSegment;
double relativeDistanceInSegment = absoluteDistanceInSegment / segmentLength[targetIndex];
double locationX = result.segmentStart.x
+ ((result.segmentEnd.x - result.segmentStart.x) * relativeDistanceInSegment);
double locationY = result.segmentStart.y
+ ((result.segmentEnd.y - result.segmentStart.y) * relativeDistanceInSegment);
result.location = new PrecisionPoint(locationX, locationY);
}
return result;
}
/**
* Returns the location on the connection, which is calculated based on the
* current distance values and the connection.
*
* @return The location on the connection.
*/
@Override
protected Point getReferencePoint() {
CalculationResult calculationResult = calculateLocation();
getConnection().translateToAbsolute(calculationResult.location);
return calculationResult.location;
}
/**
* Rotates the figure, if it is a RotatableDecoration.
*
* @param target
* The figure to rotate.
*/
@Override
public void relocate(IFigure target) {
if (target instanceof RotatableDecoration) {
CalculationResult calculationResult = calculateLocation();
RotatableDecoration rotatable = (RotatableDecoration) target;
if (rotatable instanceof GFText) {
GFText text = (GFText) rotatable;
GraphicsAlgorithm ga = text.getGraphicsAlgorithm();
Point textLocation = calculationResult.location.getCopy().translate(ga.getX(), ga.getY());
rotatable.setLocation(textLocation);
} else {
rotatable.setLocation(calculationResult.location);
}
// determine reference point
Point p1 = calculationResult.segmentStart;
Point p2 = calculationResult.segmentEnd;
Point reference;
if (p1.equals(p2)) { // no clear direction -> choose "horizontal"
reference = calculationResult.location.getCopy().translate(-10, 0);
} else { // extend line through [p1, p2] to keep direction
reference = new Point();
reference.x = p2.x + (p2.x - p1.x);
reference.y = p2.y + (p2.y - p1.y);
rotatePoint(calculationResult.location, reference, getRotateDegrees());
}
rotatable.setReferencePoint(reference);
} else {
super.relocate(target);
}
}
protected void rotatePoint(Point center, Point rotate, double degrees) {
if (degrees != 0) {
double radians = Math.toRadians(degrees);
double sin = Math.sin(radians);
double cos = Math.cos(radians);
Point v = new Point(rotate.x - center.x, rotate.y - center.y);
rotate.x = center.x + (int) (cos * v.x - sin * v.y);
rotate.y = center.y + (int) (sin * v.x + cos * v.y);
}
}
}