blob: c53c4f012209b779d4fbf61dd1ec6c0d854a9864 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2007 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.draw2d.ui.internal.routers;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Ray;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
/**
* @author sshaw
* @canBeSeenBy org.eclipse.gmf.runtime.draw2d.ui.*
*
* RectilinearRouter which routes the Connection so that the lines
* are always vertical or horizontal.
*/
public class RectilinearRouter extends ObliqueRouter implements OrthogonalRouter {
/**
* removeSegmentsInViews
* This method will parse through all the line segments in the given
* polyline and remove any of the segments that intersect with the
* start and end figures.
*
* @param conn Connection figure that is used to access anchors
* @param newLine PointList that will be modified by the routine.
* @return boolean true if method change newLine PointList, false otherwise
*/
protected boolean removeSegmentsInViews(Connection conn, PointList newLine) {
// Ignore the first and last points
PointList newPoints = new PointList(newLine.size());
Point ptStart = new Point(newLine.getFirstPoint());
Point ptEnd = new Point(newLine.getLastPoint());
for (int i = 0; i < newLine.size(); i++) {
if (i != 0 && i != newLine.size() - 1)
newPoints.addPoint(new Point(newLine.getPoint(i)));
}
if (newPoints.size() < 3)
return false;
int lastIntersect = 0;
int count = 0;
boolean found = false;
boolean bChanged = false;
if (conn.getSourceAnchor().getOwner() == null)
return false;
Rectangle startRect =
new Rectangle(conn.getSourceAnchor().getOwner().getBounds());
conn.getSourceAnchor().getOwner().translateToAbsolute(startRect);
conn.translateToRelative(startRect);
for (int i = 0; i < newPoints.size() - 1; i++) {
boolean in1 = startRect.contains(newPoints.getPoint(i));
boolean in2 = startRect.contains(newPoints.getPoint(i + 1));
if (in1 != in2) {
lastIntersect = count;
found = true;
} else if (!(in1 || in2)) // Neither intersect, so skip out
{
break;
}
++count;
}
// remove segments before the one that finally
// intersects:
if (found) {
for (int i = 0; i <= lastIntersect; ++i) {
newPoints.removePoint(0);
bChanged = true;
}
}
lastIntersect = count = newLine.size() - 1;
found = false;
if (conn.getTargetAnchor().getOwner() == null)
return false;
Rectangle endRect =
new Rectangle(conn.getTargetAnchor().getOwner().getBounds());
conn.getTargetAnchor().getOwner().translateToAbsolute(endRect);
conn.translateToRelative(endRect);
for (int i = newPoints.size() - 1; i > 0; i--) {
boolean in1 = endRect.contains(newPoints.getPoint(i));
boolean in2 = endRect.contains(newPoints.getPoint(i - 1));
if (in1 != in2) {
lastIntersect = count;
found = true;
} else if (!(in1 || in2)) // Neither intersect, so skip out
{
break;
}
--count;
}
// remove segments after the one that finally
// intersects:
if (found) {
for (int i = newPoints.size() - 1; i >= lastIntersect; --i) {
newPoints.removePoint(newPoints.size() - 1);
bChanged = true;
}
}
if (newPoints.size() != newLine.size()) {
newLine.removeAllPoints();
newLine.addPoint(ptStart);
for (int i = 0; i < newPoints.size(); i++)
newLine.addPoint(new Point(newPoints.getPoint(i)));
newLine.addPoint(ptEnd);
}
return bChanged;
}
/**
* updateToBiTerminal
* Determines if the polyline has only two bendpoints (endpoints) and if so,
* updates the connection to be consistent with the Rectilinear router - i.e.
* vertical or horizontal alignment.
*
* @param conn Connection that is being routed.
* @param newLine PointList to be checked and modified if bi-terminal routing is
* possible
* @return true if PointList is a candidate for bi-terminal routing, false otherwise
*/
protected boolean updateToBiTerminal(Connection conn, PointList newLine) {
boolean retVal = false;
if (newLine.size() == 2) {
Point ptOrig = new Point(newLine.getPoint(0));
Point ptTerm = new Point(newLine.getPoint(1));
Dimension offsets = new Dimension(10, 10);
conn.translateToRelative(offsets);
Rectangle bBoxF, bBoxT;
if (conn.getSourceAnchor().getOwner() != null) {
bBoxF = getBounds(conn.getSourceAnchor().getOwner());
conn.getSourceAnchor().getOwner().translateToAbsolute(bBoxF);
conn.translateToRelative(bBoxF);
} else
bBoxF = new Rectangle(ptOrig.x - offsets.width / 2, ptOrig.y - offsets.height / 2,
offsets.width, offsets.height);
if (conn.getTargetAnchor().getOwner() != null) {
bBoxT = getBounds(conn.getTargetAnchor().getOwner());
conn.getTargetAnchor().getOwner().translateToAbsolute(bBoxT);
conn.translateToRelative(bBoxT);
} else
bBoxT = new Rectangle(ptTerm.x - offsets.width / 2, ptTerm.y - offsets.height / 2,
offsets.width, offsets.height);
int ix1 = Math.max(bBoxF.getLeft().x, bBoxT.getLeft().x);
int ix2 = Math.min(bBoxF.getRight().x, bBoxT.getRight().x);
Point posF = bBoxF.getCenter();
Point posT = bBoxT.getCenter();
Ray origSeg = new Ray(ptOrig, ptTerm);
boolean isOblique = (origSeg.y != 0 && origSeg.x != 0);
if (ix1 <= ix2) {
// The two boundboxes overlap each other so we can create a single
// segment that goes between them, but only if we have a nonrectilinear line
// or the existing segment is already is routed between the two icons
if (isOblique || ptOrig.x < ix1 || ptOrig.x > ix2) {
if (isOblique && ptOrig.x > ix1 && ptOrig.x < ix2)
posF.x = ptOrig.x;
else
posF.x = ix1 + (ix2 - ix1) / 2;
posT.x = posF.x;
newLine.removeAllPoints();
newLine.addPoint(posF);
newLine.addPoint(posT);
retVal = true;
}
} else {
int iy1 = Math.max(bBoxF.getTop().y, bBoxT.getTop().y);
int iy2 = Math.min(bBoxF.getBottom().y, bBoxT.getBottom().y);
if (iy1 <= iy2) {
// The two boundboxes overlap each other so we can create a single
// segment that goes between them, but only if we have a nonrectilinear line
// or the existing segment is already is routed between the two icons
if (isOblique || ptOrig.y < iy1 || ptOrig.y > iy2) {
if (isOblique && ptOrig.y > iy1 && ptOrig.y < iy2)
posF.y = ptOrig.y;
else
posF.y = iy1 + (iy2 - iy1) / 2;
posT.y = posF.y;
newLine.removeAllPoints();
newLine.addPoint(posF);
newLine.addPoint(posT);
retVal = true;
}
}
}
}
return retVal;
}
/**
* resetEndPointsToEdge
* Resets both of the end points in the polyline to be anchored properly on the
* edge of the start and end figures.
* @see org.eclipse.gmf.runtime.draw2d.ui.internal.routers.ObliqueRouter#resetEndPointsToEdge(org.eclipse.draw2d.Connection, org.eclipse.draw2d.geometry.PointList)
*/
protected void resetEndPointsToEdge(Connection conn, PointList newLine) {
// if we are reorienting, then just default to the super class implementation and
// don't try to do rectilinear routing.
if (isReorienting(conn)) {
super.resetEndPointsToEdge(conn, newLine);
return;
}
Point origin = null;
if (conn.getSourceAnchor().getOwner() instanceof Connection) {
origin = getIntersectionPoint((Connection) conn.getSourceAnchor()
.getOwner(), new LineSeg(newLine.getPoint(1), newLine
.getPoint(0)));
}
if (origin == null) {
LineSeg edgeLine1 = OrthogonalRouterUtilities
.getOrthogonalLineSegToAnchorLoc(conn, conn.getSourceAnchor(),
newLine.getPoint(1));
origin = edgeLine1.getOrigin();
}
Point terminus = null;
if (conn.getTargetAnchor().getOwner() instanceof Connection) {
int numPoints = newLine.size();
terminus = getIntersectionPoint((Connection) conn.getTargetAnchor()
.getOwner(), new LineSeg(newLine.getPoint(numPoints - 2),
newLine.getPoint(numPoints - 1)));
}
if (terminus == null) {
LineSeg edgeLine2 = OrthogonalRouterUtilities
.getOrthogonalLineSegToAnchorLoc(conn, conn.getTargetAnchor(),
newLine.getPoint(newLine.size() - 2));
terminus = edgeLine2.getOrigin();
}
if (origin != null && terminus != null) {
newLine.setPoint(origin, 0);
if (newLine.size() > 2) {
for (int i=0; i<2; i++) {
Point ptCurrent = newLine.getPoint(i);
Point ptNext = newLine.getPoint(i+1);
makeOrthogonal(ptCurrent, ptNext);
newLine.setPoint(ptNext, i+1);
}
}
newLine.setPoint(terminus, newLine.size() - 1);
if (newLine.size() > 2) {
for (int i=newLine.size() - 1; i>=newLine.size() - 2; i--) {
Point ptCurrent = newLine.getPoint(i);
Point ptNext = newLine.getPoint(i-1);
makeOrthogonal(ptCurrent, ptNext);
newLine.setPoint(ptNext, i-1);
}
}
} else
super.resetEndPointsToEdge(conn, newLine);
}
private void makeOrthogonal(Point ptCurrent, Point ptNext) {
if (Math.abs(ptNext.x - ptCurrent.x) < Math.abs(ptNext.y - ptCurrent.y)) {
ptNext.x = ptCurrent.x;
} else {
ptNext.y = ptCurrent.y;
}
}
private static int CONNECTION_OFFSET = 26;
/**
* updateIfNotRectilinear
* This is the core method that will calculate the rectilinear version of the
* polyline points.
*
* @param conn Connection that is the owner of the PointList
* @param newLine PointList to be checked and modified
*/
protected void updateIfNotRectilinear(Connection conn, PointList newLine) {
boolean isRectilinear = true;
for (int i = 0; i < newLine.size() - 1; i++) {
Ray segVector =
new Ray(newLine.getPoint(i), newLine.getPoint(i + 1));
if (segVector.x != 0 && segVector.y != 0) {
isRectilinear = false;
break;
}
}
// first see if it is already rectilinear already
if (isRectilinear && areEndsInBounds(conn, newLine)) {
return;
}
// now try to turn it into a biterminal (i.e. one straight line)
if (updateToBiTerminal(conn, newLine) && areEndsInBounds(conn, newLine)) {
return;
}
// We've got a line that isn't rectilinear, so let's route
// General rules based on number of segments starting with
// if starting with two points (one segment) take shortest distance first.
// if starting with three points (two segments) put longest segment
// as the middle segment
OrthogonalRouterUtilities.resetEndPointsToCenter(conn, newLine);
PointList oldPoints = PointListUtilities.copyPoints(newLine);
PointList newPoints = new PointList();
newPoints.addPoint(oldPoints.removePoint(0));
while (oldPoints.size() > 0) {
if (oldPoints.size() >= 2) {
// This starts at point where last left off,
// or the starting point if first time through.
Point p0 = newPoints.getLastPoint();
Point p1 = oldPoints.removePoint(0);
Point p2 = oldPoints.removePoint(0);
// make the shortest segment first.
if (Math.abs(p2.y - p0.y) > Math.abs(p2.x - p0.x)) {
// x has shortest segment
newPoints.addPoint(new Point(p1.x, p0.y));
newPoints.addPoint(new Point(p1.x, p2.y));
} else // y has shortest segment first.
{
newPoints.addPoint(new Point(p0.x, p1.y));
newPoints.addPoint(new Point(p2.x, p1.y));
}
newPoints.addPoint(p2);
} else if (oldPoints.size() == 1) {
Point p0 = newPoints.getLastPoint();
Point p1 = oldPoints.removePoint(0);
if (Math.abs(p1.y - p0.y) > Math.abs(p1.x - p0.x)) {
newPoints.addPoint(new Point(p1.x, p0.y));
} else {
newPoints.addPoint(new Point(p0.x, p1.y));
}
newPoints.addPoint(p1);
}
}
oldPoints.removeAllPoints();
// Now make a pass through to collapse any redundent segments.
oldPoints.addPoint(newPoints.removePoint(0));
while (newPoints.size() >= 2) {
Point p0 = oldPoints.getLastPoint();
Point p1 = newPoints.getPoint(0);
Point p2 = newPoints.getPoint(1);
if (p0.x == p1.x && p0.x == p2.x) {
// Have two vertical segments in a row
// get rid of the point between
newPoints.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
newPoints.removePoint(0);
} else {
oldPoints.addPoint(newPoints.removePoint(0));
}
}
while (newPoints.size() > 0) {
oldPoints.addPoint(newPoints.removePoint(0));
}
// set the newly routed line back into newLine
newLine.removeAllPoints();
for (int i = 0; i < oldPoints.size(); i++)
newLine.addPoint(oldPoints.getPoint(i));
}
/**
* checkEndSegments
* This method is useful to ensure that the arrow heads and / or tail adornments
* are always visible irrespective of any routing that occurs. This is accomplished
* by assert a minimum length of the line segments that are at the beginning and end
* of the PointList.
*
* @param conn Connection to check the end segments of
* @param newLine PointList to modify
* @return boolean true if end segments are ok, false otherwise.
*/
protected boolean checkEndSegments(Connection conn, PointList newLine) {
boolean bOk = true;
Dimension connection_offset = new Dimension(CONNECTION_OFFSET, 0);
conn.translateToRelative(connection_offset);
// now check for end segments length and fix up after.
if (newLine.size() > 2) {
Point ptFix = new Point(newLine.getPoint(1));
if (!checkEndSegment(conn, conn.getSourceAnchor(), ptFix, connection_offset.width / 2)) {
newLine.setPoint(ptFix, 1);
// check next point to ensure rectilinear
Point ptNext = newLine.getPoint(2);
makeOrthogonal(ptFix, ptNext);
newLine.setPoint(ptNext, 2);
bOk = false;
}
ptFix = new Point(newLine.getPoint(newLine.size() - 2));
if (!checkEndSegment(conn, conn.getTargetAnchor(), ptFix, connection_offset.width / 2)) {
newLine.setPoint(ptFix, newLine.size() - 2);
// check next point to ensure rectilinear
Point ptNext = newLine.getPoint(newLine.size() - 3);
makeOrthogonal(ptFix, ptNext);
newLine.setPoint(ptNext, newLine.size() - 3);
bOk = false;
}
}
return bOk;
}
/**
* straightenPoints
* This is a simpler version of the @see updateIfNotRectilinear that simply ensures
* that the lines are horizontal or vertical without any intelligence in terms of
* shortest distance around a rectangle.
*
* @param newLine PointList to check for rectilinear qualities and change if necessary.
*/
protected void straightenPoints(PointList newLine) {
for (int i=0; i<newLine.size()-1; i++) {
Point ptCurrent = newLine.getPoint(i);
Point ptNext = newLine.getPoint(i+1);
makeOrthogonal(ptCurrent, ptNext);
newLine.setPoint(ptNext, i+1);
}
}
/**
* checkEndSegment
* This method is useful to ensure that the arrow heads and / or tail adornments
* are always visible irrespective of any routing that occurs. This is accomplished
* by assert a minimum length of the line segments that are at the beginning and end
* of the PointList.
*
* @param conn Connection that is used to reference the source / target anchors
* @param anchor ConnectionAnchor used to calculate the edge point
* @param ptNext Point that is checked against the edge to see if it's in violation. It will
* be modified to a correct value if the method returns false.
* @param offset int value representing the offset allowed from the shape edge.
* @return boolean true if end segment is ok, false otherwise.
*/
protected boolean checkEndSegment(
Connection conn,
ConnectionAnchor anchor,
Point ptNext,
int offset) {
LineSeg seg = OrthogonalRouterUtilities.getOrthogonalLineSegToAnchorLoc(conn, anchor, ptNext);
if (seg != null) {
// ensure target line segments is bigger then a tolerance level (average arrow size)
if (seg.length() < offset) {
seg.pointOn(offset, LineSeg.KeyPoint.ORIGIN, ptNext);
return false;
}
}
return true;
}
private static final int maxRoutingDepth = 10;
/**
* Overridden method from ObliqueRouter that will perform the conversion of the
* polyline to a rectilinear version.
* @see org.eclipse.gmf.runtime.draw2d.ui.internal.routers.ObliqueRouter#routeLine(org.eclipse.draw2d.Connection, int, org.eclipse.draw2d.geometry.PointList)
*/
public void routeLine(
Connection conn,
int nestedRoutingDepth,
PointList newLine) {
boolean skipNormalization =
(routerFlags & ROUTER_FLAG_SKIPNORMALIZATION) != 0;
int nStartSize = newLine.size();
// 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);
resetEndPointsToEdge(conn, newLine);
return;
}
// get the original line
if (checkSelfRelConnection(conn, newLine)) {
checkEndSegments(conn, newLine);
resetEndPointsToEdge(conn, newLine);
return;
}
// We've eliminated any unnecessary segments,
// Now let's make sure everything is rectilinear
updateIfNotRectilinear(conn, newLine);
// Because we have created a polyline, it may have multiple
// points of intersection with the originating and
// terminating views. We need to find the last intersection
// point.
boolean normalizationChangedLine = false;
if (!skipNormalization) {
normalizationChangedLine = removeSegmentsInViews(conn, newLine);
normalizationChangedLine |= removePointsInViews(conn, newLine);
// Normalize the polyline to remove unwanted segments
Dimension tolerance = new Dimension(3, 0);
if (!RouterHelper.getInstance().isFeedback(conn))
tolerance = (Dimension)MapModeUtil.getMapMode(conn).DPtoLP(tolerance);
normalizationChangedLine |= PointListUtilities.normalizeSegments(newLine, tolerance.width);
}
// check the end segments to ensure they conform to a minimum distance.
checkEndSegments(conn, newLine);
resetEndPointsToEdge(conn, newLine);
// final fix-up to ensure straight lines
straightenPoints(newLine);
if (normalizationChangedLine) {
// May need to reposition endpoints again, so recurse. It must be
// the case that normalization reduces the complexity of the line,
// so that the recursion terminates.
if (nestedRoutingDepth < maxRoutingDepth) {
nestedRoutingDepth++;
routeLine(conn, nestedRoutingDepth, newLine);
// If unwinding from setting to 0, then don't decrement.
if (nestedRoutingDepth != 0)
nestedRoutingDepth--;
}
}
else {
Rectangle startRect = getBounds(conn.getSourceAnchor().getOwner());
conn.getSourceAnchor().getOwner().translateToAbsolute(startRect);
conn.translateToRelative(startRect);
Dimension buffer = new Dimension(2, 2);
conn.translateToRelative(buffer);
startRect.expand(buffer.width, buffer.height);
Rectangle endRect = getBounds(conn.getTargetAnchor().getOwner());
conn.getTargetAnchor().getOwner().translateToAbsolute(endRect);
conn.translateToRelative(endRect);
endRect.expand(buffer.width, buffer.height);
if (!startRect.contains(newLine.getPoint(0)) ||
!endRect.contains(newLine.getPoint(newLine.size() - 1)) ||
newLine.size() - nStartSize >= 2) {
newLine.removeAllPoints();
Point r1 = conn.getSourceAnchor().getReferencePoint();
conn.translateToRelative(r1);
newLine.addPoint(r1);
Point r2 = conn.getTargetAnchor().getReferencePoint();
conn.translateToRelative(r2);
newLine.addPoint(r2);
updateIfNotRectilinear(conn, newLine);
resetEndPointsToEdge(conn, newLine);
}
}
//## end RectRouter::routeLine%803842153.body
}
/**
* Returns true if the ends of the line passed in our within the bounds of
* the connection's source and target ends.
*
* @param connection
* the connection whose source and target ends will be looked at
* @param line
* the line in question
* @return true if the two ends of the lines are within the bounds; false
* otherwise
*/
private boolean areEndsInBounds(Connection connection, PointList line) {
Rectangle startRect = new PrecisionRectangle(getBounds(connection
.getSourceAnchor().getOwner()));
connection.getSourceAnchor().getOwner().translateToAbsolute(startRect);
connection.translateToRelative(startRect);
Rectangle endRect = new PrecisionRectangle(getBounds(connection
.getTargetAnchor().getOwner()));
connection.getTargetAnchor().getOwner().translateToAbsolute(endRect);
connection.translateToRelative(endRect);
if (!startRect.contains(line.getPoint(0))
|| !endRect.contains(line.getPoint(line.size() - 1))) {
return false;
}
return true;
}
/**
* Returns a copy of the bounds of this figure or if the figure is a
* <code>Connection</code> the bounds of the pointlist will be returned.
*
* @param figure
* @return a copy of the bounds
*/
private Rectangle getBounds(IFigure figure) {
return figure instanceof Connection ? ((Connection) figure).getPoints()
.getBounds().getCopy()
: figure.getBounds().getCopy();
}
/**
* Returns the closest intersection point from the line segment given that
* will extend to hit the connection passed in.
*
* @param connection
* the connection
* @param lineSeg
* the line segment to extend to find intersections with the
* connection
* @return the closeest intersecting point or null if there are none
*/
private Point getIntersectionPoint(Connection connection, LineSeg lineSeg) {
PointList intersections = lineSeg
.getLineIntersectionsWithLineSegs(connection.getPoints());
if (intersections.size() > 0) {
return PointListUtilities.pickClosestPoint(intersections, lineSeg
.getOrigin());
}
return null;
}
}