blob: 2ce9836bdad37816c75d7d5009915a4864ad5e16 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006, 2010 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
****************************************************************************/
package org.eclipse.gmf.runtime.draw2d.ui.figures;
import org.eclipse.draw2d.AbstractConnectionAnchor;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.common.core.util.StringStatics;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PrecisionPointList;
/**
* Provides the implementation of Slidable anchor
*
* @author oboyko / sshaw
*
*/
public class BaseSlidableAnchor
extends AbstractConnectionAnchor implements OrthogonalConnectionAnchor {
final private static char TERMINAL_START_CHAR = '(';
final private static char TERMINAL_DELIMITER_CHAR = ',';
final private static char TERMINAL_END_CHAR = ')';
// The connection anchor reference point (sometimes the same as anchor location)
private PrecisionPoint relativeReference;
/**
* Empty constructor
*/
public BaseSlidableAnchor() {
// empty constructor
}
/**
* Default constructor. The anchor will have the center of the figure as the
* reference point
*
* @param f <code>IFigure</code> that this anchor is associated with.
*/
public BaseSlidableAnchor(IFigure f) {
super(f);
}
/**
* Constructor. Takes point p to store the reference point
*
* @param f <code>IFigure</code> that this anchor is associated with.
* @param p the <code>PrecisionPoint</code> that the anchor will initially attach to.
*/
public BaseSlidableAnchor(IFigure f, PrecisionPoint p) {
super(f);
this.relativeReference = new PrecisionPoint(p.preciseX, p.preciseY);
}
/**
* Creates terminal string for slidable anchor
*
* @return <code>String</code> terminal for slidable anchor
*/
public String getTerminal() {
if (isDefaultAnchor())
return StringStatics.BLANK;
return composeTerminalString(relativeReference);
}
/*
* (non-Javadoc)
* @see org.eclipse.draw2d.ConnectionAnchor#getReferencePoint()
*/
public Point getReferencePoint() {
return getAnchorPosition();
}
/**
* Creates a terminal string for any reference point passed in the format understandable by
* slidable anchors
*
* @param p - a <Code>PrecisionPoint</Code> that must be represented as a unique
* <Code>String</Code>, namely as "(preciseX,preciseY)"
* @return <code>String</code> terminal composed from specified <code>PrecisionPoint</code>
*/
private String composeTerminalString(PrecisionPoint p) {
StringBuffer s = new StringBuffer(24);
s.append(TERMINAL_START_CHAR); // 1 char
s.append(p.preciseX); // 10 chars
s.append(TERMINAL_DELIMITER_CHAR); // 1 char
s.append(p.preciseY); // 10 chars
s.append(TERMINAL_END_CHAR); // 1 char
return s.toString(); // 24 chars max (+1 for safety, i.e. for string termination)
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj instanceof BaseSlidableAnchor) {
BaseSlidableAnchor anchor = (BaseSlidableAnchor) obj;
/*
* Owning figures must be identical to satisfy equality of anchors
*/
if (getOwner() == anchor.getOwner()) {
if (isDefaultAnchor()) {
return anchor.isDefaultAnchor();
}
return relativeReference.equals(anchor.relativeReference);
}
}
return false;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int figureHashCode = getOwner() != null ? getOwner().hashCode() : 0;
if (relativeReference == null) {
return figureHashCode;
}
return new Double(relativeReference.preciseX()).hashCode()
^ new Double(relativeReference.preciseY()).hashCode()
^ figureHashCode;
}
/**
* From relative reference returns the relative coordinates of the anchor
* Method's visibility can be changed as needed
*/
private Point getAnchorPosition() {
PrecisionRectangle rBox = new PrecisionRectangle(getBox());
if (isDefaultAnchor())
return rBox.getCenter();
return new PrecisionPoint(relativeReference.preciseX * rBox.preciseWidth
+ rBox.preciseX, relativeReference.preciseY * rBox.preciseHeight
+ rBox.preciseY);
}
/**
* Calculates the location of the anchor depending on the anchors own reference
* and foreign reference points
*
* @param ownReference - the own reference of the anchor
* @param foreignReference - foreign reference that comes in
* @return the location of the anchor depending on the anchors own reference
* and foreign reference points
*/
protected Point getLocation(Point ownReference, Point foreignReference) {
PointList intersections = getIntersectionPoints(ownReference, foreignReference);
if (intersections!=null && intersections.size()!=0) {
Point location = PointListUtilities.pickClosestPoint(intersections,
foreignReference);
return location;
}
return null;
}
static private int STRAIGHT_LINE_TOLERANCE = 10;
/*
* (non-Javadoc)
* @see org.eclipse.draw2d.ConnectionAnchor#getLocation(org.eclipse.draw2d.geometry.Point)
*/
public Point getLocation(Point reference) {
Point ownReference = normalizeToStraightlineTolerance(reference, getReferencePoint(), STRAIGHT_LINE_TOLERANCE);
Point location = getLocation(ownReference, reference);
if (location == null) {
location = getLocation(new PrecisionPoint(getBox().getCenter()), reference);
if (location == null) {
location = getBox().getCenter();
}
}
return location;
}
/**
* Returns a new owned reference point that is normalized to be with-in a straight-line
* tolerance value.
*
* @param foreignReference <code>Point</code> that is the foreign reference point used to calculate
* the interfection anchor point on the shape in absolute coordinates.
* @param ownReference <code>Point</code> that is the reference point with-in the shape in
* absolute coordinates
* @param tolerance <code>int</code> value that is the difference in absolute coordinates where the
* two points would be considered straight and then adjusted.
* @return <code>Point</code> that is the normalized owned reference to be with-in a given
* straight-line tolerance value of the foreign reference point.
*/
protected Point normalizeToStraightlineTolerance(Point foreignReference, Point ownReference, int tolerance) {
PrecisionPoint preciseOwnReference = new PrecisionPoint(ownReference);
PrecisionPoint normalizedReference = (PrecisionPoint)preciseOwnReference.getCopy();
PrecisionPoint preciseForeignReference = new PrecisionPoint(foreignReference);
if (Math.abs(preciseForeignReference.preciseX - preciseOwnReference.preciseX) < tolerance) {
normalizedReference.preciseX = preciseForeignReference.preciseX;
normalizedReference.updateInts();
return normalizedReference;
}
if (Math.abs(preciseForeignReference.preciseY - preciseOwnReference.preciseY) < tolerance) {
normalizedReference.preciseY = preciseForeignReference.preciseY;
normalizedReference.updateInts();
}
return normalizedReference;
}
/**
* Calculates intersection points of the figure and the line that passes through
* ownReference and foreignReference points
*
* @param ownReference the reference <code>Point</code> on or inside the shape that is being
* anchored to.
* @param foreignReference the outside reference <code>Point</code> point that is the terminal
* end of the line formed by the two parameters.
* @return intersection points of the figure and the line that passes through
* ownReference and foreignReference points
*/
protected PointList getIntersectionPoints(Point ownReference, Point foreignReference) {
final PointList polygon = getPolygonPoints();
return (new LineSeg(ownReference, foreignReference)).getLineIntersectionsWithLineSegs(polygon);
}
/**
* Returns the list of all the vertices of the figure.
* The created list must form a polygon, i.e. closed polyline, for figures
* hence the starting and ending points must be the same
*
* @return the <code>PointList</code> list of all the vertices of the figure.
*/
protected PointList getPolygonPoints() {
if (getOwner() instanceof IPolygonAnchorableFigure) {
PrecisionPointList polyList = new PrecisionPointList(((IPolygonAnchorableFigure) getOwner()).getPolygonPoints());
getOwner().translateToAbsolute(polyList);
return polyList;
}
PrecisionRectangle r = new PrecisionRectangle(getBox());
PrecisionPointList ptList = new PrecisionPointList(5);
ptList.addPoint(new PrecisionPoint(r.preciseX, r.preciseY));
ptList.addPoint(new PrecisionPoint(r.preciseX + r.preciseWidth, r.preciseY));
ptList.addPoint(new PrecisionPoint(r.preciseX + r.preciseWidth, r.preciseY + r.preciseHeight));
ptList.addPoint(new PrecisionPoint(r.preciseX, r.preciseY + r.preciseHeight));
ptList.addPoint(new PrecisionPoint(r.preciseX, r.preciseY));
return ptList;
}
/**
* Calculates the relative location of the reference point with respect to the bounds
* of the figure. If point p is not inside of the figure's bounds then the point
* is mapped on the bounds and the point relative location is calculated
*
* @param p the <code>Point</code> that is relative coordinates of the point
* @return <Code>PrecisionPoint</Code>, i.e. the relative reference for
* <Code>SlidableAnchor</Code>
*/
static public PrecisionPoint getAnchorRelativeLocation(Point p, Rectangle bounds) {
if (bounds.width == 0 || bounds.height == 0) {
/*
* If figure hasn't been laid out yet, we don't want to fail the slidable anchor creation.
* Hence, we'll just return the (0.5, 0.5) meaning that the anchor reference point is the center of the figure.
*/
return new PrecisionPoint(0.5, 0.5);
}
PrecisionPoint relLocation;
PrecisionPoint temp = new PrecisionPoint(p);
if (p.x < bounds.x || p.x > bounds.x + bounds.width
|| p.y < bounds.y || p.y > bounds.y + bounds.height) {
if (p.x < bounds.x || p.x > bounds.x + bounds.width) {
temp.preciseX = p.x < bounds.x ? bounds.x
: bounds.x + bounds.width;
}
if (p.y < bounds.y || p.y > bounds.y + bounds.height) {
temp.preciseY = p.y < bounds.y ? bounds.y
: bounds.y + bounds.height;
}
relLocation = new PrecisionPoint((temp.preciseX - bounds.x)
/ bounds.width, (temp.preciseY - bounds.y)
/ bounds.height);
} else {
relLocation = new PrecisionPoint((temp.preciseX - bounds.x)
/ bounds.width, (temp.preciseY - bounds.y)
/ bounds.height);
}
return relLocation;
}
/**
* Gets the anchors associated figure's bounding box in absolute coordinates.
*
* @return a <code>Rectangle</code> that is the bounding box of the owner figure
* in absolute coordinates
*/
protected Rectangle getBox() {
Rectangle rBox = (getOwner() instanceof Connection) ? ((Connection) getOwner())
.getPoints().getBounds()
: getOwner().getBounds();
PrecisionRectangle box = new PrecisionRectangle(rBox);
getOwner().translateToAbsolute(box);
return box;
}
/**
* Returns true if the <Code>SlidableAnchor</Code> is default one with a reference at the center
*
* @return <code>boolean</code> <code>true</code> is the <code>SlidableAnchor</code> is default one, <code>false</code> otherwise
*/
public boolean isDefaultAnchor() {
return relativeReference == null;
}
/**
* Parses anchors terminal string and returns the relative reference icorporated
* in the terminal string
*
* @param terminal - the terminal string containing relative reference
* @return returns the relative reference incorporated in the terminal string
*/
public static PrecisionPoint parseTerminalString(String terminal) {
try {
return new PrecisionPoint(Double.parseDouble(terminal.substring(
terminal.indexOf(BaseSlidableAnchor.TERMINAL_START_CHAR) + 1,
terminal.indexOf(BaseSlidableAnchor.TERMINAL_DELIMITER_CHAR))),
Double.parseDouble(terminal.substring(terminal
.indexOf(BaseSlidableAnchor.TERMINAL_DELIMITER_CHAR) + 1,
terminal.indexOf(BaseSlidableAnchor.TERMINAL_END_CHAR))));
} catch (Exception e) {
return null;
}
}
public Point getOrthogonalLocation(Point orthoReference) {
PrecisionPoint ownReference = new PrecisionPoint(getReferencePoint());
// PrecisionRectangle bounds = new PrecisionRectangle(getBox());
PrecisionRectangle bounds = new PrecisionRectangle(FigureUtilities.getAnchorableFigureBounds(getOwner()));
getOwner().translateToAbsolute(bounds);
bounds.expand(0.000001, 0.000001);
PrecisionPoint preciseOrthoReference = new PrecisionPoint(orthoReference);
int orientation = PositionConstants.NONE;
if (bounds.contains(preciseOrthoReference)) {
int side = getClosestSide(ownReference, bounds);
switch (side) {
case PositionConstants.LEFT:
case PositionConstants.RIGHT:
ownReference.preciseY = preciseOrthoReference.preciseY();
orientation = PositionConstants.HORIZONTAL;
break;
case PositionConstants.TOP:
case PositionConstants.BOTTOM:
ownReference.preciseX = preciseOrthoReference.preciseX();
orientation = PositionConstants.VERTICAL;
break;
}
} else if (preciseOrthoReference.preciseX >= bounds.preciseX && preciseOrthoReference.preciseX <= bounds.preciseX + bounds.preciseWidth) {
ownReference.preciseX = preciseOrthoReference.preciseX;
orientation = PositionConstants.VERTICAL;
} else if (preciseOrthoReference.preciseY >= bounds.preciseY && preciseOrthoReference.preciseY <= bounds.preciseY + bounds.preciseHeight) {
ownReference.preciseY = preciseOrthoReference.preciseY;
orientation = PositionConstants.HORIZONTAL;
}
ownReference.updateInts();
Point location = getLocation(ownReference, preciseOrthoReference);
if (location == null) {
location = getLocation(orthoReference);
orientation = PositionConstants.NONE;
}
if (orientation != PositionConstants.NONE) {
PrecisionPoint loc = new PrecisionPoint(location);
if (orientation == PositionConstants.VERTICAL) {
loc.preciseX = preciseOrthoReference.preciseX;
} else {
loc.preciseY = preciseOrthoReference.preciseY;
}
loc.updateInts();
location = loc;
}
return location;
}
/**
* Returns the position of the closest edge of the rectangle closest to the point
* @param p the point
* @param r the rectangle
* @return position of the closest edge
*/
private static int getClosestSide(Point p, Rectangle r) {
double diff = Math.abs(r.preciseX() + r.preciseWidth() - p.preciseX());
int side = PositionConstants.RIGHT;
double currentDiff = Math.abs(r.preciseX() - p.preciseX());
if (currentDiff < diff) {
diff = currentDiff;
side = PositionConstants.LEFT;
}
currentDiff = Math.abs(r.preciseY() + r.preciseHeight() - p.preciseY());
if (currentDiff < diff) {
diff = currentDiff;
side = PositionConstants.BOTTOM;
}
currentDiff = Math.abs(r.preciseY() - p.preciseY());
if (currentDiff < diff) {
diff = currentDiff;
side = PositionConstants.TOP;
}
return side;
}
}