Home » Eclipse Projects » GEF » Contribution: RectilinearRouter and RelativeConnectionAnchor
Contribution: RectilinearRouter and RelativeConnectionAnchor [message #85664] |
Thu, 26 June 2003 17:52  |
Eclipse User |
|
|
|
This is a multi-part message in MIME format.
--------------030604000709020304070008
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
RelativeConnectionAnchor - an anchor whose location is based on a double
x and y ratio in its parent IFigure
RectilinearRouter - an AbstractRouter whose route() method routes a
rectilinear Connection in a way that avoids other IFigures as much as
possible to do quickly and without making the Connection look like it
walked through a maze
RectilinearRouterTest - a standalone test class, so you need the SWT dll
in your java.library.path to run it
RectilinearRouter currently has no support for bendpoints. I am working
on adding support for rectilinear handles (which will be contributed
when it is done).
I am submitting this now as RectilinearRouter is useful by itself (as an
alternative to the ManhattanConnectionRouter), and in keeping with the
"release early, release often" philosophy.
Any feedback is greatly appreciated.
Thanks,
Peter Armstrong
--------------030604000709020304070008
Content-Type: application/x-javascript;
name="RelativeConnectionAnchor.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="RelativeConnectionAnchor.java"
/*********************************************************** ********************
* Copyright (c) 2000, 2003 Intalio, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Intalio, Inc. - initial implementation
************************************************************ ******************/
package com.intalio.draw2d;
import org.eclipse.draw2d.AbstractConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
/**
* RelativeConnectionAnchor is an AbstractConnectionAnchor whose location is
* given by an x-ratio and a y-ratio of its location in its IFigure owner.
*
* @author Peter Armstrong
*/
public class RelativeConnectionAnchor extends AbstractConnectionAnchor {
/**
* The ratio (between 0 and 1) of where the RelativeConnectionAnchor is
* in the x direction with respect to its owner IFigure.
*/
private double _xRatio;
/**
* The ratio (between 0 and 1) of where the RelativeConnectionAnchor is
* in the y direction with respect to its owner IFigure.
*/
private double _yRatio;
/**
* Create a new RelativeConnectionAnchor with at the the given xRatio and
* yRatio inside the given owner.
* @param owner the IFigure that the RelativeConnectionAnchor is in
* @param xRatio the x location within the IFigure owner [0.0, 1.0]
* @param yRatio the y location within the IFigure owner [0.0, 1.0]
*/
public RelativeConnectionAnchor(
IFigure owner, double xRatio, double yRatio) {
super(owner);
_xRatio = xRatio;
_yRatio = yRatio;
}
/**
* @param figure the ancestor that moved
* @see org.eclipse.draw2d.AbstractConnectionAnchor#ancestorMoved(IF igure)
*/
public void ancestorMoved(IFigure figure) {
if (figure instanceof ScalableFreeformLayeredPane) {
return;
}
super.ancestorMoved(figure);
}
/**
* Get the location of the RelativeConnectionAnchor given the given
* reference point (which is ignored in this case). This method exists
* to implement the ConnectionAnchor interface, but its effect is identical
* to calling getReferencePoint().
* @param reference the reference point, which is ignored
* @return the location of the RelativeConnectionAnchor
*/
public Point getLocation(Point reference) {
return getReferencePoint();
}
/**
* Get the getReferencePoint of the RelativeConnectionAnchor, which is
* actually the location of the RelativeConnectionAnchor.
* @return the getReferencePoint of the RelativeConnectionAnchor, which is
* actually the location of the RelativeConnectionAnchor.
*/
public Point getReferencePoint() {
Rectangle r = getOwner().getBounds();
Point p = new PrecisionPoint(
r.x + _xRatio * r.width,
r.y + _yRatio * r.height);
getOwner().translateToAbsolute(p);
return p;
}
}
--------------030604000709020304070008
Content-Type: application/x-javascript;
name="RectilinearRouterTest.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="RectilinearRouterTest.java"
/*********************************************************** ********************
* Copyright (c) 2000, 2003 Intalio, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Intalio, Inc. - initial implementation
************************************************************ ******************/
package com.intalio.draw2d;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* This class is the test for the RectilinearRouter. It is heavily based on the
* "Draw2D Developers Guide->Programmer's Guide->Connections and Anchors Demo".
* It is strictly Draw2D -- no GEF functionality is used and no GEF class is
* imported.
*
* Note: it also uses the RelativeConnectionAnchor class, which is a class I
* wrote which could also be a candidate for inclusion within Draw2D (it uses
* no GEF functionality).
*
* @author Peter Armstrong
*/
public class RectilinearRouterTest {
/**
* Main method
* @param args the args
*/
public static void main(String[] args) {
Shell shell = new Shell();
shell.setSize(600, 600);
shell.open();
shell.setText("RectilinearRouterTest");
LightweightSystem lws = new LightweightSystem(shell);
IFigure panel = new Figure();
lws.setContents(panel);
RectilinearRouter router = new RectilinearRouter();
RectangleFigure node1 = new RectangleFigure();
node1.setBackgroundColor(ColorConstants.red);
node1.setLocation(new Point(100, 100));
RelativeConnectionAnchor node1a1 =
new RelativeConnectionAnchor(node1, 0.5, 0);
RelativeConnectionAnchor node1a2 =
new RelativeConnectionAnchor(node1, 0.5, 1);
RectangleFigure node2 = new RectangleFigure();
node2.setBackgroundColor(ColorConstants.blue);
node2.setLocation(new Point(200, 200));
RelativeConnectionAnchor node2a1 =
new RelativeConnectionAnchor(node2, 0.5, 0);
RelativeConnectionAnchor node2a2 =
new RelativeConnectionAnchor(node2, 0.5, 1);
RectangleFigure node3 = new RectangleFigure();
node3.setBackgroundColor(ColorConstants.yellow);
node3.setLocation(new Point(300, 300));
RelativeConnectionAnchor node3a1 =
new RelativeConnectionAnchor(node3, 0.5, 0);
RelativeConnectionAnchor node3a2 =
new RelativeConnectionAnchor(node3, 0.5, 1);
PolylineConnection con1 = new PolylineConnection();
con1.setSourceAnchor(node1a2);
con1.setTargetAnchor(node2a1);
con1.setTargetDecoration(new PolygonDecoration());
con1.setConnectionRouter(router);
PolylineConnection con2 = new PolylineConnection();
con2.setSourceAnchor(node2a2);
con2.setTargetAnchor(node3a1);
con2.setTargetDecoration(new PolygonDecoration());
con2.setConnectionRouter(router);
panel.add(node1);
panel.add(node2);
panel.add(node3);
panel.add(con1);
panel.add(con2);
new Dragger(node1);
new Dragger(node2);
new Dragger(node3);
Display display = Display.getDefault();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
static class Dragger
extends MouseMotionListener.Stub
implements MouseListener {
public Dragger(IFigure figure) {
figure.addMouseMotionListener(this);
figure.addMouseListener(this);
}
Point last;
public void mouseReleased(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mouseDoubleClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
last = e.getLocation();
}
public void mouseDragged(MouseEvent e) {
Point p = e.getLocation();
Dimension delta = p.getDifference(last);
last = p;
Figure f = ((Figure)e.getSource());
f.setBounds(f.getBounds().getTranslated(delta.width, delta.height));
}
};
}
--------------030604000709020304070008
Content-Type: application/x-javascript;
name="RectilinearRouter.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="RectilinearRouter.java"
/*********************************************************** ********************
* Copyright (c) 2000, 2003 Intalio, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* Intalio, Inc. - initial API and implementation
************************************************************ ******************/
package com.intalio.draw2d;
import java.util.Iterator;
import java.util.Vector;
import org.eclipse.draw2d.AbstractRouter;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
/**
* RectilinearRouter is an AbstractRouter whose route() method routes
* a rectilinear {@link org.eclipse.draw2d.Connection} in a way that avoids
* other IFigures as much as possible to do <i>quickly</i> and without
* making the Connection look like it walked through a maze.
*
* It is useful on its own, but its primary purpose is to be subclassed by
* RectilinearBendpointRouter, which adds smart rectilinear handles to it.
*
* @author Peter Armstrong
*/
public class RectilinearRouter extends AbstractRouter {
//
//These directional constants are used to describe to the location of a
//point relative to an IFigure x. They are also used to categorize anchors.
//
// NW N NE
// W x E
// SW S SE
//
/** The point is north of the IFigure (or on its top edge). */
protected static final int N = 0;
/** The point is east of the IFigure (or on its top edge). */
protected static final int E = 1;
/** The point is south of the IFigure (or on its top edge). */
protected static final int S = 2;
/** The point is west of the IFigure (or on its top edge). */
protected static final int W = 3;
/** The point is north-east of the IFigure (or on its top right corner). */
protected static final int NE = 4;
/** The point is north-west of the IFigure (or on its top left corner). */
protected static final int NW = 5;
/**
* The point is south-east of the IFigure (or on its bottom right corner).
*/
protected static final int SE = 6;
/**
* The point is south-west of the IFigure (or on its bottom left corner).
*/
protected static final int SW = 7;
/** The point is north of the IFigure (or on its top edge). */
protected static final int INTERIOR = 8; //on top of the IFigure x
/**
* The default gap AT 1.0 (100%) ZOOM that should be made between an IFigure
* and the Connector being routed around it--i.e. the distance it "goes out"
* before turning 90 degrees in one direction (if it does turn).
*/
protected static final int DEFAULT_GAP = 10;
/**
* This is the gap AT 1.0 (100%) ZOOM that should be made between an IFigure
* and the Connector being routed around it--i.e. the distance it "goes out"
* before turning 90 degrees in one direction (if it does turn).
*/
protected int _gap;
/**
* This is the zoom ratio (100% == 1.0) of the IFigure we're routing in.
*/
protected double _zoomFactor;
/**
* Construct a new RectilinearRouter.
*/
public RectilinearRouter() {
_gap = DEFAULT_GAP;
_zoomFactor = 1.0;
}
/**
* Get the gap that should be made between an IFigure and the Connector
* being routed around it. This is the distance it "goes out" before
* turning 90 degrees in one direction.
* @return the gap that should be made between an IFigure and the Connector
* being routed around it. This is the distance it "goes out" before
* turning 90 degrees in one direction.
*/
public int getGap() {
return _gap;
}
/**
* Set the gap that should be made between an IFigure and the Connector
* being routed around it. This is the distance it "goes out" before
* turning 90 degrees in one direction.
* @param gap the gap that should be made between an IFigure and the
* Connector being routed around it. This is the distance it "goes out"
* before turning 90 degrees in one direction.
*/
public void setGap(int gap) {
_gap = gap;
}
/**
* Return the zoom ratio (100% == 1.0) of the IFigure we're routing in.
* @return the zoom ratio (100% == 1.0) of the IFigure we're routing in
*/
public double getZoomFactor() {
return _zoomFactor;
}
/**
* Set the zoom ratio (100% == 1.0) of the IFigure we're routing in.
* @param ratio the zoom ratio (100% == 1.0) of the IFigure we're routing
* in.
*/
public void setZoomFactor(double ratio) {
_zoomFactor = ratio;
}
/**
* Route the {@link org.eclipse.draw2d.Connection} in a rectilinear way that
* avoids other IFigures as much as possible to do <i>quickly</i> and
* without making the Connection look like it walked through a maze.
*
* Since the destination point is an anchor point, we need to have the line
* which goes from the source point to the destination point not only avoid
* the source IFigure, but also avoid the destination IFigure that the
* destination anchor is part of. But (and this is where it gets tricky),
* we don't just want to avoid the destination IFigure; we want to do so
* with the least number of possible bends in our line. This means that we
* can't just code a generic algorithm for getting around a IFigure, and
* then apply it to both the source and destination IFigures--the avoiding
* code needs to take into account where the other IFigure and anchor point
* is.
*
* Note: To get to a IFigure on the opposite side of the anchor we're
* starting from, we will go around the side of the source IFigure that we
* are closest to.
*
* @param con the Connection being routed
* @see org.eclipse.draw2d.ConnectionRouter#route(org.eclipse.draw2d .Connection)
*/
public void route(Connection con) {
ConnectionAnchor sourceAnchor = con.getSourceAnchor();
ConnectionAnchor targetAnchor = con.getTargetAnchor();
if (sourceAnchor == null || targetAnchor == null) {
return;
}
IFigure sourceFigure = sourceAnchor.getOwner();
IFigure targetFigure = targetAnchor.getOwner();
if (sourceFigure == null || targetFigure == null) {
return;
}
IFigure commonAncestor =
getNearestCommonAncestor(sourceFigure, targetFigure);
Point sourceAnchorLoc =
sourceAnchor.getLocation(sourceAnchor.getReferencePoint());
Point targetAnchorLoc =
targetAnchor.getLocation(targetAnchor.getReferencePoint());
//this includes source, which is the FIRST point of the returned array
Point[] sourcePts =
getRequiredSourcePoints(
sourceAnchorLoc,
sourceAnchor,
sourceFigure,
targetAnchorLoc);
//this includes dest, which is the LAST point of the returned array
Point[] targetPts =
getRequiredDestPoints(
targetAnchorLoc,
targetAnchor,
targetFigure,
sourcePts[sourcePts.length - 1]);
Point lastSourcePt = sourcePts[sourcePts.length - 1];
Point lastDestPt = targetPts[0];
Point bridgeCandidateOne = new Point(lastSourcePt.x, lastDestPt.y);
Point bridgeCandidateTwo = new Point(lastDestPt.x, lastSourcePt.y);
int numPoints = sourcePts.length + 1 + targetPts.length;
Point[] boundsCheckPointsOne =
new Point[]{
new Point(lastSourcePt),
bridgeCandidateOne,
new Point(lastDestPt)};
int numIntersectedShapesOne =
getNumIntersectedFigures(boundsCheckPointsOne, commonAncestor);
Point[] boundsCheckPointsTwo =
new Point[]{
new Point(lastSourcePt),
bridgeCandidateTwo,
new Point(lastDestPt)};
int numIntersectedShapesTwo =
getNumIntersectedFigures(boundsCheckPointsTwo, commonAncestor);
Point[] newPointArray = new Point[numPoints];
System.arraycopy(sourcePts, 0, newPointArray, 0, sourcePts.length);
if (numIntersectedShapesOne <= numIntersectedShapesTwo) {
newPointArray[sourcePts.length] = bridgeCandidateOne;
} else {
newPointArray[sourcePts.length] = bridgeCandidateTwo;
}
System.arraycopy(
targetPts,
0,
newPointArray,
sourcePts.length + 1,
targetPts.length);
newPointArray =
addSidePoints(stripRedundantCornerPoints(newPointArray));
PointList newPts = new PointList(newPointArray.length);
for (int i = 0; i < newPointArray.length; i++) {
newPts.addPoint(newPointArray[i]);
}
PointList points = con.getPoints();
points.removeAllPoints();
con.setPoints(newPts);
}
/**
* @param points an array OF CORNER POINTS ONLY
* @return the array of non-redundant corner points
*/
protected Point[] stripRedundantCornerPoints(Point[] points) {
Vector keepPts = new Vector();
Point prevAddedPoint = points[0];
keepPts.add(points[0]); //always add start point
boolean xLine, yLine;
for (int i = 1; i < points.length - 1; i++) {
xLine =
sameX(prevAddedPoint, points[i])
&& sameX(points[i], points[i + 1]);
yLine =
sameY(prevAddedPoint, points[i])
&& sameY(points[i], points[i + 1]);
if (!(xLine || yLine)) {
keepPts.add(points[i]);
prevAddedPoint = points[i];
}
}
keepPts.add(points[points.length - 1]); //always add end point
return (Point[])(keepPts.toArray(new Point[]{}));
}
/**
* Return true if the two Points have the same x, false otherwise.
* @param a the first point
* @param b the second point
* @return true if the two Points have the same x, false otherwise.
*/
protected boolean sameX(Point a, Point b) {
return (a.x == b.x);
}
/**
* Return true if the two Points have the same y, false otherwise.
* @param a the first point
* @param b the second point
* @return true if the two Points have the same y, false otherwise.
*/
protected boolean sameY(Point a, Point b) {
return (a.y == b.y);
}
/**
* Create the side handles.
* @param points an array of non-redundant corner points
* @return an array which includes all the non-redundant corner points,
* now separated by side handles
*/
protected Point[] addSidePoints(Point[] points) {
Point[] retval = new Point[2 * points.length - 1];
//assign even points to the corner points passed in
for (int i = 0; i < points.length; i++) {
retval[2 * i] = points[i];
}
//generate the side points
for (int i = 0; i < points.length - 1; i++) {
if (points[i].x == points[i + 1].x) { //vertical
retval[2 * i + 1] =
new Point(points[i].x, (points[i].y + points[i + 1].y) / 2);
} else { //horizontal
retval[2 * i + 1] =
new Point((points[i].x + points[i + 1].x) / 2, points[i].y);
}
}
return retval;
}
/**
* Return the required points coming out of the source IFigure.
* @param source the source point
* @param sourceAnchor the source anchor
* @param sourceFigure the source shape
* @param dest the dest point
* @return the required points coming out of the source IFigure
*/
protected Point[] getRequiredSourcePoints(
Point source,
ConnectionAnchor sourceAnchor,
IFigure sourceFigure,
Point dest) {
Vector pts = new Vector();
pts.add(source);
Rectangle sb = sourceFigure.getBounds();
int zoomedGap = (int)(_gap * _zoomFactor);
Rectangle sbg =
new Rectangle(
sb.x - zoomedGap,
sb.y - zoomedGap,
sb.width + 2 * zoomedGap,
sb.height + 2 * zoomedGap);
int relToSrcShapeLoc = getRelativeLocation(sbg, dest);
if (relToSrcShapeLoc == INTERIOR) {
return new Point[]{source};
}
int anchorType =
getRelativeLocation(
sourceAnchor.getOwner().getBounds(),
sourceAnchor.getLocation(sourceAnchor.getReferencePoint()));
switch (anchorType) {
case N:
pts.add(new Point(source.x, sbg.y));
switch (relToSrcShapeLoc) {
case N:
case NW:
case NE:
//don't add any more
break;
case W:
case SW:
pts.add(new Point(sbg.x, sbg.y));
break;
case E:
case SE:
pts.add(new Point(sbg.x + sbg.width, sbg.y));
break;
case S:
if (dest.x < sb.x + sb.width / 2) { //go around left
pts.add(new Point(sbg.x, sbg.y));
pts.add(new Point(sbg.x, sbg.y + sbg.height));
} else { //go around right
pts.add(new Point(sbg.x + sbg.width, sbg.y));
pts.add(
new Point(
sbg.x + sbg.width,
sbg.y + sbg.height));
}
break;
default : //unreachable
}
break;
case S:
pts.add(new Point(source.x, sbg.y + sbg.height));
switch (relToSrcShapeLoc) {
case S:
case SW:
case SE:
//don't add any more
break;
case NW:
case W:
pts.add(new Point(sbg.x, sbg.y + sbg.height));
break;
case NE:
case E:
pts.add(
new Point(sbg.x + sbg.width, sbg.y + sbg.height));
break;
case N:
if (dest.x < sb.x + sb.width / 2) { //go around left
pts.add(new Point(sbg.x, sbg.y + sbg.height));
pts.add(new Point(sbg.x, sbg.y));
} else { //go around right
pts.add(
new Point(
sbg.x + sbg.width,
sbg.y + sbg.height));
pts.add(new Point(sbg.x + sbg.width, sbg.y));
}
break;
default : //unreachable
}
break;
case E:
pts.add(new Point(sbg.x + sbg.width, source.y));
switch (relToSrcShapeLoc) {
case E:
case NE:
case SE:
//don't add any more
break;
case S:
case SW:
pts.add(
new Point(sbg.x + sbg.width, sbg.y + sbg.height));
break;
case N:
case NW:
pts.add(new Point(sbg.x + sbg.width, sbg.y));
break;
case W:
if (dest.y < sb.y + sb.height / 2) { //go around top
pts.add(new Point(sbg.x + sbg.width, sbg.y));
pts.add(new Point(sbg.x, sbg.y));
} else { //go around bottom
pts.add(
new Point(
sbg.x + sbg.width,
sbg.y + sbg.height));
pts.add(new Point(sbg.x, sbg.y + sbg.height));
}
break;
default : //unreachable
}
break;
case W:
pts.add(new Point(sbg.x, source.y));
switch (relToSrcShapeLoc) {
case W:
case NW:
case SW:
//don't add any more
break;
case S:
case SE:
pts.add(new Point(sbg.x, sbg.y + sbg.height));
break;
case N:
case NE:
pts.add(new Point(sbg.x, sbg.y));
break;
case E:
if (dest.y < sb.y + sb.height / 2) { //go around top
pts.add(new Point(sbg.x, sbg.y));
pts.add(new Point(sbg.x + sbg.width, sbg.y));
} else { //go around bottom
pts.add(new Point(sbg.x, sbg.y + sbg.height));
pts.add(
new Point(
sbg.x + sbg.width,
sbg.y + sbg.height));
}
break;
default : //unreachable
}
break;
case NE:
switch (relToSrcShapeLoc) {
case NE:
//could work either way; don't append any points
break;
case N:
case NW:
pts.add(new Point(source.x, sbg.y));
break;
case E:
case SE:
pts.add(new Point(sbg.x + sbg.width, source.y));
break;
case S:
case SW:
pts.add(new Point(sbg.x + sbg.width, source.y));
pts.add(
new Point(sbg.x + sbg.width, sbg.y + sbg.height));
break;
case W:
pts.add(new Point(source.x, sbg.y));
pts.add(new Point(sbg.x, sbg.y));
break;
default : //unreachable
}
break;
case NW:
switch (relToSrcShapeLoc) {
case NW:
//could work either way; don't append any points
break;
case N:
case NE:
pts.add(new Point(source.x, sbg.y));
break;
case W:
case SW:
pts.add(new Point(sbg.x, source.y));
break;
case S:
case SE:
pts.add(new Point(sbg.x, source.y));
pts.add(new Point(sbg.x, sbg.y + sbg.height));
break;
case E:
pts.add(new Point(source.x, sbg.y));
pts.add(new Point(sbg.x + sbg.width, sbg.y));
break;
default : //unreachable
}
break; //to here
case SE:
switch (relToSrcShapeLoc) {
case SE:
//could work either way; don't append any points
break;
case S:
case SW:
pts.add(new Point(source.x, sbg.y + sbg.height));
break;
case E:
case NE:
pts.add(new Point(sbg.x + sbg.width, source.y));
break;
case N:
case NW:
pts.add(new Point(sbg.x + sbg.width, source.y));
pts.add(new Point(sbg.x + sbg.width, sbg.y));
break;
case W:
pts.add(new Point(source.x, sbg.y + sbg.height));
pts.add(new Point(sbg.x, sbg.y + sbg.height));
break;
default : //unreachable
}
break;
case SW:
switch (relToSrcShapeLoc) {
case SW:
//could work either way; don't append any points
break;
case S:
case SE:
pts.add(new Point(source.x, sbg.y + sbg.height));
break;
case W:
case NW:
pts.add(new Point(sbg.x, source.y));
break;
case N:
case NE:
pts.add(new Point(sbg.x, source.y));
pts.add(new Point(sbg.x, sbg.y));
break;
case E:
pts.add(new Point(source.x, sbg.y + sbg.height));
pts.add(
new Point(sbg.x + sbg.width, sbg.y + sbg.height));
break;
default : //unreachable
}
break;
default : //INTERIOR
}
return (Point[])(pts.toArray(new Point[]{}));
}
/**
* Return the location of the Point p relative to the Rectangle r (one of
* the directional constants)
* @param r the Rectangle the Point is relative to
* @param p the Point whose location relative to the Rectangle we are
* getting
* @return the location of the Point p relative to the Rectangle r (one of
* the directional constants)
*/
protected int getRelativeLocation(Rectangle r, Point p) {
if (p.y <= r.y) { //top
if (p.x <= r.x) { //left
return NW;
} else if (p.x >= r.right()) { //right
return NE;
} else { //middle
return N;
}
} else if (p.y >= r.bottom()) { //bottom
if (p.x <= r.x) { //left
return SW;
} else if (p.x >= r.right()) { //right
return SE;
} else { //middle
return S;
}
} else {
if (p.x <= r.x) { //left
return W;
} else if (p.x >= r.right()) { //right
return E;
} else { //middle
return INTERIOR;
}
}
}
/**
* Return the required points coming out of the source IFigure.
* @param dest the dest point
* @param destAnchor the dest anchor
* @param destFigure the dest shape
* @param source the source point
* @return the required points coming out of the source IFigure
*/
protected Point[] getRequiredDestPoints(
Point dest,
ConnectionAnchor destAnchor,
IFigure destFigure,
Point source) {
if (destAnchor != null && destFigure != null) {
Point[] backwardsPts =
getRequiredSourcePoints(dest, destAnchor, destFigure, source);
Point[] retval = new Point[backwardsPts.length];
for (int i = 0; i < retval.length; i++) {
retval[i] = backwardsPts[backwardsPts.length - i - 1];
}
return retval;
} else {
return new Point[]{dest};
}
}
/**
* Return the number of IFigures that are children of the given IFigure that
* the given polyline intersects (not including Connections).
* @param polylinePoints the points of the polyline
* @param parent the IFigure whose children we are checking to see if
* they intersect
* @return the number of IFigures that are children of the given IFigure
* that the given polyline intersects (not including Connections)
*/
protected int getNumIntersectedFigures(
Point[] polylinePoints,
IFigure parent) {
java.awt.Polygon bounds = createBounds(polylinePoints);
int numShapesIntersected = 0;
Iterator shapes = parent.getChildren().iterator();
IFigure shape;
while (shapes.hasNext()) {
shape = (IFigure)shapes.next();
Rectangle sb = shape.getBounds();
if (!(shape instanceof Connection)
&& bounds.intersects(sb.x, sb.y, sb.width, sb.height)) {
numShapesIntersected++;
}
}
return numShapesIntersected;
}
/**
* I have to return a java.awt.Polygon here, since the intersects method of
* org.eclipse.draw2d.Polygon does not work the way I need it to: it does
* not test if the org.eclipse.draw2d.Polygon intersects, but rather if the
* bounding rectangle of the org.eclipse.draw2d.Polygon does (it merely
* inherits its behavior from Figure, which does this. So I need to do this
* unless the behavior of org.eclipse.draw2d.Polygon is changed.
*
* This method creates the bounds polygons which are the bounds of the
* connectors. Each handle results in two bounds points being created, at
* the ith and (n - 1 - i)th positions in the array (since we want the
* polygon lines not to criss-cross). The bounds polygon treats the
* connector as the median in a divided highway.
*
* @param points the points in the Connector
* @return a Polygon which surrounds the points
*/
protected static java.awt.Polygon createBounds(Point[] points) {
//Half of the width of the bounds polygon we're creating.
int bwidth2 = 1;
int n = 2 * points.length; //number of points
int[] xpoints = new int[n];
int[] ypoints = new int[n];
Point h1, h2, h3; //the three handle locations we're considering at
//once to get the angle
//
//Process the special case of the first handle.
//
h1 = points[0];
h2 = points[1];
if (h1.x == h2.x) {
//it's a vertical line
ypoints[0] = h1.y;
ypoints[n - 1] = h1.y;
if (h1.y < h2.y) { //going down
xpoints[0] = h1.x - bwidth2;
xpoints[n - 1] = h1.x + bwidth2;
} else { //going up
xpoints[0] = h1.x + bwidth2;
xpoints[n - 1] = h1.x - bwidth2;
}
} else {
//it's a horizontal line
xpoints[0] = h1.x;
xpoints[n - 1] = h1.x;
if (h1.x < h2.x) { //going right
ypoints[0] = h1.y + bwidth2;
ypoints[n - 1] = h1.y - bwidth2;
} else { //going left
ypoints[0] = h1.y - bwidth2;
ypoints[n - 1] = h1.y + bwidth2;
}
}
//
//Process the second to the second last handles (we're assigning the
//bounds points for h2).
//
for (int i = 0; i < points.length - 2; i++) {
h1 = points[i];
h2 = points[i + 1];
h3 = points[i + 2];
if (h1.x == h2.x) {
//first line is a vertical line, so the second line will be
//a horizontal line
if (h1.y < h2.y) { //first line going down
xpoints[i + 1] = h2.x - bwidth2;
xpoints[n - 2 - i] = h2.x + bwidth2;
if (h2.x < h3.x) {
//second line left to right
ypoints[i + 1] = h2.y + bwidth2;
ypoints[n - 2 - i] = h2.y - bwidth2;
} else {
//second line right to left
ypoints[i + 1] = h2.y - bwidth2;
ypoints[n - 2 - i] = h2.y + bwidth2;
}
} else { //first line going up
xpoints[i + 1] = h2.x + bwidth2;
xpoints[n - 2 - i] = h2.x - bwidth2;
if (h2.x < h3.x) {
//second line left to right
ypoints[i + 1] = h2.y + bwidth2;
ypoints[n - 2 - i] = h2.y - bwidth2;
} else {
//second line right to left
ypoints[i + 1] = h2.y - bwidth2;
ypoints[n - 2 - i] = h2.y + bwidth2;
}
}
} else {
//first line is a horizontal line, so the second line will be
//vertical
if (h1.x < h2.x) { //first line going right
ypoints[i + 1] = h2.y + bwidth2;
ypoints[n - 2 - i] = h2.y - bwidth2;
if (h2.y < h3.y) {
//second line up to down
xpoints[i + 1] = h2.x - bwidth2;
xpoints[n - 2 - i] = h2.x + bwidth2;
} else {
//second line down to up
xpoints[i + 1] = h2.x + bwidth2;
xpoints[n - 2 - i] = h2.x - bwidth2;
}
} else { //first line going left
ypoints[i + 1] = h2.y - bwidth2;
ypoints[n - 2 - i] = h2.y + bwidth2;
if (h2.y < h3.y) {
//second line up to down
xpoints[i + 1] = h2.x - bwidth2;
xpoints[n - 2 - i] = h2.x + bwidth2;
} else {
//second line down to up
xpoints[i + 1] = h2.x + bwidth2;
xpoints[n - 2 - i] = h2.x - bwidth2;
}
}
}
}
//
//Process the special case of the last handle.
//
h1 = points[points.length - 2];
h2 = points[points.length - 1]; //last handle
if (h1.x == h2.x) {
//last line is vertical
ypoints[points.length - 1] = h2.y;
ypoints[points.length] = h2.y;
if (h1.y < h2.y) { //going down
xpoints[points.length - 1] = h2.x - bwidth2;
xpoints[points.length] = h2.x + bwidth2;
} else { //going up
xpoints[points.length - 1] = h2.x + bwidth2;
xpoints[points.length] = h2.x - bwidth2;
}
} else {
//last line is horizontal
xpoints[points.length - 1] = h2.x;
xpoints[points.length] = h2.x;
if (h1.x < h2.x) { //going right
ypoints[points.length - 1] = h2.y + bwidth2;
ypoints[points.length] = h2.y - bwidth2;
} else { //going left
ypoints[points.length - 1] = h2.y - bwidth2;
ypoints[points.length] = h2.y + bwidth2;
}
}
//finally build and return the new bounds polygon
return new java.awt.Polygon(xpoints, ypoints, n);
}
/**
* Get the IFigure that contains fig1 and fig2 the most directly.
* @param fig1 the first IFigure to check
* @param fig2 the second IFigure to check
* @return the IFigure that contains fig1 and fig2 the most directly.
*/
protected IFigure getNearestCommonAncestor(
IFigure fig1, IFigure fig2) {
IFigure fig1Parent = fig1.getParent();
IFigure fig2Parent = fig2.getParent();
if (fig1Parent == null || fig2Parent == null) {
return null;
} else if (fig1Parent == fig2Parent) {
return fig1Parent;
} else {
//If we get here, both fig1parent and fig2parent are non-null and
//not equal to each other. So, figure out whether either is the
//ancestor of each other (and if so, return it) or else if they have
//a common ancestor (and return that). If neither of them are, then
//check their parents...
//TODO - refactor/rewrite?
if (isAncestor(fig1Parent, fig2Parent)) {
return fig1Parent;
}
if (isAncestor(fig2Parent, fig1Parent)) {
return fig2Parent;
}
return getNearestCommonAncestor(fig1Parent, fig2Parent);
}
}
/**
* Return true if fig1 is an ancestor of fig2, false otherwise.
* @param fig1 the IFigure ancestor candidate
* @param fig2 the IFigure which we're checking ancestors of
* @return true if fig1 is an ancestor of fig2, false otherwise
*/
protected boolean isAncestor(IFigure fig1, IFigure fig2) {
IFigure fig2parent = fig2.getParent();
if (fig2parent == null) {
return false;
}
if (fig2parent == fig1) {
return true;
}
return isAncestor(fig1, fig2parent);
}
}
--------------030604000709020304070008--
|
|
| | |
Re: Contribution: RectilinearRouter and RelativeConnectionAnchor [message #85833 is a reply to message #85818] |
Fri, 27 June 2003 14:14  |
Eclipse User |
|
|
|
Originally posted by: none.us.ibm.com
See BendpointRouter, and use of translateToAbsolute, and
IFigure#translateToRelative()
"Peter Armstrong" <armstrong@intalio.com> wrote in message
news:bdhvmb$3h6$1@rogue.oti.com...
> Gunnar Wagenknecht wrote:
> > Thanks for the nice work. Did you checked it inside a scrollable viewer?
If
> > I scroll the viewer the router paints the connections somewhere relative
to
> > origin of the whole viewer but not of the view pane.
> >
> > Cu, Gunnar
>
> Thanks!
>
> If I use the RectilinearRouter in a GraphicalEditor whose RootEditPart
> is a FreeformGraphicalRootEditPart, I can reproduce the issues with the
> connections that you described if I move an IFigure to the left so that
> the FreeformGraphicalRootEditPart expands to its left. This could be
> some issue with negative coordinates; I'm not sure yet.
>
> Was this the scrollable viewer you were referring to? I am fairly new
> at GEF :-) Do you have a standalone Draw2D example that you reproduced
> this in?
>
> Note: If I modify the RectilinearRouterTest I attached in my previous
> message to just use a simple ScrollPane like this
>
> Shell shell = new Shell();
> shell.setSize(600, 600);
> shell.open();
> shell.setText("RectilinearRouterScrollableTest");
> LightweightSystem lws = new LightweightSystem(shell);
>
> ScrollPane sp = new ScrollPane();
> IFigure panel = new Figure();
> panel.setSize(1000, 1000);
> sp.setContents(panel);
> lws.setContents(sp);
> RectilinearRouter router = new RectilinearRouter();
>
> RectangleFigure node1 = new RectangleFigure();
> //rest of the code is unchanged...
>
> the RectilinearRouter still works fine regardless of where the
> ScrollPane's viewport is positioned. So that's why I'm starting with
> the assumption that it's something related to the
> FreeformGraphicalRootEditPart. I'm not sure yet how to use the
> FreeformLayout & FreeformViewport by themselves in Draw2D--looking into
> that now...
>
> Thanks again,
> Peter
>
|
|
|
Goto Forum:
Current Time: Wed Apr 30 09:12:11 EDT 2025
Powered by FUDForum. Page generated in 0.03366 seconds
|