package causation.lab; // packages import java.awt.*; // custom classes import causation.lab.WorkbenchObject; /** *
* This class instantiates an edge between two objects on the Workbench. * Edges are either between two WorkbenchObjects, or from one * WorkbenchObject to a Point (when we're in the middle of * drawing it). The Edge can then be used to determine where the start * and end points for the drawing of an arrow are, since this can be * highly variable when we are dealing with differently shaped objects. *
* Copyright 1999 by David Danks, Joe Ramsey, and Frank Wimberly. All * rights reserved. *
* * @see WorkbenchObject * * @version 1.0 June 5, 1999 * @author David Danks * @author Joe Ramsey * @author Frank Wimberly */ public class WorkbenchEdge { /////////////////// Class Variables /////////////////// public static final Color DRAW_COLOR = new Color(102, 153, 204); public static final Color SELECTED_COLOR = new Color(204, 0, 0); public static final Color ENABLED_COLOR = Color.black; public static final Color DISABLED_COLOR = new Color(204, 204, 204); /////////////////// Instance Variables /////////////////// private boolean selected = false; private boolean enabled = true; private WorkbenchObject fromObject; private WorkbenchObject toObject; private Point endPoint; /////////////////// Constructors /////////////////// /** * Creates an Edge between two WorkbenchObjects. * @param fromObj The WorkbenchObject the Edge is from * @param toObj The WorkbenchObject the Edge is to */ public WorkbenchEdge(WorkbenchObject fromObj, WorkbenchObject toObj) { fromObject = fromObj; toObject = toObj; endPoint = null; } /** * Creates an Edge between a WorkbenchObject and a Point on the Workbench. * @param fromObj The WorkbenchObject the Edge is from * @param endPoint The Point the Edge is to */ public WorkbenchEdge(WorkbenchObject fromObj, Point endPoint) { fromObject = fromObj; this.endPoint = endPoint; } /////////////////// Class Methods /////////////////// /** * Calculate the edge which goes from the side of one Shape to the * side of another non-overlapping Shape along a line which * connects their center points. * @param s1 The "from" Shape. * @param s2 The "to" Shape. * @return The PointPair for the Edge */ public static PointPair calculateEdge(Shape s1, Shape s2) { PointPair edge = new PointPair(); // first, determine the two rectangle centers Rectangle r1 = s1.getBounds(); int xcenter1 = (int)(r1.x + r1.width/2.0); int ycenter1 = (int)(r1.y + r1.height/2.0); Rectangle r2 = s2.getBounds(); int xcenter2 = (int)(r2.x + r2.width/2.0); int ycenter2 = (int)(r2.y + r2.height/2.0); // figure out the "from side" point Point pFrom = new Point(xcenter1, ycenter1); Point pTo = new Point(xcenter2, ycenter2); Point pMid = null; while (dist(pFrom, pTo) > 2.0) { pMid = new Point((pFrom.x + pTo.x) / 2, (pFrom.y + pTo.y) / 2); if (s1.contains((double)pMid.x, (double)pMid.y)) pFrom = pMid; else pTo = pMid; } edge.from = pMid; // now figure out the "to side" point pFrom = new Point(xcenter1, ycenter1); pTo = new Point(xcenter2, ycenter2); pMid = null; while (dist(pFrom, pTo) > 2.0) { pMid = new Point((pFrom.x + pTo.x) / 2, (pFrom.y + pTo.y) / 2); if (s2.contains((double)pMid.x, (double)pMid.y)) pTo = pMid; else pFrom = pMid; } edge.to = pMid; return edge; } /////////////////// Instance Methods /////////////////// /** * Returns the PointPair corresponding to the start and end points of the * Edge on the Workbench. This method actually just calls the appropriate *calculateEdge
method.
* @return The PointPair for the start and end points of the Edge
*/
public PointPair getPointPair()
{
if (endPoint != null)
return calculateEdge(fromObject.getPerimeter(),
new Rectangle(endPoint.x, endPoint.y, 1, 1));
else
return calculateEdge(fromObject.getPerimeter(),
toObject.getPerimeter());
}
/**
* @return The Polygon corresponding to the sleeve around the Edge
*/
public Polygon getSleeve() { return calcSleeve(getPointPair()); }
/**
* @param selected Sets whether the Edge is now selected
*/
public void setSelected(boolean selected) { this.selected = selected; }
/**
* @return Whether the Edge is currently selected
*/
public boolean isSelected() { return selected; }
/**
* @param enabled Sets whether the Edge is now enabled
*/
public void setEnabled(boolean enabled) { this.enabled = enabled; }
/**
* @return Whether the Edge is currently enabled
*/
public boolean isEnabled() { return enabled; }
/**
* @return The current Color of the Edge
*/
public Color getColor() {
if (endPoint != null)
return DRAW_COLOR;
if (selected && enabled)
return SELECTED_COLOR;
else if (enabled)
return ENABLED_COLOR;
else
return DISABLED_COLOR;
}
/**
* @return The WorkbenchObject the Edge is from
*/
public WorkbenchObject getFromObject() { return fromObject; }
/**
* @return The WorkbenchObject the Edge is to, if there is one;
* null
, otherwise.
*/
public WorkbenchObject getToObject() { return toObject; }
/**
* @return The Point the Edge is to, if there is one;
* null
, otherwise.
*/
public Point getEndPoint() { return endPoint; }
/**
* Sets the Edge to be to newTo
. Automatically sets endPoint
* to null
.
* @param newToObj The WorkbenchObject the Edge is now to
*/
public void setToObject(WorkbenchObject newToObj) {
if (newToObj == null) return;
toObject = newToObj;
endPoint = null;
}
/**
* Sets the Edge to be to p
. Automatically sets toObject
* to null
.
* @param p The Point the Edge is now to
*/
public void setEndPoint(Point p) {
if (p == null) return;
endPoint = p;
toObject = null;
}
/**
* Sets the Edge to be to (x, y)
. Automatically sets toVar
* to null
.
* @param x The X coordinate of the point the Edge is now to
* @param y The Y coordinate of the point the Edge is now to
*/
public void setEndPoint(int x, int y) { setEndPoint(new Point(x, y)); }
/**
* Gets a rectangular sleeve a default = 7 number of pixels on
* either side of a line segment with the endpoints given in
* PointPair pp. Used to determine when the user has clicked near
* enough to an edge to select it.
* @param pp A pair of endpoints for the line segment.
* @return A rectangle sleeve for the line segment from P to Q.
*/
private static Polygon calcSleeve(PointPair pp) {
int d = 7; // halfwidth of the sleeve.
if (Math.abs(pp.from.y - pp.to.y) <= 3)
return getHorizSleeve(pp, d);
int xpoints[] = new int[4];
int ypoints[] = new int[4];
double qx, qy;
qx = (double)(pp.to.x - pp.from.x);
qy = (double)(pp.to.y - pp.from.y);
double sx, sy;
sx = (double)(d * d) / (1.0 + (qx * qx) / (qy * qy));
sx = Math.pow(sx, 0.5);
sy = - (qx / qy) * sx;
sx += (double)pp.from.x + 1.0;
sy += (double)pp.from.y + 1.0;
Point t = new Point(
(int)(sx) - pp.from.x,
(int)(sy) - pp.from.y
);
xpoints[0] = pp.from.x + t.x;
xpoints[1] = pp.to.x + t.x;
xpoints[2] = pp.to.x - t.x;
xpoints[3] = pp.from.x - t.x;
ypoints[0] = pp.from.y + t.y;
ypoints[1] = pp.to.y + t.y;
ypoints[2] = pp.to.y - t.y;
ypoints[3] = pp.from.y - t.y;
return new Polygon(xpoints, ypoints, 4);
}
/**
* Calculates a polygon sleeve for the special case where the line
* segment joining P and Q is horizontal.
* @param pp A pair of endpoints for the line segment.
* @param d The halfwidth of the rectangle in pixels
* @return A rectangle sleeve for the line segment from P to Q.
*/
private static Polygon getHorizSleeve(PointPair pp, int d) {
int[] xpoints = new int[4];
int[] ypoints = new int[4];
xpoints[0] = pp.from.x;
xpoints[1] = pp.from.x;
xpoints[2] = pp.to.x;
xpoints[3] = pp.to.x;
ypoints[0] = pp.from.y + d;
ypoints[1] = pp.from.y - d;
ypoints[2] = pp.to.y - d;
ypoints[3] = pp.to.y + d;
return new Polygon(xpoints, ypoints, 4);
}
/**
* Calculates the distance between two points.
* @param p1 The first point.
* @param p2 The second point.
*/
private static double dist(Point p1, Point p2) {
double d = 0.0;
d = (p1.x - p2.x)*(p1.x - p2.x);
d += (p1.y - p2.y)*(p1.y - p2.y);
d = Math.sqrt(d);
return d;
}
/**
* @return A clone of this Edge
*/
public Object clone() {
if (toObject != null) return new WorkbenchEdge(fromObject, toObject);
else return new WorkbenchEdge(fromObject, endPoint);
}
}