/*
* @(#)OffsetConnector.java
*
* Project: JHotdraw - a GUI framework for technical drawings
* http://www.jhotdraw.org
* http://jhotdraw.sourceforge.net
* Copyright: (c) by the original author(s) and all contributors
* License: Lesser GNU Public License (LGPL)
* http://www.opensource.org/licenses/lgpl-license.html
*/
package org.jhotdraw.standard;
import java.awt.*;
import org.jhotdraw.framework.*;
import org.jhotdraw.figures.*;
import org.jhotdraw.util.*;
/**
* An OffsetConnector locates connection points with the help of an
* OffsetLocator.
* <p>
* It allows the dynamic creation of connection points for new LineConnections.
* <p>
* It dynamically adjusts connection points when connection handles are
* dragged.
* <p>
* This class is not thread safe
*
* <hr>
* <b>Design Patterns </b>
* <P>
* <img src="images/red-ball-small.gif" width=6 height=6 alt=" o "><b><a
* href=../pattlets/sld036.htm>Proxy </a> </b> <br>
* <b><a href=../pattlets/sld036.htm>Prototype </a> </b> <br>
* Tracking connectors are Proxy Objects that are provided by the
* trackConnector method when new connectors need to be created. The tracking
* connectors permit deferral of OffsetConnector creation until the
* finalizeConnector() method is called. New connectors are then created by
* copying the tracking connectors (as in the Prototype pattern).
* <hr>
*
* @see OffsetLocator
* @see Connector
*/
public class OffsetConnector extends LocatorConnector {
// Static trackingConnectors are used to minimize object creation.
// A static trackingPoint minimizes Point creation.
static public OffsetConnector trackingConnector1 = new OffsetConnector();
static public OffsetConnector trackingConnector2 = new OffsetConnector();
static private Point trackingPoint = new Point();
// variables used in trackConnector() to control what trackingConnector to
// use
static private OffsetConnector firstConnector;
static private OffsetConnector lastConnector;
// need GridConstraint of view
static private DrawingView view;
// The variable fOwnerBox preserves the owner's previous display box;
// it is used to maintain line orientations during resizing and to
// minimize Rectangle creation.
private transient Rectangle fOwnerBox;
/**
* Called when a ConnectionTool starts a new connection. (ConnectionTool
* MouseDown event).
*
* @param drawingView -
* the current DrawingView; needed for it's GridConstrainer
*/
public void reset(DrawingView drawingView) {
if (this == trackingConnector1) {
view = drawingView;
firstConnector = null;
lastConnector = null;
trackingConnector1.fOwner = null;
trackingConnector2.fOwner = null;
}
}
/**
* Use this method to create new OffsetConnectors.
*
* <p>
* Returns a tracking Connector initialized to the required owner and
* location. The trackingConnector will create a new connector when it's
* method finalizeConnector() is called.
*
* <p>
* This method depends on trackingConnector1.reset() resetting the
* trackingConnectors
*
* @see finalizeConnector(boolean start)
*
* @param owner - the owning figure
* @param x - x co-ordinate
* @param y - y co-ordinate
* @return - either trackingConnector1 or trackingConnector2 (if 1 is in use)
*/
static public OffsetConnector trackConnector(Figure owner, int x, int y) {
OffsetConnector trackingConnector = trackingConnector1;
//This method depends on reset() nullifying firstConnector and
//lastConnector. It also depends on the tracking connector's owner
//being set to null in reset(). Otherwise fOwner and fOwnerBox would
// have
// to be set unconditionally and would then create a Rectangle on every
// call.
if (firstConnector != null && owner != trackingConnector1.owner()) {
trackingConnector = trackingConnector2;
}
if (trackingConnector.fOwner != owner) {
trackingConnector.fOwner = owner;
trackingConnector.fOwnerBox = owner.displayBox();
}
if (firstConnector == null) {
firstConnector = trackingConnector;
}
lastConnector = trackingConnector;
return trackingConnector.calculateFigureConstrainedOffsets(x, y);
}
/**
* Constructs a connector that has no owner. It is used internally to
* resurrect a connector from a StorableOutput and to create the static
* tracking connectors.
*/
public OffsetConnector() {
OffsetLocator loc = new OffsetLocator(RelativeLocator.northWest());
myLocator = loc;
fOwner = null;
fOwnerBox = new Rectangle();
//System.out.println("OffsetConnector()-Tracking Only");
}
/**
* Constructs an OffsetConnector with the given owner and given location.
* It is called only by the finalizeConnector method;
*
* @param owner
* @param offsetX
* @param offsetY
*/
private OffsetConnector(Figure owner, int offsetX, int offsetY) {
super(owner, null);
OffsetLocator loc = new OffsetLocator(RelativeLocator.northWest(), offsetX, offsetY);
myLocator = loc;
fOwnerBox = owner.displayBox();
//System.out.println("OffsetConnector(" + owner.toString()+","+offsetX
// +","+offsetY+")");
}
/**
* Returns a newly created OffsetConnector for tracking connectors. The
* tracking connector's owner and offsets are copied to the new connector.
* <p>
* Existing non-tracking connectors are returned unchanged without side
* effects.
* <p>
* This method is called by the connectStart(Connector) and the
* connectEnd(Connector) methods of the LineConnection object
*
* @see LineConnection
*
* @param start -
* a boolean indicating whether the receiver is a start or end
* Connector
* @return - the receiver unchanged if it is not a tracking connector; a
* new Offset connector if this is a tracking connector.
*/
public Connector finalizeConnector(boolean start) {
if ((this != OffsetConnector.trackingConnector1)
&& (this != OffsetConnector.trackingConnector2)) {
return this;
}
OffsetLocator l = (OffsetLocator) myLocator;
OffsetConnector o = new OffsetConnector(owner(), l.fOffsetX, l.fOffsetY);
// an adjustment to the end connector that helps draw vertical or
// horizontal lines.
// This adjustment applies only to the initial rendering of the line
// and has no
// subsequent effect
// N.B. trackingConnector2 is used iff 2 connectors needed ... new line
// connection
if (this == OffsetConnector.trackingConnector2) {
int p1X = trackingConnector1.locateX();
int p1Y = trackingConnector1.locateY();
int p2X = locateX();
int p2Y = locateY();
if (Math.abs(p1X - p2X) <= 8) p2X = p1X;
if (Math.abs(p1Y - p2Y) <= 8) p2Y = p1Y;
l = (OffsetLocator) o.myLocator;
l.fOffsetX = Geom.range(0, fOwnerBox.width, p2X - fOwnerBox.x);
l.fOffsetY = Geom.range(0, fOwnerBox.height, p2Y - fOwnerBox.y);
}
return o;
}
/**
* Resets offsets for an existing OffsetConnector. Called when dragging a
* ChangeConnectionHandle.
*
* @see org.jhotdraw.standard.ChangeConnectionHandle
*
* @param x -
* x coordinate of point moved to
* @param y -
* y coordinate of point moved to
* @see org.jhotdraw.framework.Connector#connectorMovedTo(int, int)
*/
public Point connectorMovedTo(int x, int y) {
calculateFigureConstrainedOffsets(x, y);
// adjustment to make it easier for user to position point
// will use x or y parameters under certain conditions overriding
// calculated point
// only applies to sides of figure & the adjusted point will still lie
// on appropriate side
int px = locateX();
int py = locateY();
OffsetLocator l = (OffsetLocator) myLocator;
if (owner() instanceof RectangleFigure) {
if (Math.abs(py - y) <= 3) {
if (l.fOffsetX == 0 || l.fOffsetX == fOwnerBox.width) {
// can use y
l.fOffsetY = Geom.range(0, fOwnerBox.height, y - fOwnerBox.y);
}
}
if (Math.abs(px - x) <= 3) {
if (l.fOffsetY == 0 || l.fOffsetY == fOwnerBox.height) {
// can use x
l.fOffsetX = Geom.range(0, fOwnerBox.width, x - fOwnerBox.x);
}
}
}
return new Point(locateX(), locateY());
}
/**
* Gets the connection point. If the owner is resized the connection points
* are (visually) preserved provided they lie on the box of the resized
* figure.
*
* @see org.jhotdraw.standard.AbstractConnector#findPoint(org.jhotdraw.framework.ConnectionFigure)
*/
protected Point findPoint(ConnectionFigure connection) {
Rectangle r = owner().displayBox();
if (fOwnerBox.width == 0 && fOwnerBox.height == 0) {
// for deSerialization
fOwnerBox = r;
}
OffsetLocator l = (OffsetLocator) myLocator;
Point p1 = locate(connection);
// if not resized then no adjustments needed
if (fOwnerBox.width == r.width && fOwnerBox.height == r.height) {
fOwnerBox = r;
//System.out.println("findPoint - " +this.toString() +":"+
// p1.toString());
return p1;
}
// ????
if (owner() instanceof EllipseFigure) {
calculateFigureConstrainedOffsets(p1.x, p1.y);
fOwnerBox = r;
return p1;
}
//get the point (use previous box with offsets)
p1.x = fOwnerBox.x + l.fOffsetX;
p1.y = fOwnerBox.y + l.fOffsetY;
if (l.fOffsetX == 0) {
p1.x = r.x;
}
else if (l.fOffsetX == fOwnerBox.width) {
p1.x = r.x + r.width;
}
if (l.fOffsetY == 0) {
p1.y = r.y;
}
else if (l.fOffsetY == fOwnerBox.height) {
p1.y = r.y + r.height;
}
if (view != null && view.getConstrainer() != null) {
p1 = view.getConstrainer().constrainPoint(p1);
}
l.fOffsetX = Geom.range(0, r.width, p1.x - r.x);
l.fOffsetY = Geom.range(0, r.height, p1.y - r.y);
fOwnerBox = r;
//System.out.println("findPoint(x) - " +this.toString() +":"+
// p1.toString());
return p1;
}
/**
* @return the connector Point
*/
protected Point locate(ConnectionFigure connection) {
return myLocator.locate(owner());
}
/**
* @return the x-coordinate of this connector
*/
public int locateX() {
OffsetLocator l = (OffsetLocator) myLocator;
return fOwnerBox.x + l.fOffsetX;
}
/**
* @return the y-coordinate of this connector
*/
public int locateY() {
OffsetLocator l = (OffsetLocator) myLocator;
return fOwnerBox.y + l.fOffsetY;
}
/**
* Constrains the point (x,y) to the figure and calculates the offsets for
* the resulting constrained point.
*
* @param x - x coordinate
* @param y - y coordinate
*/
public OffsetConnector calculateFigureConstrainedOffsets(int x, int y) {
// minimize Point, Rectangle & other object creation
// as this method is called by the trackConnector() method.
trackingPoint = calculateFigureConstrainedTrackingPoint(x, y);
OffsetLocator l = (OffsetLocator) myLocator;
l.fOffsetX = trackingPoint.x - fOwnerBox.x;
l.fOffsetY = trackingPoint.y - fOwnerBox.y;
return this;
}
/**
* Constrains the point (x,y) to the figure and returns a constrained
* point. This method can be overridden for different figure types or
* different constraining policies.
* <p>
* For efficiency reasons the <em>same</em> point object is returned from
* every call. Be careful not to publicly expose this internal tracking
* point when overriding.
* <p>
* This method is NOT thread safe.
*
* @param x - x coordinate
* @param y - y coordinate
* @return internal tracking point containing the constrained coordinates
*/
protected Point calculateFigureConstrainedTrackingPoint(int x, int y) {
// minimize Point, Rectangle & other object creation
trackingPoint.x = x;
trackingPoint.y = y;
if (view != null && view.getConstrainer() != null) {
trackingPoint = view.getConstrainer().constrainPoint(trackingPoint);
}
if (!(owner() instanceof EllipseFigure)) {
trackingPoint = Geom.angleToPoint(fOwnerBox, Geom
.pointToAngle(fOwnerBox, trackingPoint));
}
else {
trackingPoint = Geom.ovalAngleToPoint(fOwnerBox, Geom.pointToAngle(fOwnerBox,
trackingPoint));
}
return trackingPoint;
}
}