/*FreeMind - A Program for creating and viewing Mindmaps
*Copyright (C) 2000-2001 Joerg Mueller <joergmueller@bigfoot.com>
*See COPYING for Details
*
*This program is free software; you can redistribute it and/or
*modify it under the terms of the GNU General Public License
*as published by the Free Software Foundation; either version 2
*of the License, or (at your option) any later version.
*
*This program is distributed in the hope that it will be useful,
*but WITHOUT ANY WARRANTY; without even the implied warranty of
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
*GNU General Public License for more details.
*
*You should have received a copy of the GNU General Public License
*along with this program; if not, write to the Free Software
*Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*$Id: ArrowLinkView.java,v 1.8.14.4.4.6 2008/06/08 14:00:32 dpolivaev Exp $*/
package freemind.view.mindmapview;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import freemind.modes.MindMapArrowLink;
// end Convex Hull
/**
* This class represents a ArrowLink around a node.
*/
public class ArrowLinkView {
protected MindMapArrowLink arrowLinkModel;
protected NodeView source, target;
protected int iterativeLevel;
protected CubicCurve2D arrowLinkCurve;
static final Stroke DEF_STROKE = new BasicStroke(1);
/* Note, that source and target are nodeviews and not nodemodels!. */
protected ArrowLinkView(MindMapArrowLink arrowLinkModel, NodeView source,
NodeView target) {
this.arrowLinkModel = arrowLinkModel;
this.source = source;
this.target = target;
}
public Rectangle getBounds() {
if (arrowLinkCurve == null)
return new Rectangle();
return arrowLinkCurve.getBounds();
}
/**
* \param iterativeLevel describes the n-th nested arrowLink that is to be
* painted.
*/
public void paint(Graphics graphics) {
if (!isSourceVisible() && !isTargetVisible())
return;
Point p1 = null, p2 = null, p3 = null, p4 = null;
boolean targetIsLeft = false;
boolean sourceIsLeft = false;
Graphics2D g = (Graphics2D) graphics.create();
g.setColor(getColor());
/* set stroke. */
g.setStroke(getStroke());
// if one of the nodes is not present then draw a dashed line:
if (!isSourceVisible() || !isTargetVisible())
g.setStroke(new BasicStroke(getWidth(), BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 0, new float[] { 0, 3, 0, 3 }, 0));
// determine, whether destination exists:
if (isSourceVisible()) {
p1 = source.getLinkPoint(arrowLinkModel.getStartInclination());
sourceIsLeft = source.isLeft();
}
if (isTargetVisible()) {
p2 = target.getLinkPoint(arrowLinkModel.getEndInclination());
targetIsLeft = target.isLeft();
}
// determine point 2 and 3:
if (arrowLinkModel.getEndInclination() == null
|| arrowLinkModel.getStartInclination() == null) {
double dellength = isSourceVisible() && isTargetVisible() ? p1
.distance(p2) / getZoom() : 30;
if (isSourceVisible()
&& arrowLinkModel.getStartInclination() == null) {
Point incl = calcInclination(source, dellength);
arrowLinkModel.setStartInclination(incl);
p1 = source.getLinkPoint(arrowLinkModel.getStartInclination());
}
if (isTargetVisible() && arrowLinkModel.getEndInclination() == null) {
Point incl = calcInclination(target, dellength);
incl.y = -incl.y;
arrowLinkModel.setEndInclination(incl);
p2 = target.getLinkPoint(arrowLinkModel.getEndInclination());
}
}
arrowLinkCurve = new CubicCurve2D.Double();
if (p1 != null) {
p3 = new Point(p1);
p3.translate(
((sourceIsLeft) ? -1 : 1)
* getMap().getZoomed(
arrowLinkModel.getStartInclination().x),
getMap().getZoomed(arrowLinkModel.getStartInclination().y));
if (p2 == null) {
arrowLinkCurve.setCurve(p1, p3, p1, p3);
}
}
if (p2 != null) {
p4 = new Point(p2);
p4.translate(
((targetIsLeft) ? -1 : 1)
* getMap().getZoomed(
arrowLinkModel.getEndInclination().x),
getMap().getZoomed(arrowLinkModel.getEndInclination().y));
if (p1 == null) {
arrowLinkCurve.setCurve(p2, p4, p2, p4);
}
}
if (p1 != null && p2 != null) {
arrowLinkCurve.setCurve(p1, p3, p4, p2);
g.draw(arrowLinkCurve);
// arrow source:
}
if (isSourceVisible() && !arrowLinkModel.getStartArrow().equals("None")) {
paintArrow(p1, p3, g);
}
// arrow target:
if (isTargetVisible() && !arrowLinkModel.getEndArrow().equals("None")) {
paintArrow(p2, p4, g);
}
// Control Points
if (arrowLinkModel.getShowControlPointsFlag() || !isSourceVisible()
|| !isTargetVisible()) {
g.setStroke(new BasicStroke(getWidth(), BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 0, new float[] { 0, 3, 0, 3 }, 0));
if (p1 != null) {
g.drawLine(p1.x, p1.y, p3.x, p3.y);
}
if (p2 != null) {
g.drawLine(p2.x, p2.y, p4.x, p4.y);
}
}
}
/**
*/
private boolean isTargetVisible() {
return (target != null && target.isContentVisible());
}
/**
*/
private boolean isSourceVisible() {
return (source != null && source.isContentVisible());
}
/**
*/
private Point calcInclination(NodeView node, double dellength) {
/*
* int w = node.getWidth(); int h = node.getHeight(); double r =
* Math.sqrt(w*w+h*h); double wr = dellength * w / r; double hr =
* dellength * h / r; return new Point((int)wr, (int)hr);
*/
return new Point((int) dellength, 0);
}
/**
* @param p1
* is the start point
* @param p3
* is the another point indicating the direction of the arrow.
*/
private void paintArrow(Point p1, Point p3, Graphics2D g) {
double dx, dy, dxn, dyn;
dx = p3.x - p1.x; /* direction of p1 -> p3 */
dy = p3.y - p1.y;
double length = Math.sqrt(dx * dx + dy * dy) / (getZoom() * 10/*
* =zoom
* factor
* for
* arrows
*/);
dxn = dx / length; /* normalized direction of p1 -> p3 */
dyn = dy / length;
// suggestion of daniel to have arrows that are not so wide open. fc,
// 7.12.2003.
double width = .5f;
Polygon p = new Polygon();
p.addPoint((int) (p1.x), (int) (p1.y));
p.addPoint((int) (p1.x + dxn + width * dyn), (int) (p1.y + dyn - width
* dxn));
p.addPoint((int) (p1.x + dxn - width * dyn), (int) (p1.y + dyn + width
* dxn));
p.addPoint((int) (p1.x), (int) (p1.y));
g.fillPolygon(p);
}
/** MAXIMAL_RECTANGLE_SIZE_FOR_COLLISION_DETECTION describes itself. */
private final int MAXIMAL_RECTANGLE_SIZE_FOR_COLLISION_DETECTION = 16;
/**
* Determines, whether or not a given point p is in an epsilon-neighbourhood
* for the cubic curve.
*/
public boolean detectCollision(Point p) {
if (arrowLinkCurve == null)
return false;
Rectangle2D rec = getControlPoint(p);
// flatten the curve and test for intersection (bug fix, fc, 16.1.2004).
FlatteningPathIterator pi = new FlatteningPathIterator(
arrowLinkCurve.getPathIterator(null),
MAXIMAL_RECTANGLE_SIZE_FOR_COLLISION_DETECTION / 4, 10/*
* =maximal
* 2^10=1024
* points.
*/);
double oldCoordinateX = 0, oldCoordinateY = 0;
while (pi.isDone() == false) {
double[] coordinates = new double[6];
int type = pi.currentSegment(coordinates);
switch (type) {
case PathIterator.SEG_LINETO:
if (rec.intersectsLine(oldCoordinateX, oldCoordinateY,
coordinates[0], coordinates[1]))
return true;
/*
* this case needs the same action as the next case, thus no
* "break"
*/
case PathIterator.SEG_MOVETO:
oldCoordinateX = coordinates[0];
oldCoordinateY = coordinates[1];
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
case PathIterator.SEG_CLOSE:
default:
break;
}
pi.next();
}
return false;
}
protected Rectangle2D getControlPoint(Point2D p) {
// Create a small square around the given point.
int side = MAXIMAL_RECTANGLE_SIZE_FOR_COLLISION_DETECTION;
return new Rectangle2D.Double(p.getX() - side / 2, p.getY() - side / 2,
side, side);
}
public Color getColor() {
return getModel().getColor(); /* new Color(240,240,240) *//* selectedColor */
}
public Stroke getStroke() {
int width = getWidth();
if (width < 1) {
return DEF_STROKE;
}
return new BasicStroke(width, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
}
public int getWidth() {
return getModel().getWidth();
}
/**
* Get the width in pixels rather than in width constant (like -1)
*/
public int getRealWidth() {
int width = getWidth();
return (width < 1) ? 1 : width;
}
protected MapView getMap() {
return (source == null) ? target.getMap() : source.getMap();
}
/**
* fc: This getter is public, because the view gets the model by click on
* the curve.
*/
public MindMapArrowLink getModel() {
return arrowLinkModel;
}
protected double getZoom() {
return getMap().getZoom();
}
/**
*/
public void changeInclination(int originX, int originY, int newX, int newY) {
// TODO Auto-generated method stub
}
}