| /******************************************************************************* |
| * <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); |
| } |
| } |
| } |