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); } }