/***************************************************************************** * Copyright (c) 2010 CEA * * * 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: * Atos Origin - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.sequence.draw2d.routers; import org.eclipse.draw2d.Connection; 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.draw2d.geometry.Ray; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gmf.runtime.common.core.util.StringStatics; 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.PointListUtilities; import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.ObliqueRouter; import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities; import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil; import org.eclipse.papyrus.uml.diagram.sequence.edit.helpers.AnchorHelper; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.AbstractMessageEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart.LifelineFigure; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.Message4EditPart.MessageCreate; /** * A multi behavior router which enable to draw message. * It can behave as an oblique router (with no bendpoint), or as an horizontal router (with no bendpoint), * or as a rectilinear router with 2 bendpoints. * * @author mvelten and vhemery */ @SuppressWarnings({ "restriction", "deprecation" }) public class MessageRouter extends ObliqueRouter { private static final int MAX_DELTA = 10; public static enum RouterKind { HORIZONTAL, OBLIQUE, SELF; public static RouterKind getKind(Connection conn, PointList newLine) { if(isSelfConnection(conn)) { return SELF; } if(isHorizontalConnection(conn, newLine)) { return HORIZONTAL; } return OBLIQUE; } private static boolean isHorizontalConnection(Connection conn, PointList newLine) { /* apex improved start */ if (!(conn instanceof AbstractMessageEditPart.MessageFigure)) { return false; } /* apex improved end */ /* apex replaced if(!(conn instanceof Message2EditPart.MessageAsync)) { return false; } */ Point sourcePoint = newLine.getFirstPoint(); Point targetPoint = newLine.getLastPoint(); return Math.abs(sourcePoint.y - targetPoint.y) <= MAX_DELTA; } /** * It is self if the parent lifeline is the same. */ private static boolean isSelfConnection(Connection conn) { if(conn == null || conn.getSourceAnchor() == null || conn.getTargetAnchor() == null) { return false; } IFigure sourceLifeline = conn.getSourceAnchor().getOwner(); while(sourceLifeline != null && !(sourceLifeline instanceof LifelineFigure)) { sourceLifeline = sourceLifeline.getParent(); } IFigure targetLifeline = conn.getTargetAnchor().getOwner(); while(targetLifeline != null && !(targetLifeline instanceof LifelineFigure)) { targetLifeline = targetLifeline.getParent(); } return sourceLifeline != null && sourceLifeline.equals(targetLifeline); } } @Override public void routeLine(Connection conn, int nestedRoutingDepth, PointList newLine) { Point sourcePoint, targetPoint; switch(RouterKind.getKind(conn, newLine)) { case HORIZONTAL: originalRectilinearRouteLine(conn, nestedRoutingDepth, newLine); // force 2 bendpoints on the same Y coordinate sourcePoint = newLine.getFirstPoint(); targetPoint = newLine.getLastPoint(); targetPoint.y = sourcePoint.y; newLine.removeAllPoints(); newLine.addPoint(sourcePoint); newLine.addPoint(targetPoint); break; case OBLIQUE: super.routeLine(conn, nestedRoutingDepth, newLine); adjustCreateEndpoint(conn, newLine); // force 2 bendpoints only if(newLine.size() > 2) { sourcePoint = newLine.getFirstPoint(); targetPoint = newLine.getLastPoint(); newLine.removeAllPoints(); newLine.addPoint(sourcePoint); newLine.addPoint(targetPoint); } break; case SELF: // Handle special routing: self connections and intersecting shapes connections if(checkSelfRelConnection(conn, newLine)) { super.resetEndPointsToEdge(conn, newLine); OrthogonalRouterUtilities.transformToOrthogonalPointList(newLine, getOffShapeDirection(getAnchorOffRectangleDirection(newLine.getFirstPoint(), sourceBoundsRelativeToConnection(conn))), getOffShapeDirection(getAnchorOffRectangleDirection(newLine.getLastPoint(), targetBoundsRelativeToConnection(conn)))); removeRedundantPoints(newLine); return; } break; } } protected boolean checkShapesIntersect(Connection conn, PointList newLine) { if(conn.getTargetAnchor().getOwner() instanceof AnchorHelper.CombinedFragmentNodeFigure){ return false; } return super.checkShapesIntersect(conn, newLine); } protected void adjustCreateEndpoint(Connection conn, PointList newLine) { if(conn instanceof MessageCreate){ if(newLine.size() >= 2){ Point start = newLine.getFirstPoint(); Point end = newLine.getLastPoint(); if(start.y != end.y){ start.y = end.y; newLine.setPoint(start, 0); } } } } @Override protected void getSelfRelVertices(Connection conn, PointList newLine) { rectilinearResetEndPointsToEdge(conn, newLine); IFigure owner = conn.getSourceAnchor().getOwner(); Point middle = owner.getBounds().getCenter(); // ensure that the end points are anchored properly to the shape. Point ptS2 = newLine.getPoint(0); Point ptS1 = conn.getSourceAnchor().getReferencePoint(); conn.translateToRelative(ptS1); Point ptAbsS2 = new Point(ptS2); conn.translateToAbsolute(ptAbsS2); Point ptEdge = conn.getSourceAnchor().getLocation(ptAbsS2); conn.translateToRelative(ptEdge); ptS1 = getStraightEdgePoint(ptEdge, ptS1, ptS2); Point ptE2 = newLine.getPoint(newLine.size() - 1); Point ptE1 = conn.getTargetAnchor().getReferencePoint(); conn.translateToRelative(ptE1); Point ptAbsE2 = new Point(ptE2); conn.translateToAbsolute(ptAbsE2); ptEdge = conn.getTargetAnchor().getLocation(ptAbsE2); conn.translateToRelative(ptEdge); ptE1 = getStraightEdgePoint(ptEdge, ptE1, ptE2); newLine.removeAllPoints(); newLine.addPoint(ptS1); // insert two points Point extraPoint1 = ptS2.getTranslated(ptE2).scale(0.5); if(isOnRightHand(conn, owner, middle)){ extraPoint1.translate(SELFRELSIZEINIT, 0); } else { extraPoint1.translate(-SELFRELSIZEINIT, 0); } Point extraPoint2 = extraPoint1.getCopy(); if(isFeedback(conn)) { extraPoint1.y = ptS2.y; extraPoint2.y = ptE2.y; } else { extraPoint1.y = ptS1.y; extraPoint2.y = ptE1.y; } newLine.addPoint(extraPoint1); newLine.addPoint(extraPoint2); newLine.addPoint(ptE1); } protected boolean isOnRightHand(Connection conn, IFigure owner, Point middle) { boolean right = true; if(conn.getTargetAnchor() instanceof AnchorHelper.SideAnchor){ AnchorHelper.SideAnchor anchor = ( AnchorHelper.SideAnchor) conn.getTargetAnchor(); right = anchor.isRight(); }else{ PointList list = conn.getPoints(); if(list.getPoint(0).x > list.getPoint(1).x) right = false; } return right; } @Override protected boolean checkSelfRelConnection(Connection conn, PointList newLine) { if(RouterKind.getKind(conn, newLine).equals(RouterKind.SELF)) { getSelfRelVertices(conn, newLine); return true; } return false; } /** * All the code after this comment is copied from RectilinearRouter and RouterHelper * * Copyright (c) 2002, 2010 IBM Corporation and others. */ private void originalRectilinearRouteLine(Connection conn, int nestedRoutingDepth, PointList newLine) { boolean skipNormalization = (routerFlags & ROUTER_FLAG_SKIPNORMALIZATION) != 0; // if we are reorienting, then just default to the super class implementation and // don't try to do rectilinear routing. if(isReorienting(conn)) { super.routeLine(conn, nestedRoutingDepth, newLine); return; } /* * Remove and store former anchor points. Anchor points will be re-calculated anyway. * However, the old anchor points may be useful if connection didn't have any bend points * except the anchor points. */ Point lastStartAnchor = newLine.removePoint(0); Point lastEndAnchor = newLine.removePoint(newLine.size() - 1); /* * Check if connection is rectilinear and if not make it rectilinear */ if(!OrthogonalRouterUtilities.isRectilinear(newLine)) { OrthogonalRouterUtilities.transformToOrthogonalPointList(newLine, PositionConstants.NONE, PositionConstants.NONE); } removeRedundantPoints(newLine); /* * Remove unnecessary points that are contained within source and/or target shapes * as well as insert extra points if all points are within source and/or target shapes */ removePointsInViews(conn, newLine, lastStartAnchor, lastEndAnchor); Dimension tolerance = new Dimension(3, 0); if(!isFeedback(conn)) tolerance = (Dimension)MapModeUtil.getMapMode(conn).DPtoLP(tolerance); /* * Normalize polyline to eliminate extra segments. (This makes 3 segments collapsing into * one, while line segments are moved) */ if(!skipNormalization) { if(PointListUtilities.normalizeSegments(newLine, tolerance.width)) { /* * Normalization can make our polyline not rectilinear. Hence, we need to normalize * segments of polyline to straight line tolerance. */ normalizeToStraightLineTolerance(newLine, tolerance.width); } } /* * Normalization is not touching the end points, hence we'd like to handle this here. * If distance between start and end (which are the only points in a polyline) points * is too short we'll remove one of the points */ if(newLine.size() == 2) { Ray middleSeg = new Ray(newLine.getFirstPoint(), newLine.getLastPoint()); if(middleSeg.length() <= tolerance.width) { newLine.removePoint(0); } } /* * Calculate connection anchor points and possibly some extra routing work to keep * the connection rectilinear if anchor points make it not rectilinear. */ rectilinearResetEndPointsToEdge(conn, newLine); if(nestedRoutingDepth < 1 && !isValidRectilinearLine(conn, newLine)) { routeLine(conn, ++nestedRoutingDepth, newLine); } } /** * Rectilinear polyline is invalid if: * 1. First bend point is within the source * 2. Last bend point is within the target * 3. First bend point and source anchor are on different sides of the source shape * 4. Last bend point and target anchor are on different sides of the target shape * * @param conn * connection * @param line * rectilinear polyline * @return <code>true</code> if the line is valid */ private boolean isValidRectilinearLine(Connection conn, PointList line) { if(!(conn.getSourceAnchor().getOwner() instanceof Connection)) { Rectangle source = new PrecisionRectangle(FigureUtilities.getAnchorableFigureBounds(conn.getSourceAnchor().getOwner())); conn.getSourceAnchor().getOwner().translateToAbsolute(source); conn.translateToRelative(source); if(source.contains(line.getPoint(1))) { return false; } int firstSegmentOrientation = line.getFirstPoint().x == line.getPoint(1).x ? PositionConstants.VERTICAL : PositionConstants.HORIZONTAL; if(getOutisePointOffRectanglePosition(line.getPoint(1), source) != getAnchorLocationBasedOnSegmentOrientation(line.getFirstPoint(), source, firstSegmentOrientation)) { return false; } } if(!(conn.getTargetAnchor().getOwner() instanceof Connection)) { Rectangle target = new PrecisionRectangle(FigureUtilities.getAnchorableFigureBounds(conn.getTargetAnchor().getOwner())); conn.getTargetAnchor().getOwner().translateToAbsolute(target); conn.translateToRelative(target); if(target.contains(line.getPoint(line.size() - 2))) { return false; } int lastSegmentOrientation = line.getLastPoint().x == line.getPoint(line.size() - 2).x ? PositionConstants.VERTICAL : PositionConstants.HORIZONTAL; if(getOutisePointOffRectanglePosition(line.getPoint(line.size() - 2), target) != getAnchorLocationBasedOnSegmentOrientation(line.getLastPoint(), target, lastSegmentOrientation)) { return false; } } return true; } /** * Calculates geographic position of a point located outside the given rectangle relative * to the rectangle * * @param p * point outside of rectangle * @param r * the rectangle * @return geographic position of the point relative to the recatangle */ private int getOutisePointOffRectanglePosition(Point p, Rectangle r) { int position = PositionConstants.NONE; if(r.x > p.x) { position |= PositionConstants.WEST; } else if(r.x + r.width < p.x) { position |= PositionConstants.EAST; } if(r.y > p.y) { position |= PositionConstants.NORTH; } else if(r.y + r.height < p.y) { position |= PositionConstants.SOUTH; } return position; } /** * Given the coordinates of the connection anchor point the shape's rectangle and the * orientation of the first rectilinear connection segment that comes out from the anchor * point the method detemines on which geographic side of the rectangle the anchor point * is located on. * * @param anchorPoint * coordinates of the anchor point * @param rectangle * the shape's bounding rectangle * @param segmentOrientation * orinetation of the segment coming out from the anchor point * @return geographic position of the anchor point relative to the rectangle */ private int getAnchorLocationBasedOnSegmentOrientation(Point anchorPoint, Rectangle rectangle, int segmentOrientation) { if(segmentOrientation == PositionConstants.VERTICAL) { if(Math.abs(anchorPoint.y - rectangle.y) < Math.abs(anchorPoint.y - rectangle.y - rectangle.height)) { return PositionConstants.NORTH; } else { return PositionConstants.SOUTH; } } else if(segmentOrientation == PositionConstants.HORIZONTAL) { if(Math.abs(anchorPoint.x - rectangle.x) < Math.abs(anchorPoint.x - rectangle.x - rectangle.width)) { return PositionConstants.WEST; } else { return PositionConstants.EAST; } } return PositionConstants.NONE; } /** * Goes through line segments of a polyline and makes strict straight segments * from nearly straight segments. * * @param line * polyline * @param tolerance * tolerance value specifying nearly straight lines. */ private void normalizeToStraightLineTolerance(PointList line, int tolerance) { for(int i = 0; i < line.size() - 1; i++) { Point pt1 = line.getPoint(i); Point pt2 = line.getPoint(i + 1); if(Math.abs(pt1.x - pt2.x) < tolerance) { line.setPoint(new Point(pt1.x, pt2.y), i + 1); } else if(Math.abs(pt1.y - pt2.y) < tolerance) { line.setPoint(new Point(pt2.x, pt1.y), i + 1); } } } /** * Removes consecutive points contained within the source shape and removes consecutive * points contained within the target shape. If all points have been removed an extra point * outside source and target shapes will be added. * * @param conn * connection * @param newLine * polyline of the connection (routed connection) * @param start * old start anchor point * @param end * old end anchor point */ private void removePointsInViews(Connection conn, PointList newLine, Point start, Point end) { /* * Get the bounds of anchorable figure of the source and target and translate it to * connection relative coordinates. */ PrecisionRectangle source = conn.getSourceAnchor().getOwner() != null ? new PrecisionRectangle(FigureUtilities.getAnchorableFigureBounds(conn.getSourceAnchor().getOwner())) : null; PrecisionRectangle target = conn.getTargetAnchor().getOwner() != null ? new PrecisionRectangle(FigureUtilities.getAnchorableFigureBounds(conn.getTargetAnchor().getOwner())) : null; if(source != null) { conn.getSourceAnchor().getOwner().translateToAbsolute(source); conn.translateToRelative(source); } if(target != null) { conn.getTargetAnchor().getOwner().translateToAbsolute(target); conn.translateToRelative(target); } Point lastRemovedFromSource = null; Point lastRemovedFromTarget = null; /* * Starting from the first point of polyline remove points that are contained * within the source shape until the first point outside is found. * Remember the point that was removed from the source shape last for a possible * case of all points removed from polyline. */ if(!(conn.getSourceAnchor().getOwner() instanceof Connection) && newLine.size() != 0 && source.contains(new PrecisionPoint(newLine.getFirstPoint()))) { lastRemovedFromSource = newLine.removePoint(0); for(int i = 0; i < newLine.size() && source.contains(new PrecisionPoint(newLine.getPoint(i))); i++) { lastRemovedFromSource = newLine.removePoint(i--); } } /* * Starting from the end point of polyline remove points that are contained * within the target shape until the first point outside is found. * Remember the point that was removed from the target shape last for a possible * case of all points removed from polyline. */ if(!(conn.getTargetAnchor().getOwner() instanceof Connection) && newLine.size() != 0 && target.contains(new PrecisionPoint(newLine.getLastPoint()))) { lastRemovedFromTarget = newLine.removePoint(newLine.size() - 1); for(int i = newLine.size(); i > 0 && target.contains(new PrecisionPoint(newLine.getPoint(i - 1))); i--) { lastRemovedFromTarget = newLine.removePoint(i - 1); } } /* * Handle the special case of all points removed from polyline. */ if(newLine.size() == 0) { Dimension tolerance = new Dimension(1, 0); if(!isFeedback(conn)) tolerance = (Dimension)MapModeUtil.getMapMode(conn).DPtoLP(tolerance); int toleranceValue = tolerance.width; if(lastRemovedFromSource == null) { lastRemovedFromSource = start; } if(lastRemovedFromTarget == null) { lastRemovedFromTarget = end; } /* * If last point removed from source and the points removed from target form * a vertical or horizontal line we'll find a point located on this line and is * outside of source and target shape and insert it in the polyline. * The check for vertical and horizontal segment is using tolerance value, because * bend point location extracted from RelativeBendpoint can have precision errors due * to non-integer weight factors. */ if(Math.abs(lastRemovedFromSource.x - lastRemovedFromTarget.x) < toleranceValue) { // Vertical if(source.preciseY < target.preciseY) { newLine.addPoint(lastRemovedFromSource.x, (source.getBottom().y + target.getTop().y) / 2); } else { newLine.addPoint(lastRemovedFromSource.x, (source.getTop().y + target.getBottom().y) / 2); } } else if(Math.abs(lastRemovedFromSource.y - lastRemovedFromTarget.y) < toleranceValue) { // Horizontal if(source.preciseX < target.preciseX) { newLine.addPoint((source.getRight().x + target.getLeft().x) / 2, lastRemovedFromSource.y); } else { newLine.addPoint((source.getLeft().x + target.getRight().x) / 2, lastRemovedFromSource.y); } } else if((conn.getSourceAnchor() instanceof BaseSlidableAnchor && StringStatics.BLANK.equals(((BaseSlidableAnchor)conn.getSourceAnchor()).getTerminal()) && (conn.getTargetAnchor() instanceof BaseSlidableAnchor && StringStatics.BLANK.equals(((BaseSlidableAnchor)conn.getTargetAnchor()).getTerminal())))) { /* * This a special case for old diagrams with rectilinear connections routed by * the old router to look good with the new router */ if(lastRemovedFromSource != null && lastRemovedFromTarget != null) { newLine.addPoint((lastRemovedFromSource.x + lastRemovedFromTarget.x) / 2, (lastRemovedFromSource.y + lastRemovedFromTarget.y) / 2); } else { double startX = Math.max(source.preciseX, target.preciseX); double endX = Math.min(source.preciseX + source.preciseWidth, target.preciseX + target.preciseWidth); double startY = Math.max(source.preciseY, target.preciseY); double endY = Math.min(source.preciseY + source.preciseHeight, target.preciseY + target.preciseHeight); if(startX < endX) { if(source.preciseY < target.preciseY) { newLine.addPoint((int)Math.round((startX + endX) / 2.0), (source.getBottom().y + target.getTop().y) / 2); } else { newLine.addPoint((int)Math.round((startX + endX) / 2.0), (source.getTop().y + target.getBottom().y) / 2); } } else if(startY < endY) { if(source.preciseX < target.preciseX) { newLine.addPoint((source.getRight().x + target.getLeft().x) / 2, (int)Math.round((startY + endY) / 2.0)); } else { newLine.addPoint((source.getLeft().x + target.getRight().x) / 2, (int)Math.round((startY + endY) / 2.0)); } } } } } } protected void rectilinearResetEndPointsToEdge(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 = sourceBoundsRelativeToConnection(conn); PrecisionRectangle target = targetBoundsRelativeToConnection(conn); int offSourceDirection = PositionConstants.NONE; int offTargetDirection = PositionConstants.NONE; int sourceAnchorRelativeLocation = PositionConstants.NONE; int targetAnchorRelativeLocation = PositionConstants.NONE; if(line.size() == 0) { /* * If there are no valid bend points, we'll use the oblique connection anchor points * and just convert the polyline from oblique to rectilinear. */ // Need to add 2 dumb points to ensure that RouterHelper#resetEndPointsToEdge works line.addPoint(new Point()); line.addPoint(new Point()); super.resetEndPointsToEdge(conn, line); sourceAnchorRelativeLocation = getAnchorOffRectangleDirection(line.getFirstPoint(), source); targetAnchorRelativeLocation = getAnchorOffRectangleDirection(line.getLastPoint(), target); /* * 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. */ Point offStart = line.getFirstPoint(); Point offEnd = line.getLastPoint(); Dimension offsetDim = offStart.getDifference(offEnd).scale(0.5); offStart.translate(getTranslationValue(sourceAnchorRelativeLocation, Math.abs(offsetDim.width), Math.abs(offsetDim.height))); offEnd.translate(getTranslationValue(targetAnchorRelativeLocation, Math.abs(offsetDim.width), Math.abs(offsetDim.height))); line.insertPoint(offStart, 1); line.insertPoint(offEnd, 2); offSourceDirection = getOffShapeDirection(sourceAnchorRelativeLocation); offTargetDirection = getOffShapeDirection(targetAnchorRelativeLocation); } else { Point start = line.getFirstPoint(); Point end = line.getLastPoint(); if(conn.getSourceAnchor() instanceof OrthogonalConnectionAnchor) { line.insertPoint(OrthogonalRouterUtilities.getOrthogonalLineSegToAnchorLoc(conn, conn.getSourceAnchor(), start).getOrigin(), 0); } 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(start); 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) { line.addPoint(OrthogonalRouterUtilities.getOrthogonalLineSegToAnchorLoc(conn, conn.getTargetAnchor(), end).getOrigin()); } 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(end); conn.getSourceAnchor().getOwner().translateToAbsolute(reference); PrecisionPoint anchorLocation = new PrecisionPoint(conn.getTargetAnchor().getLocation(reference)); conn.translateToRelative(anchorLocation); line.addPoint(anchorLocation); } sourceAnchorRelativeLocation = getAnchorOffRectangleDirection(line.getFirstPoint(), source); offSourceDirection = getOffShapeDirection(sourceAnchorRelativeLocation); targetAnchorRelativeLocation = getAnchorOffRectangleDirection(line.getLastPoint(), target); offTargetDirection = getOffShapeDirection(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); removeRedundantPoints(line); } /** * Returns a translation dimension for the anchor point. Translation dimension * translates the anchor point off the shape. The off shape direction * is specified by the relative to the shape geographic position of the anchor * * @param position * relative to the shape geographic position of the anchor * @param xFactorValue * translation value along x-axis * @param yFactorValue * translation value along y-axis * @return */ private Dimension getTranslationValue(int position, int xFactorValue, int yFactorValue) { Dimension translationDimension = new Dimension(); if(position == PositionConstants.EAST) { translationDimension.width = xFactorValue; } else if(position == PositionConstants.SOUTH) { translationDimension.height = yFactorValue; } else if(position == PositionConstants.WEST) { translationDimension.width = -xFactorValue; } else if(position == PositionConstants.NORTH) { translationDimension.height = -yFactorValue; } return translationDimension; } /** * Target bounding rectangle relative to connection figure coordinates * * @param conn * connection * @return <code>PrecisionRectangle</code> target bounds relative to connection's coordinate * system */ private PrecisionRectangle targetBoundsRelativeToConnection(Connection conn) { PrecisionRectangle target = new PrecisionRectangle(conn.getTargetAnchor().getOwner().getBounds()); conn.getTargetAnchor().getOwner().translateToAbsolute(target); conn.translateToRelative(target); return target; } /** * Iterates through points of a polyline and does the following: * if 3 points lie on the same line the middle point is removed * * @param line * polyline's points */ private boolean removeRedundantPoints(PointList line) { int initialNumberOfPoints = line.size(); if(line.size() > 2) { PointList newLine = new PointList(line.size()); newLine.addPoint(line.removePoint(0)); while(line.size() >= 2) { Point p0 = newLine.getLastPoint(); Point p1 = line.getPoint(0); Point p2 = line.getPoint(1); if(p0.x == p1.x && p0.x == p2.x) { // Have two vertical segments in a row // get rid of the point between line.removePoint(0); } else if(p0.y == p1.y && p0.y == p2.y) { // Have two horizontal segments in a row // get rid of the point between line.removePoint(0); } else { newLine.addPoint(line.removePoint(0)); } } while(line.size() > 0) { newLine.addPoint(line.removePoint(0)); } line.removeAllPoints(); line.addAll(newLine); } return line.size() != initialNumberOfPoints; } /** * Determines whether the rectilinear line segment coming out of the shape should be * horizontal or vertical based on the anchor geographic position relative to the shape * * @param anchorRelativeLocation * @return */ private int getOffShapeDirection(int anchorRelativeLocation) { if(anchorRelativeLocation == PositionConstants.EAST || anchorRelativeLocation == PositionConstants.WEST) { return PositionConstants.HORIZONTAL; } else if(anchorRelativeLocation == PositionConstants.NORTH || anchorRelativeLocation == PositionConstants.SOUTH) { return PositionConstants.VERTICAL; } return PositionConstants.NONE; } /** * Source bounding rectangle relative to connection figure coordinates * * @param conn * connection * @return <code>PrecisionRectangle</code> source bounds relative to connection's coordinate * system */ private PrecisionRectangle sourceBoundsRelativeToConnection(Connection conn) { PrecisionRectangle source = new PrecisionRectangle(conn.getSourceAnchor().getOwner().getBounds()); conn.getSourceAnchor().getOwner().translateToAbsolute(source); conn.translateToRelative(source); return source; } /** * Determines the relative to rectangle geographic location of a point. * Example: If shape is closer to the the top edge of the rectangle location * would be north. * Method used to determine which side of shape's bounding rectangle is closer * to connection's anchor point. * All geometric quantities must be in the same coordinate system. * * @param anchorPoint * location of the anchor point * @param rect * bounding rectangle of the shape * @return */ private int getAnchorOffRectangleDirection(Point anchorPoint, Rectangle rect) { int position = PositionConstants.NORTH; int criteriaValue = Math.abs(anchorPoint.y - rect.y); int tempCriteria = Math.abs(anchorPoint.y - rect.y - rect.height); if(tempCriteria < criteriaValue) { criteriaValue = tempCriteria; position = PositionConstants.SOUTH; } tempCriteria = Math.abs(anchorPoint.x - rect.x); if(tempCriteria < criteriaValue) { criteriaValue = tempCriteria; position = PositionConstants.WEST; } tempCriteria = Math.abs(anchorPoint.x - rect.x - rect.width); if(tempCriteria < criteriaValue) { criteriaValue = tempCriteria; position = PositionConstants.EAST; } return position; } /** * @param conn * the <code>Connection</code> that is to be check if it is a feedback * connection or not. * @return <code>true</code> is it is a feedback connection, <code>false</code> otherwise. */ private static boolean isFeedback(Connection conn) { Dimension dim = new Dimension(100, 100); Dimension dimCheck = dim.getCopy(); conn.translateToRelative(dimCheck); return dim.equals(dimCheck); } }