blob: 6b1ad514a70885dda3cbf76bf6feb10db79b7687 [file] [log] [blame]
package org.eclipse.gmf.tooling.runtime.linklf.router;
import java.util.IdentityHashMap;
import java.util.Map;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.draw2d.ui.figures.FigureUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.figures.OrthogonalConnectionAnchor;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
import org.eclipse.gmf.tooling.runtime.linklf.DiagramGridSpec;
import org.eclipse.gmf.tooling.runtime.linklf.SlidableSnapToGridAnchor;
import org.eclipse.gmf.tooling.runtime.providers.router.SnapToGridRouter;
/**
* Extends the standard GMF Runtime rectilinear router with following
* improvements:
* <ul>
* <li>respects Snap to grid for the implicit bendpoints introduced when
* switching from oblique to recti- routing</li>
* <li>enforces that the last / first segment is always orthogonal to the node
* side, avoiding the visual anchor position different to the user-set anchor
* position. The latter happens with default RectilinearRouter when first or
* last segment is parallel to (and in fact hidden by) the node side</li>
* <li>routes the segments from/to affixed items orthogonal to the side of the
* parent node to which affixed node is attached</li>
* </ul>
*
* @since 3.3
*/
public class LinkLFRectilinearRouter extends RectilinearRouter2 implements
HintedOrthogonalRouter, SnapToGridRouter {
private final Map<Connection, EndRoutingHint> myEndRoutingHints = new IdentityHashMap<Connection, EndRoutingHint>();
private DiagramGridSpec myGridSpec;
public LinkLFRectilinearRouter() {
super();
}
public EndRoutingHint getDefaultEndRoutingStrategy() {
return EndRoutingHint.FixAnchorMoveBendpoint;
}
@Override
public void setEditPartViewer(EditPartViewer viewer) {
if (myGridSpec != null && myGridSpec.getViewer() == viewer) {
return;
}
if (myGridSpec != null) {
myGridSpec.dispose();
}
myGridSpec = viewer == null ? null : new DiagramGridSpec(viewer);
}
@Override
public void setEndRoutingHint(Connection conn, EndRoutingHint hint) {
if (hint == getDefaultEndRoutingStrategy()) {
hint = null;
}
if (hint == null) {
myEndRoutingHints.remove(conn);
} else {
myEndRoutingHints.put(conn, hint);
}
}
/**
* While {@link RectilinearRouter} claims to ignore the endpoints, it still
* passes them into the
* {@link #removePointsInViews(Connection, PointList, Point, Point)} method.
*
* This leads to 2 problems: - the one described in the bug #451604 - even
* if we fix the bug above by providing more hints, this added middle point
* is not snapped when should be
* <p/>
*
* So this class ensures that the endpoints are reset to anchor positions
* befor routing
*/
@Override
public void routeLine(Connection conn, int nestedRoutingDepth,
PointList newLine) {
if (nestedRoutingDepth == 0 && newLine.size() >= 2) {
Point sourceLoc = conn.getSourceAnchor().getReferencePoint();
Point targetLoc = conn.getTargetAnchor().getReferencePoint();
conn.translateToRelative(sourceLoc);
conn.translateToRelative(targetLoc);
newLine.setPoint(sourceLoc, 0);
newLine.setPoint(targetLoc, newLine.size() - 1);
}
super.routeLine(conn, nestedRoutingDepth, newLine);
}
/**
* @see org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter#resetEndPointsToEdge(org.eclipse.draw2d.Connection,
* org.eclipse.draw2d.geometry.PointList)
*
* @param conn
* @param line
*/
@Override
protected void resetEndPointsToEdge(Connection conn, PointList line) {
if (isReorienting(conn)) {
/*
* If the connection doesn't have a shape as a source or target
* we'll let the oblique router to do the work. The connection
* doesn't need to be rectilinear at this point. There is no support
* for making a rectilinear connection for which one of the ends is
* not connected to anything.
*/
super.resetEndPointsToEdge(conn, line);
return;
}
PrecisionRectangle source = sourceBoundsRelativeToConnection2(conn);
PrecisionRectangle target = targetBoundsRelativeToConnection2(conn);
int offSourceDirection = PositionConstants.NONE;
int offTargetDirection = PositionConstants.NONE;
int sourceAnchorRelativeLocation = PositionConstants.NONE;
int targetAnchorRelativeLocation = PositionConstants.NONE;
SnapToGrid snapper = getSnapper();
if (line.size() == 0) {
/*
* No valid bend points. We can't call super, so we will do the work
* from RouterHelper ourselves
*/
PrecisionPoint sourceReference = new PrecisionPoint(conn
.getTargetAnchor().getReferencePoint());
PrecisionPoint sourceAnchorPoint = new PrecisionPoint(conn
.getSourceAnchor().getLocation(sourceReference));
PrecisionPoint targetAnchorPoint = new PrecisionPoint(conn
.getTargetAnchor().getLocation(sourceAnchorPoint));
conn.translateToRelative(sourceAnchorPoint);
conn.translateToRelative(targetAnchorPoint);
line.addPoint(sourceAnchorPoint);
line.addPoint(targetAnchorPoint);
sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
sourceAnchorPoint, source);
targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
targetAnchorPoint, target);
insertPointsProducingNotAlignedRectilinearSegments(conn, line,
sourceAnchorRelativeLocation, targetAnchorRelativeLocation,
snapper);
offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
} else {
if (conn.getSourceAnchor() instanceof OrthogonalConnectionAnchor) {
addSegmentToSourceAnchor(line, conn,
(OrthogonalConnectionAnchor) conn.getSourceAnchor());
} else {
/*
* If anchor is not supporting orthogonal connections we'll use
* the oblique connection anchors and then convert it to
* rectilinear.
*/
PrecisionPoint reference = new PrecisionPoint(
line.getFirstPoint());
conn.getSourceAnchor().getOwner()
.translateToAbsolute(reference);
PrecisionPoint anchorLocation = new PrecisionPoint(conn
.getSourceAnchor().getLocation(reference));
conn.translateToRelative(anchorLocation);
line.insertPoint(anchorLocation, 0);
}
if (conn.getTargetAnchor() instanceof OrthogonalConnectionAnchor) {
addSegmentToTargetAnchor(line, conn,
(OrthogonalConnectionAnchor) conn.getTargetAnchor());
} else {
/*
* If anchor is not supporting orthogonal connections we'll use
* the oblique connection anchors and then convert it to
* rectilinear.
*/
PrecisionPoint reference = new PrecisionPoint(
line.getLastPoint());
conn.getSourceAnchor().getOwner()
.translateToAbsolute(reference);
PrecisionPoint anchorLocation = new PrecisionPoint(conn
.getTargetAnchor().getLocation(reference));
conn.translateToRelative(anchorLocation);
line.addPoint(anchorLocation);
}
sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
line.getFirstPoint(), source);
offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
line.getLastPoint(), target);
offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
}
/*
* Convert the polyline to rectilinear. If the connection is rectilinear
* already then the connection will remain as it is.
*/
OrthogonalRouterUtilities.transformToOrthogonalPointList(line,
offSourceDirection, offTargetDirection);
removeRedundantPoints2(line);
}
/**
* We need to find two points offset from the source and target anchors
* outside the shapes such that when the polyline is converted to
* rectilinear from oblique we won't have rectilinear line segments alligned
* with source or target shapes edges.
* <p/>
* Copy-pasted from {@link RectilinearRouter} lines 416.
*/
@Deprecated
public static void insertPointsProducingNotAlignedRectilinearSegments(
PointList line, int sourceAnchorRelativeLocation,
int targetAnchorRelativeLocation) {
Point offStart = line.getFirstPoint();
Point offEnd = line.getLastPoint();
Dimension offsetDim = offStart.getDifference(offEnd).scale(0.5);
offStart.translate(getTranslationValue2(sourceAnchorRelativeLocation,
Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
offEnd.translate(getTranslationValue2(targetAnchorRelativeLocation,
Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
line.insertPoint(offStart, 1);
line.insertPoint(offEnd, 2);
}
public static void insertPointsProducingNotAlignedRectilinearSegments(
Connection conn, PointList line, int sourceAnchorRelativeLocation,
int targetAnchorRelativeLocation, SnapToGrid snapper) {
insertPointsProducingNotAlignedRectilinearSegments(line,
sourceAnchorRelativeLocation, targetAnchorRelativeLocation);
if (asVerticalOrHorizontal(sourceAnchorRelativeLocation) != asVerticalOrHorizontal(targetAnchorRelativeLocation)) {
// for "|_"-like routing we don't need snapping, the added bendpoint
// is determined fully by anchors
return;
}
// so we are in "_|~"-like routing
if (snapper != null) {
PrecisionPoint addedForSource = new PrecisionPoint(line.getPoint(1));
PrecisionPoint addedForSourceAbs = makeAbsolute(conn,
addedForSource.getPreciseCopy());
PrecisionPoint snappedForSourceAbs = addedForSourceAbs
.getPreciseCopy();
PrecisionPoint addedForTarget = new PrecisionPoint(line.getPoint(2));
PrecisionPoint addedForTargetAbs = makeAbsolute(conn,
addedForTarget.getPreciseCopy());
PrecisionPoint snappedForTargetAbs = addedForTargetAbs
.getPreciseCopy();
snapper.snapPoint(null,
asVerticalOrHorizontal(sourceAnchorRelativeLocation),
addedForSourceAbs, snappedForSourceAbs);
snapper.snapPoint(null,
asVerticalOrHorizontal(targetAnchorRelativeLocation),
addedForTargetAbs, snappedForTargetAbs);
PrecisionPoint snappedForSource = makeRelative(conn,
snappedForSourceAbs.getPreciseCopy());
PrecisionPoint snappedForTarget = makeRelative(conn,
snappedForTargetAbs.getPreciseCopy());
if (snappedForSource.getDistance(addedForSource) <= snappedForTarget
.getDistance(addedForTarget)) {
Dimension delta = snappedForSource
.getDifference(addedForSource);
line.setPoint(snappedForSource, 1);
line.setPoint(addedForTarget.getTranslated(delta), 2);
} else {
Dimension delta = snappedForTarget
.getDifference(addedForTarget);
line.setPoint(addedForSource.getTranslated(delta), 1);
line.setPoint(snappedForTarget, 2);
}
}
}
public static int asVerticalOrHorizontal(int direction) {
return getOffShapeDirection2(direction);
}
protected SnapToGrid getSnapper() {
if (myGridSpec == null || myGridSpec.getAbsoluteGridSpec() == null) {
return null;
}
return new SnapToGrid((GraphicalEditPart) myGridSpec.getViewer()
.getContents());
}
protected void addSegmentToSourceAnchor(PointList line, Connection conn,
OrthogonalConnectionAnchor anchor) {
Point firstBend = line.getFirstPoint();
int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
: getSegmentOrientation(firstBend, line.getPoint(1));
Point[] prependSegment = computeFirstOrLastSegment(conn, anchor,
firstBend, prevSegmentOrientation);
for (int i = 0; i < prependSegment.length; i++) {
Point next = prependSegment[i];
if (i == prependSegment.length - 1 && next.equals(firstBend)) {
continue; // actually break
}
line.insertPoint(next, i);
}
}
protected void addSegmentToTargetAnchor(PointList line, Connection conn,
OrthogonalConnectionAnchor anchor) {
Point lastBend = line.getLastPoint();
int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
: getSegmentOrientation(lastBend,
line.getPoint(line.size() - 2));
Point[] appendSegment = computeFirstOrLastSegment(conn, anchor,
lastBend, prevSegmentOrientation);
for (int i = appendSegment.length - 1; i >= 0; i--) {
Point next = appendSegment[i];
if (i == appendSegment.length - 1 && next.equals(lastBend)) {
continue;
}
line.addPoint(next);
}
}
/**
* Returns the array of point that represents the routing from the point at
* figure bounds to the given first or last bendpoint. Result array is
* ordered from the figure bounds, so the 0-th index always represent the
* point at figure.
*/
protected Point[] computeFirstOrLastSegment(Connection conn,
OrthogonalConnectionAnchor anchor, Point bendpoint,
int prevSegmentOrientation) {
LineSeg fromFigureToBendpoint = OrthogonalRouterUtilities
.getOrthogonalLineSegToAnchorLoc(conn, anchor, bendpoint);
Point figurePoint = fromFigureToBendpoint.getOrigin();
Point syntheticBend = bendpoint.getCopy();
EndRoutingHint hint = findEndRoutingHint(conn, anchor);
if (hint == EndRoutingHint.FixAnchorMoveBendpoint) {
Point bendpointInAbsCoordinates = bendpoint.getCopy();
conn.translateToAbsolute(bendpointInAbsCoordinates);
int orientation = getOrientationAgainst(anchor,
bendpointInAbsCoordinates);
if (orientation == PositionConstants.NONE) {
orientation = getPreferredOutgoingDirection(conn, anchor);
}
if (orientation == PositionConstants.NONE) {
orientation = flipOrientation(prevSegmentOrientation);
}
Point refPoint = anchor.getReferencePoint().getCopy();
conn.translateToRelative(refPoint);
switch (orientation) {
case PositionConstants.VERTICAL:
syntheticBend.setX(refPoint.x());
break;
case PositionConstants.HORIZONTAL:
syntheticBend.setY(refPoint.y());
break;
default:
// Activator.log.error(new
// IllegalStateException("Unexpected orientation: " +
// orientation + //
// ", bendpoint-abs: " + bendpointInAnchorCoordinates +
// ", bendpoint-link-rel:: [" + bendpoint + "], " + //
// ", anchor: " + anchor.getReferencePoint()
// ));
break;
}
fromFigureToBendpoint = OrthogonalRouterUtilities
.getOrthogonalLineSegToAnchorLoc(conn, anchor,
syntheticBend);
figurePoint = fromFigureToBendpoint.getOrigin();
}
Point[] result = new Point[] { figurePoint, syntheticBend };
return result;
}
/**
* [GMFRT] extract to utility method
*
* This code is partial copy of the
* {@link BaseSlidableAnchor#getOrthogonalLocation(Point)}.
*
* @deprecated
*/
private int getOrientationAgainst(ConnectionAnchor anchor,
Point orthoReference) {
// Note that <code>ownReference</code> is not used besides call the
// getClosestSide()
// so in contrast to the original code in BaseSlidableAnchor, we have
// commented out useless updates
PrecisionPoint ownReference = new PrecisionPoint(
anchor.getReferencePoint());
PrecisionRectangle bounds = new PrecisionRectangle(
FigureUtilities.getAnchorableFigureBounds(anchor.getOwner()));
anchor.getOwner().translateToAbsolute(bounds);
bounds.expand(0.000001, 0.000001);
PrecisionPoint preciseOrthoReference = new PrecisionPoint(
orthoReference);
int orientation = PositionConstants.NONE;
if (bounds.contains(preciseOrthoReference)) {
int side = SlidableSnapToGridAnchor.getClosestSide2(ownReference,
bounds);
switch (side) {
case PositionConstants.LEFT:
case PositionConstants.RIGHT:
// ownReference.preciseY = preciseOrthoReference.preciseY();
orientation = PositionConstants.HORIZONTAL;
break;
case PositionConstants.TOP:
case PositionConstants.BOTTOM:
// ownReference.preciseX = preciseOrthoReference.preciseX();
orientation = PositionConstants.VERTICAL;
break;
}
} else if (preciseOrthoReference.preciseX >= bounds.preciseX
&& preciseOrthoReference.preciseX <= bounds.preciseX
+ bounds.preciseWidth) {
// ownReference.preciseX = preciseOrthoReference.preciseX;
orientation = PositionConstants.VERTICAL;
} else if (preciseOrthoReference.preciseY >= bounds.preciseY
&& preciseOrthoReference.preciseY <= bounds.preciseY
+ bounds.preciseHeight) {
// ownReference.preciseY = preciseOrthoReference.preciseY;
orientation = PositionConstants.HORIZONTAL;
}
// ownReference.updateInts();
return orientation;
}
protected EndRoutingHint findEndRoutingHint(Connection conn,
OrthogonalConnectionAnchor anchor) {
EndRoutingHint result = myEndRoutingHints.get(conn);
if (result == null) {
result = getDefaultEndRoutingStrategy();
}
return result;
}
protected static int flipOrientation(int verticalOrHorizontal) {
switch (verticalOrHorizontal) {
case PositionConstants.VERTICAL:
return PositionConstants.HORIZONTAL;
case PositionConstants.HORIZONTAL:
return PositionConstants.VERTICAL;
default:
return PositionConstants.NONE;
}
}
protected static int getSegmentOrientation(Point start, Point end) {
if (start.x() == end.x()) {
return PositionConstants.VERTICAL;
}
if (start.y() == end.y()) {
return PositionConstants.HORIZONTAL;
}
return PositionConstants.NONE;
}
/**
* When anchor is placed directly at the anchorable bound of its owner, this
* method returns the direction orthogonal to the side this anchor is
* attached to. Returns {@link PositionConstants#NONE} in all other cases.
*
* @return {@link PositionConstants#HORIZONTAL} or
* {@link PositionConstants#VERTICAL} or
* {@link PositionConstants#NONE}
*/
protected int getPreferredOutgoingDirection(Connection conn,
ConnectionAnchor anchor) {
final double TOLERANCE = 2;
IFigure owner = anchor.getOwner();
if (owner == null) {
return PositionConstants.NONE;
}
PrecisionRectangle bounds = new PrecisionRectangle(
FigureUtilities.getAnchorableFigureBounds(owner));
owner.translateToAbsolute(bounds);
conn.translateToRelative(bounds);
PrecisionPoint anchorLoc = new PrecisionPoint(
anchor.getReferencePoint());
conn.translateToRelative(anchorLoc);
if (Math.abs(anchorLoc.preciseX() - bounds.preciseX()) < TOLERANCE) {
return PositionConstants.HORIZONTAL; // .WEST
}
if (Math.abs(anchorLoc.preciseX() - bounds.preciseRight()) < TOLERANCE) {
return PositionConstants.HORIZONTAL; // .EAST
}
if (Math.abs(anchorLoc.preciseY() - bounds.preciseY()) < TOLERANCE) {
return PositionConstants.VERTICAL; // .NORTH
}
if (Math.abs(anchorLoc.preciseY() - bounds.preciseBottom()) < TOLERANCE) {
return PositionConstants.VERTICAL; // .SOUTH
}
return PositionConstants.NONE;
}
}