/********************************************************************************
* *
* (c) Copyright 2010 Verizon Communications USA and The Open University UK *
* *
* This software is freely distributed in accordance with *
* the GNU Lesser General Public (LGPL) license, version 3 or later *
* as published by the Free Software Foundation. *
* For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html *
* and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html *
* *
* This software is provided by the copyright holders and contributors "as is" *
* and any express or implied warranties, including, but not limited to, the *
* implied warranties of merchantability and fitness for a particular purpose *
* are disclaimed. In no event shall the copyright owner or contributors be *
* liable for any direct, indirect, incidental, special, exemplary, or *
* consequential damages (including, but not limited to, procurement of *
* substitute goods or services; loss of use, data, or profits; or business *
* interruption) however caused and on any theory of liability, whether in *
* contract, strict liability, or tort (including negligence or otherwise) *
* arising in any way out of the use of this software, even if advised of the *
* possibility of such damage. *
* *
********************************************************************************/
package com.compendium.ui;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.UIDefaults;
import com.compendium.core.ICoreConstants;
import com.compendium.ui.plaf.LineUI;
/**
* This is the base class for links in Compendium maps.
*
* @author Ron van Hoof / Michelle Bachler
*/
public class UILine extends JComponent {
/** Defines the points as points in parent component's coordinate system */
public final static int ABSOLUTE = 0;
/** Defines the points as points in this component's coordinate system */
public final static int RELATIVE = 1;
/** A reference to the link arrow property for PropertyChangeEvents.*/
public static final String ARROW_PROPERTY = "linkarrow"; //$NON-NLS-1$
/** A reference to the link arrow property for PropertyChangeEvents.*/
public static final String THICKNESS_PROPERTY = "linkthickness"; //$NON-NLS-1$
/** The default arrow head width.*/
protected int nArrowWidth = 7;
/** The current arrow head width - scaled.*/
protected int nCurrentArrowWidth = 7;
/** The line thickness.*/
protected int nThickness = 1;
/** The current line thickness - scaled.*/
protected int nCurrentThickness = 1;
/** The arrow style for this line.*/
private int nArrow = ICoreConstants.ARROW_TO;
/** The origin point fo the line.*/
private Point ptFrom = null;
/** The destination point of the line.*/
private Point ptTo = null;
/** the coordinate type for this line (ABSOLUTE/RELATIVE).*/
private int nCoordinateType = ABSOLUTE;
/** Is the line currently selected?*/
private boolean bSelected = false;
/** Is the line currently rolledover?*/
private boolean bRollover = false;
/** The selection line color.*/
private Color oSelectedColor = Color.yellow;
/** The minimum component width for the line.*/
private int nMinWidth = nThickness;
/**
* Returns the intersecting points of the line with the the given
* start and end point and given rectangle. If the line does not
* intersect with the rectangle an empty array will be returned
*
* @param r the Rectangle to check.
* @param a the origin point of the line to check.
* @param b the destination point of the line to check.
* @return Point[] the points the rectangle and line intersect.
*/
public static Point[] intersectionWithRectangle(Rectangle r, Point a, Point b) {
Point[] pts = new Point[2];
Point p1 = computeIntersectionWithRectangle(r, a, b);
Point p2 = computeIntersectionWithRectangle(r, b, a);
if (p1 != null) {
pts[0] = p1;
// if line intersect in only one point, don't store second point
if ((p2 != null) && ((p1.x != p2.x) || (p1.y != p2.y)))
pts[1] = p2;
}
else if (p2 != null) {
pts[0] = p2;
}
return pts;
}
/**
* Returns the intersecting point of the line with the given from and
* to point with the given rectangle. To get the other intersecting point
* the from and to points need to be reversed.
*
* @param r the Rectangle to check.
* @param from the origin point of the line to check.
* @param to the destination point of the line to check.
* @return Point the point the rectangle and line intersect, else null it they don't.
*/
private static Point computeIntersectionWithRectangle(Rectangle r, Point from, Point to) {
Point pt = new Point();
if ((from.x == to.x)&& (from.y == to.y)) return null;
//line to the right of rectangle
if ((from.x>r.x+r.width) && (to.x>r.x+r.width)) return null;
//line below rectangle
if ((from.y>r.y+r.height) && (to.y>r.y+r.height)) return null;
//line to left of rectangle
if ((from.x<r.x) && (to.x<r.x)) return null;
//line above rectangle
if ((from.y<r.y) && (to.y<r.y)) return null;
if (to.y != from.y) {
if (r.y+r.height<=to.y) {
pt.y=r.y+r.height;
pt.x=from.x+(to.x-from.x)*(r.y+r.height-from.y)/(to.y-from.y);
}
else {
pt.y=r.y;
pt.x=from.x+(to.x-from.x)*(r.y-from.y)/(to.y-from.y);
}
}
if (to.y==from.y || r.x>pt.x || pt.x>=r.x+r.width) {
if (r.x+r.width<=to.x) {
pt.y=from.y+(to.y-from.y)*(r.x+r.width-from.x)/(to.x-from.x);
pt.x=r.x+r.width;
}
else {
pt.y=from.y+(to.y-from.y)*(r.x-from.x)/(to.x-from.x);
pt.x=r.x;
}
}
// check if point is in boundaries of rectangle
if (contains(r, pt))
return pt;
else
return null;
}
/**
* This contains method is a workaround for a bug in the contains method of Rectangle.
* It does not include the right and bottom sides as part of the rectangle.
*
* @param r, the Rectangle to check for the point in.
* @param p, the point for check for.
* @return boolean, true if the given rectangle contains the given point, else false.
*/
private static boolean contains(Rectangle r, Point p) {
return (p.x >= r.x) && ((p.x - r.x) <= r.width) && (p.y >= r.y) && ((p.y-r.y) <= r.height);
}
/**
* Creates a line with the given to and from points in the coordinate
* system defined by the nType and adds arrows as defined by nArrow.
* @param ptFrom, the origin point for the line.
* @param ptTo, the destination point for the line.
* @param arrow, the arrow style for this line.
* @param nCoordinateType, the coordinate type for this line (ABSOLUTE / RELATIVE).
*/
public UILine(Point ptFrom, Point ptTo, int nArrow, int nCoordinateType) {
setFrom(ptFrom);
setTo(ptTo);
setArrow(nArrow);
setCoordinateType(nCoordinateType);
updateUI();
}
/**
* Creates a line with the given to and from points in the coordinate
* system of its parent (absolute) with the given arrow.
* @param ptFrom, the origin point for the line.
* @param ptTo, the destination point for the line.
* @param arrow, the arrow style for this line.
*/
public UILine(Point ptFrom, Point ptTo, int nArrow) {
this(ptFrom, ptTo, nArrow, ABSOLUTE);
}
/**
* Creates a line with the given to and from points in the coordinate
* system of its parent (absolute) and no arrows.
* @param ptFrom, the origin point for the line.
* @param ptTo, the destination point for the line.
*/
public UILine(Point ptFrom, Point ptTo) {
this(ptFrom, ptTo, ICoreConstants.ARROW_TO, ABSOLUTE);
}
/**
* Creates a line with an absolute coordinate system (of the parent) and no arrows.
*/
public UILine() {
this(null, null, ICoreConstants.NO_ARROW, ABSOLUTE);
}
/**
* Returns the L&F object that renders this component.
*
* @return LineUI object
*/
public LineUI getUI() {
return (LineUI)ui;
}
/**
* Sets the L&F object that renders this component.
*
* @param ui, the LineUI L&F object
*/
public void setUI(LineUI ui) {
super.setUI(ui);
}
/**
* Notification from the UIFactory that the L&F
* has changed.
*
* @see JComponent#updateUI
*/
public void updateUI() {
//setUI((LinkUI)UIManager.getUI(this));
setUI(new LineUI());
invalidate();
}
/**
* Returns a string that specifies the name of the l&f class
* that renders this component.
*
* @return String "LineUI"
*
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public String getUIClassID() {
return "LineUI"; //$NON-NLS-1$
}
/**
* Return the origin point for this line.
* @return Point, the origin point for this line.
*/
public Point getFrom() {
return ptFrom;
}
/**
* Set the origin point for this line. Fires a property change event.
* @param pt, the origin point for this line.
*/
public void setFrom(Point pt) {
if (ptFrom == null || pt.x != ptFrom.x || pt.y != ptFrom.y) {
Point oldValue = ptFrom;
ptFrom = pt;
firePropertyChange("from", oldValue, ptFrom); //$NON-NLS-1$
repaint();
}
}
/**
* Return the destination point for this line.
* @return Point, the destination point for this line.
*/
public Point getTo() {
return ptTo;
}
/**
* Set the destination point for this line. Fires a property change event.
* @param pt, the destination point for this line.
*/
public void setTo(Point pt) {
if (ptTo == null || pt.x != ptTo.x || pt.y != ptTo.y) {
Point oldValue = ptTo;
ptTo = pt;
firePropertyChange("to", oldValue, ptTo); //$NON-NLS-1$
repaint();
}
}
/**
* Return the current arrow head style for this line.
* @return int, the arrow head style.
* @see com.compendium.core.ICoreConstants#NO_ARROW
* @see com.compendium.core.ICoreConstants#ARROW_TO
* @see com.compendium.core.ICoreConstants#ARROW_FROM
* @see com.compendium.core.ICoreConstants#ARROW_TO_AND_FROM
*/
public int getArrow() {
return nArrow;
}
/**
* Set the arrow head style for this line. Fires a property change event.
* @param arrow, the arrow head style for this line.
* @see com.compendium.core.ICoreConstants#NO_ARROW
* @see com.compendium.core.ICoreConstants#ARROW_TO
* @see com.compendium.core.ICoreConstants#ARROW_FROM
* @see com.compendium.core.ICoreConstants#ARROW_TO_AND_FROM
*/
public void setArrow(int arrow) {
if (nArrow == arrow)
return;
Integer oldValue = new Integer(nArrow);
nArrow = arrow;
firePropertyChange(ARROW_PROPERTY, oldValue, new Integer(arrow));
repaint();
}
/**
* Return the current arrow head width - after scaling
*/
public int getCurrentArrowHeadWidth() {
return this.nCurrentArrowWidth;
}
/**
* Return the current line thickness for this line.
* @return int, the current line thickness.
*/
public int getLineThickness() {
return nThickness;
}
/**
* Set the line thickness for this line. Fires a property change event.
* @param thickness, the thickness for this line.
*/
public void setLineThickness(int thickness) {
if (nThickness == thickness)
return;
int oldValue = nThickness;
nThickness = thickness;
nArrowWidth = thickness;
firePropertyChange(THICKNESS_PROPERTY, oldValue, nThickness);
repaint();
}
/**
* Return the current line thickness - after scaling
*/
public int getCurrentLineThickness() {
return this.nCurrentThickness;
}
/**
* return the coordinate type for this line (ABSOLUTE / RELATIVE).
* @return int, the coordinate type for this line.
*/
public int getCoordinateType() {
return nCoordinateType;
}
/**
* Set the Coordinate type for this line. Fires a property change event.
* @param type, the coordinate type for this line (ABSOLUTE / RELATIVE).
*/
public void setCoordinateType(int type) {
if (nCoordinateType == type)
return;
int oldValue = nCoordinateType;
nCoordinateType = type;
firePropertyChange("coordinatetype", oldValue, nCoordinateType); //$NON-NLS-1$
repaint();
}
/**
* Return the selection status for this line.
* @return boolean, the selection status for this line.
*/
public boolean isSelected() {
return bSelected;
}
/**
* Set the selection status for this line. Fires a property change event.
* @param selected, is the line currently selected or not?
*/
public void setSelected(boolean selected) {
if (bSelected == selected)
return;
boolean oldValue = bSelected;
bSelected = selected;
firePropertyChange("selected", oldValue, bSelected); //$NON-NLS-1$
repaint();
}
/**
* Return the rollover status for this line.
* @return boolean, the rollover status for this line.
*/
public boolean isRollover() {
return bRollover;
}
/**
* Set the rollover status for this line. Fires a property change event.
* @param rollover, the rollover status for this line.
*/
public void setRollover(boolean rollover) {
if (bRollover == rollover)
return;
boolean oldValue = bRollover;
bRollover = rollover;
firePropertyChange("rollover", oldValue, bRollover); //$NON-NLS-1$
repaint();
}
/**
* Return the current selection color for this line.
* @return Color, the current selection color for this line.
*/
public Color getSelectedColor() {
return oSelectedColor;
}
/**
* Set the selection colour for this line. Fires a property change event.
* @param c, the new selection color for this line.
*/
public void setSelectedColor(Color c) {
Color oldValue = oSelectedColor;
oSelectedColor = c;
firePropertyChange("selectedcolor", oldValue, oSelectedColor); //$NON-NLS-1$
repaint();
}
/**
* Return the current minimum width for this line.
* @return the minimum width for this line.
*/
public int getMinWidth() {
return nMinWidth;
}
/**
* Set the minimum width for this line. Fires a property change event.
* @param width the new width for this line.
*/
public void setMinWidth(int width) {
if (nMinWidth == width)
return;
int oldValue = nMinWidth;
nMinWidth = width;
firePropertyChange("minwidth", oldValue, nMinWidth); //$NON-NLS-1$
repaint();
}
/**
* Override to always return false.
* @return boolean false.
*/
public boolean isOpaque() {
return false;
}
/**
* Returns the intersecting point of this line with the
* given line. If the two lines are parallel null will be
* returned.
*
* @param line the line to check.
* @return Point the intersecting point of this line with the given line, else null if thye do not intercept.
*/
public Point intersectionWithLine(UILine line) {
// calculate the slopes
double dSlope1 = slope();
double dSlope2 = line.slope();
// check if lines are parallel;
if (dSlope1 == dSlope2)
return null;
// calculate the y interceptions
double dYIntercept1 = yIntercept();
double dYIntercept2 = line.yIntercept();
// calculate the intersecting point
double dX = (dYIntercept2 - dYIntercept1)/(dSlope1 - dSlope2);
double dY = dSlope1 * ptFrom.x + dYIntercept1;
return new Point(new Double(dX).intValue(), new Double(dY).intValue());
}
/**
* Returns the intersecting points of this line with the
* given rectangle. If the line does not intersect with the
* rectangle an empty array will be returned.
*
* @param r, the Rectangle to return the interception points for.
* @return Point[], the the intersecting points of this line with the given rectangle.
*/
public Point[] intersectionWithRectangle(Rectangle r) {
Point[] pts = new Point[2];
Point p1 = computeIntersectionWithRectangle(r, ptFrom, ptTo);
Point p2 = computeIntersectionWithRectangle(r, ptTo, ptFrom);
if (p1 != null && p2 != null) {
pts[0] = p1;
// if line intersect in only one point, don't store second point
if ((p1.x != p2.x) || (p1.y != p2.y))
pts[1] = p2;
}
return pts;
}
/**
* Calculates the slope (m) of the line.
*/
public double slope() {
return (ptTo.y - ptFrom.y)/(ptTo.x - ptFrom.x);
}
/**
* Calculates the y-intercept
*/
public double yIntercept() {
return ptFrom.y - slope() * ptFrom.x;
}
/**
* Checks whether the given point (pt) is on or close to the
* straight line formed by ptFrom and ptTo. The parameter 'd'
* defines the tolerance (max. allowable distance from the point
* to the line.
*
* @param pt, the point to check.
* @param d, the tolerance for the check.
* @return boolean true if the given point (pt) is on or close to the
* line formed by ptFrom and ptTo, else false.
*/
public boolean onLine(Point pt, int d) {
// To allow for new line thickness add half the thickness to the tolerance
d += nCurrentThickness/2;
int x1, y1, x2, y2, xp, yp;
int xmin, ymin, xmax, ymax;
double x, y, slope, dd, D2, dx, dy;
x1 = ptFrom.x;
y1 = ptFrom.y;
x2 = ptTo.x;
y2 = ptTo.y;
xp = pt.x;
yp = pt.y;
// if point close to start point
if (Math.abs(xp - x1) <= d && Math.abs(yp - y1) <= d) {
return true;
}
// if point close to end point
if (Math.abs(xp - x2) <= d && Math.abs(yp - y2) <= d) {
return true;
}
if (x1 < x2) {
xmin = x1 - d;
xmax = x2 + d;
}
else {
xmin = x2 - d;
xmax = x1 + d;
}
// check if point is outside of the line x boundaries
if (xp < xmin || xmax < xp)
return false;
if (y1 < y2) {
ymin = y1 - d;
ymax = y2 + d;
}
else {
ymin = y2 - d;
ymax = y1 + d;
}
// check if point is outside of the line y boundaries
if (yp < ymin || ymax < yp)
return false;
// check if we are dealing with horizontal, vertical or angled line
// and determine the desired x and y coordinate for the point on the line.
if (x2 == x1) {
x = x1;
y = yp;
}
else if (y1 == y2) {
x = xp;
y = y1;
}
else {
slope = ((double)(x2 - x1)) / ((double)(y2-y1));
y = (slope * (xp - x1 + slope * y1) + yp) / (1 + slope * slope);
x = ((double) x1) + slope * (y - y1);
}
// determine if the point is on or close enough to the line.
dx = ((double)xp) - x;
dy = ((double)yp) - y;
D2 = dx * dx + dy * dy;
dd = d * d;
if (D2 <= dd)
return true;
return false;
}
/**
* Return the preferred bounds for this object.
* @return Rectangle, the preferred bounds for this object.
*/
public Rectangle getPreferredBounds() {
if (getUI() != null)
return getUI().getPreferredBounds(this);
else
return getBounds();
}
}