blob: f2a29787f936bdf37abbec1c5a5dc0232acd6fb3 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2004, 2009 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
* Mariot Chauvin <mariot.chauvin@obeo.fr> - bug 233344
****************************************************************************/
package org.eclipse.gmf.runtime.draw2d.ui.internal.routers;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.draw2d.AbsoluteBendpoint;
import org.eclipse.draw2d.Bendpoint;
import org.eclipse.draw2d.BendpointConnectionRouter;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.common.core.util.Trace;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.internal.Draw2dDebugOptions;
import org.eclipse.gmf.runtime.draw2d.ui.internal.Draw2dPlugin;
import org.eclipse.gmf.runtime.draw2d.ui.mapmode.MapModeUtil;
/**
* This class is a top level router for managing the individual branches in a set of
* tree routed connections.
*
* @author sshaw
*/
public class TreeRouter extends BendpointConnectionRouter implements OrthogonalRouter {
private BranchRouter branchRouter = new BranchRouter(this);
private ArrayList connectionList = new ArrayList();
private Dimension trunkVertex;
private Orientation trunkOrientation;
private boolean updatingPeers = false;
static private class Orientation {
private Orientation() {
// Empty constructor
}
/**
* Constant for the top orientation
*/
static public Orientation TOP = new Orientation();
/**
* Constant for the bottom orientation
*/
static public Orientation BOTTOM = new Orientation();
/**
* Constant for the right orientation
*/
static public Orientation RIGHT = new Orientation();
/**
* Constant for the left orientation
*/
static public Orientation LEFT = new Orientation();
/**
* getEdge
* Method to return the edge point of the given Rectangle representative
* of the orientation value of the instance.
*
* @param bounds Rectangle to retrieve the edge value from.
* @return Point that is the edge of the rectangle for the orientation of this.
*/
public Point getEdge(Rectangle bounds) {
if (this == TOP)
return bounds.getTop();
else if (this == BOTTOM)
return bounds.getBottom();
else if (this == RIGHT)
return bounds.getRight();
return bounds.getLeft();
}
}
/**
*
*/
public TreeRouter() {
super();
}
/**
* @see org.eclipse.draw2d.ConnectionRouter#invalidate(Connection)
*/
public void invalidate(Connection conn) {
if (conn.getSourceAnchor() == null || conn.getSourceAnchor().getOwner() == null ||
conn.getTargetAnchor() == null || conn.getTargetAnchor().getOwner() == null)
return;
ListIterator li = connectionList.listIterator();
while (li.hasNext()) {
Connection connNext = (Connection)li.next();
if (!trunkVertexEqual(connNext, conn)) {
final ConnectionAnchor connExtSourceAnchor = connNext.getSourceAnchor();
final ConnectionAnchor connExtTargetAnchor = connNext.getTargetAnchor();
if (connExtSourceAnchor != null && connExtSourceAnchor.getOwner() != null &&
connExtTargetAnchor != null && connExtTargetAnchor.getOwner() != null){
updateConstraint(connNext);
}
}
}
}
private boolean trunkVertexEqual(Connection connMaster, Connection connSlave) {
PointList cmPts = connMaster.getPoints();
PointList csPts = connSlave.getPoints();
if (cmPts.size() > 2 && csPts.size() > 2)
return cmPts.getPoint(2).equals(csPts.getPoint(2));
return false;
}
private Rectangle getTargetAnchorRelativeBounds(final Connection conn) {
final Rectangle bounds = conn.getTargetAnchor().getOwner().getBounds().getCopy();
conn.getTargetAnchor().getOwner().translateToAbsolute(bounds);
conn.translateToRelative(bounds);
return bounds;
}
private Rectangle getSourceAnchorRelativeBounds(final Connection conn) {
final Rectangle bounds = conn.getSourceAnchor().getOwner().getBounds().getCopy();
conn.getSourceAnchor().getOwner().translateToAbsolute(bounds);
conn.translateToRelative(bounds);
return bounds;
}
/**
* getTrunkLocation
* Method to retrieve the trunk location in relative coordinates based on
* current tree state.
*
* @param conn Connection being routed
* @return Point that is the trunk location in relative coordinates.
*/
public Point getTrunkLocation(Connection conn) {
Dimension vertex = getTrunkVertex();
Point target = getTrunkOrientation().getEdge(getTargetAnchorRelativeBounds(conn));
Point ptTrunkLoc = new Point(vertex.width, vertex.height);
ptTrunkLoc = ptTrunkLoc.getTranslated(target);
return ptTrunkLoc;
}
/**
* setTrunkLocation
* Setter method to set the trunk location. Translates the point into a relative
* point from the target edge.
*
* @param conn Connection being routed
* @param ptTrunkLoc Point that is the trunk location in relative coordinates.
*/
public void setTrunkLocation(Connection conn, Point ptTrunkLoc) {
Point ptRelTrunkLoc = new Point(ptTrunkLoc);
final Rectangle targetAnchorBounds = getTargetAnchorRelativeBounds(conn);
// update orientation
if (isTopDown(conn)) {
if (ptTrunkLoc.y < targetAnchorBounds.getCenter().y)
setTrunkOrientation(Orientation.TOP);
else
setTrunkOrientation(Orientation.BOTTOM);
}
else {
if (ptTrunkLoc.x < targetAnchorBounds.getCenter().x)
setTrunkOrientation(Orientation.LEFT);
else
setTrunkOrientation(Orientation.RIGHT);
}
Point target = getTrunkOrientation().getEdge(targetAnchorBounds);
Dimension currentVertex = ptRelTrunkLoc.getDifference(target);
setTrunkVertex(currentVertex);
}
/**
* updateConstraint
* Updates the constraint value for the connection based on the tree vertex
*
* @param conn Connection whose constraint is to be updated.
*/
protected void updateConstraint(Connection conn) {
if (isUpdatingPeers())
return;
List bendpoints = (List)conn.getRoutingConstraint();
if (bendpoints == null)
bendpoints = new ArrayList(conn.getPoints().size());
if (bendpoints != null) {
Point sourceRefPoint = conn.getSourceAnchor().getReferencePoint();
conn.translateToRelative(sourceRefPoint);
Point targetRefPoint = conn.getTargetAnchor().getReferencePoint();
conn.translateToRelative( targetRefPoint);
Point ptTrunk = getTrunkLocation(conn);
Point ptSource = getBranchRouter().getSourceLocation(conn, ptTrunk);
bendpoints.clear();
PointList pts = getBranchRouter().recreateBranch(conn, ptSource, ptTrunk);
for (int i=0; i<pts.size(); i++) {
Bendpoint bp = new AbsoluteBendpoint(pts.getPoint(i));
bendpoints.add(bp);
}
}
setUpdatingPeers(true);
try {
setConstraint(conn, bendpoints);
conn.invalidate();
conn.validate();
}
catch (Exception e) {
Trace.catching(Draw2dPlugin.getInstance(), Draw2dDebugOptions.EXCEPTIONS_CATCHING, TreeRouter.class, "updateConstraint", //$NON-NLS-1$
e);
}
finally {
setUpdatingPeers(false);
}
}
/**
* getPointsFromConstraint
* Utility method retrieve the PointList equivalent of the bendpoint constraint
* set in the Connection.
*
* @param conn Connection to retrieve the constraint from.
* @return PointList list of points that is the direct equivalent of the set constraint.
*/
public PointList getPointsFromConstraint(Connection conn) {
List bendpoints = (List)conn.getRoutingConstraint();
if (bendpoints == null)
return new PointList();
PointList points = new PointList(bendpoints.size());
for (int i = 0; i < bendpoints.size(); i++) {
Bendpoint bp = (Bendpoint) bendpoints.get(i);
points.addPoint(bp.getLocation());
}
straightenPoints(points, MapModeUtil.getMapMode(conn).DPtoLP(3));
return points;
}
/**
* 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.
* @param tolerance int tolerance value by which points will be straightened in HiMetrics
*/
static protected void straightenPoints(PointList newLine, int tolerance) {
for (int i=0; i<newLine.size()-1; i++) {
Point ptCurrent = newLine.getPoint(i);
Point ptNext = newLine.getPoint(i+1);
int xDelta = Math.abs(ptNext.x - ptCurrent.x);
int yDelta = Math.abs(ptNext.y - ptCurrent.y);
if (xDelta < yDelta) {
if (xDelta > tolerance)
return;
ptNext.x = ptCurrent.x;
} else {
if (yDelta > tolerance)
return;
ptNext.y = ptCurrent.y;
}
newLine.setPoint(ptNext, i+1);
}
}
/**
* Returns the branch router in the chain.
* @return The getBranchRouter router
*
*/
protected BranchRouter getBranchRouter() {
return branchRouter;
}
/**
* @see org.eclipse.draw2d.ConnectionRouter#remove(Connection)
*/
public void remove(Connection conn) {
if (conn.getSourceAnchor() == null || conn.getTargetAnchor() == null)
return;
int index = connectionList.indexOf(conn);
connectionList.remove(conn);
for (int i = index + 1; i < connectionList.size(); i++)
((Connection)connectionList.get(i)).revalidate();
getBranchRouter().remove(conn);
}
/**
* isTopDown
* Utility method to determine if the connection should routed in a top-down fashion
* or in a horizontal fashion.
*
* @param conn Connection to query
* @return boolean true if connection should be routed top-down, false otherwise.
*/
public boolean isTopDown(Connection conn) {
boolean vertical = true;
if (conn instanceof ITreeConnection) {
vertical = ((ITreeConnection)conn).getOrientation().equals(ITreeConnection.Orientation.VERTICAL) ? vertical = true : false;
}
return vertical;
}
private int DEFAULT_TRUNK_HEIGHT = 32;
/**
* checkTrunkVertex
* Method to initialize the trunk vertex to a default value if not already set
*
* @param conn Connection to be routed.
*/
private void checkTrunkVertex(Connection conn) {
if (getTrunkVertex() == null) {
Rectangle sourceRect = conn.getSourceAnchor().getOwner().getBounds();
Rectangle targetRect = conn.getTargetAnchor().getOwner().getBounds();
Dimension default_trunk = new Dimension(0, DEFAULT_TRUNK_HEIGHT);
conn.translateToRelative(default_trunk);
if (isTopDown(conn)) {
if (sourceRect.getCenter().y < targetRect.getCenter().y) {
setTrunkVertex(new Dimension(0, -default_trunk.height));
setTrunkOrientation(Orientation.TOP);
}
else {
setTrunkVertex(new Dimension(0, default_trunk.height));
setTrunkOrientation(Orientation.BOTTOM);
}
}
else {
if (sourceRect.getCenter().x < targetRect.getCenter().x) {
setTrunkVertex(new Dimension(-default_trunk.height, 0));
setTrunkOrientation(Orientation.LEFT);
}
else {
setTrunkVertex(new Dimension(default_trunk.height, 0));
setTrunkOrientation(Orientation.RIGHT);
}
}
}
}
/* (non-Javadoc)
* Routes the given connection. Calls the 'getBranchRouter' router method first
* @see org.eclipse.draw2d.ConnectionRouter#route(org.eclipse.draw2d.Connection)
*/
public void route(Connection conn) {
if (conn.getSourceAnchor() == null || conn.getSourceAnchor().getOwner() == null ||
conn.getTargetAnchor() == null || conn.getTargetAnchor().getOwner() == null) {
super.route(conn);
return;
}
if (!connectionList.contains(conn)) {
connectionList.add(conn);
}
checkTrunkVertex(conn);
getBranchRouter().route(conn);
invalidate(conn);
}
/**
* @return Returns the truckVertex.
*/
protected Dimension getTrunkVertex() {
return trunkVertex;
}
/**
* @param trunkVertex The trunkVertex to set.
*/
protected void setTrunkVertex(Dimension trunkVertex) {
this.trunkVertex = trunkVertex;
}
/**
* @return Returns the trunkOrientation.
*/
protected Orientation getTrunkOrientation() {
return trunkOrientation;
}
/**
* @param trunkOrientation The trunkOrientation to set.
*/
protected void setTrunkOrientation(Orientation trunkOrientation) {
this.trunkOrientation = trunkOrientation;
}
/**
* Utility method to determine if the given set of points conforms to the constraints
* of being an orthogonal connection tree-branch.
* 1. Points size must be 4.
* 2. Source point resides with-in boundary of source shape based on orientation
* 3. Target point resides with-in boundary of target shape based on orientation
* 4. Middle line is perpendicular to the 2 end lines.
*
* @param conn the <code>Connection</code> to test
* @param points <code>PointList</code> to test constraints against
* @return <code>boolean</code> <code>true</code> if points represent valid orthogaonl tree
* branch, <code>false</code> otherwise.
*/
public boolean isOrthogonalTreeBranch(Connection conn, PointList points) {
if (isTreeBranch(conn, points)) {
LineSeg branch = new LineSeg(points.getPoint(0), points.getPoint(1));
LineSeg trunkShoulder = new LineSeg(points.getPoint(1), points.getPoint(2));
LineSeg trunk = new LineSeg(points.getPoint(2), points.getPoint(3));
if (isTopDown(conn))
return branch.isVertical() && trunkShoulder.isHorizontal() && trunk.isVertical();
else
return branch.isHorizontal() && trunkShoulder.isVertical() && trunk.isHorizontal();
}
return false;
}
/**
* Utility method to determine if the given set of points conforms to the constraints
* of being a connection tree-branch.
* 1. Points size must be 4.
* 2. Source point resides with-in boundary of source shape based on orientation
* 3. Target point resides with-in boundary of target shape based on orientation
*
* @param conn the <code>Connection</code> to test
* @param points the <code>PointList</code> to test constraints against
* @return <code>boolean</code> <code>true</code> if points represent valid tree branch,
* <code>false</code> otherwise.
*/
public boolean isTreeBranch(Connection conn, PointList points) {
if (points.size() == 4) {
// just check if ends are with-in the owner bounding box
Rectangle targetBounds = getTargetAnchorRelativeBounds(conn);
Rectangle sourceBounds = getSourceAnchorRelativeBounds(conn);
if (isTopDown(conn)) {
return (points.getPoint(0).x > sourceBounds.x &&
points.getPoint(0).x < sourceBounds.x + sourceBounds.width) &&
(points.getPoint(3).x > targetBounds.x &&
points.getPoint(3).x < targetBounds.x + targetBounds.width);
}
else
{
return (points.getPoint(0).y > sourceBounds.y &&
points.getPoint(0).y < sourceBounds.y + sourceBounds.height) &&
(points.getPoint(3).y > targetBounds.y &&
points.getPoint(3).y < targetBounds.y + targetBounds.height);
}
}
return false;
}
/**
* @return Returns the connectionList List which is a copy of the internal list.
*/
public List getConnectionList() {
return (List)connectionList.clone();
}
/**
* @return Returns the updatingPeers.
*/
protected boolean isUpdatingPeers() {
return updatingPeers;
}
/**
* @param updatingPeers The updatingPeers to set.
*/
protected void setUpdatingPeers(boolean updatingPeers) {
this.updatingPeers = updatingPeers;
}
}