/*
* Freeplane - mind map editor
* Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
*
* This file is modified by Dimitry Polivaev in 2008.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.view.swing.map.cloud;
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.Stroke;
import java.util.LinkedList;
import java.util.Random;
import java.util.Vector;
import org.freeplane.features.cloud.CloudController;
import org.freeplane.features.cloud.CloudModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.view.swing.map.MapView;
import org.freeplane.view.swing.map.NodeView;
/**
* This class represents a Cloud around a node.
*/
abstract public class CloudView {
static final Stroke DEF_STROKE = new BasicStroke(1);
/** the layout functions can get the additional height of the clouded node .
* @param cloud */
static public int getAdditionalHeigth(CloudModel cloud, final NodeView source) {
final CloudView heightCalculator = new CloudViewFactory().createCloudView(cloud, source);
return (int) (2.2 * heightCalculator.getDistanceToConvexHull());
}
protected CloudModel cloudModel;
protected NodeView source;
private final int iterativeLevel;
private Random random;
CloudView(final CloudModel cloudModel, final NodeView source) {
this.cloudModel = cloudModel;
this.source = source;
iterativeLevel = getCloudIterativeLevel();
}
private int getCloudIterativeLevel() {
int iterativeLevel = 0;
for (NodeView parentNode = source.getParentView(); parentNode != null; parentNode = parentNode.getParentView()) {
if (null != parentNode.getCloudModel()) {
iterativeLevel++;
}
}
return iterativeLevel;
}
public Color getColor() {
return source.getCloudColor();
}
protected double getDistanceToConvexHull() {
return 20 / (getIterativeLevel() + 1) * getZoom();
}
public Color getExteriorColor(final Color color) {
return color.darker();
}
/**
* getIterativeLevel() describes the n-th nested cloud that is to be
* painted.
*/
protected int getIterativeLevel() {
return iterativeLevel;
}
protected MapView getMap() {
return source.getMap();
}
protected CloudModel getModel() {
return cloudModel;
}
/**
* Get the width in pixels rather than in width constant (like -1)
*/
public int getRealWidth() {
final int width = getWidth();
return (width < 1) ? 1 : width;
}
public Stroke getStroke() {
final int width = getWidth();
if (width < 1) {
return CloudView.DEF_STROKE;
}
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
}
public int getWidth() {
final NodeModel node = source.getModel();
return CloudController.getController(source.getMap().getModeController()).getWidth(node);
}
protected double getZoom() {
return getMap().getZoom();
}
public void paint(final Graphics graphics) {
random = new Random(0);
final Graphics2D g = (Graphics2D) graphics.create();
final Graphics2D gstroke = (Graphics2D) g.create();
final Color color = getColor();
g.setColor(color);
/* set a bigger stroke to prevent not filled areas. */
g.setStroke(getStroke());
/* now bold */
gstroke.setColor(getExteriorColor(color));
gstroke.setStroke(getStroke());
/*
* calculate the distances between two points on the convex hull
* depending on the getIterativeLevel().
*/
/** get coordinates */
final LinkedList<Point> coordinates = new LinkedList<Point>();
source.getCoordinates(coordinates);
final ConvexHull hull = new ConvexHull();
final Vector<Point> res = hull.calculateHull(coordinates);
final Polygon p = new Polygon();
Point lastPt = null;
for (int i = 0; i < res.size(); ++i) {
final Point pt = (Point) res.get(i);
if(!pt.equals(lastPt)){
p.addPoint(pt.x, pt.y);
lastPt = pt;
}
}
final Point pt = (Point) res.get(0);
p.addPoint(pt.x, pt.y);
paintDecoration(p, g, gstroke);
g.dispose();
}
protected void paintDecoration(final Polygon p, Graphics2D g, Graphics2D gstroke){
fillPolygon(p, g);
double middleDistanceBetweenPoints = calcDistanceBetweenPoints();
final int[] xpoints = p.xpoints;
final int[] ypoints = p.ypoints;
final Point lastPoint = new Point(xpoints[0], ypoints[0]);
double x0, y0;
x0 = lastPoint.x;
y0 = lastPoint.y;
/* close the path: */
double x2, y2; /* the drawing start points. */
x2 = x0;
y2 = y0;
for (int i = p.npoints - 2; i >= 0; --i) {
final Point nextPoint = new Point(xpoints[i], ypoints[i]);
double x1, y1, x3, y3, dx, dy, dxn, dyn;
x1 = nextPoint.x;
y1 = nextPoint.y;
dx = x1 - x0; /* direction of p0 -> p1 */
dy = y1 - y0;
final double length = Math.sqrt(dx * dx + dy * dy);
dxn = dx / length; /* normalized direction of p0 -> p1 */
dyn = dy / length;
for (int j = 0;;) {
double distanceBetweenPoints = middleDistanceBetweenPoints * random(0.7);
if (j + 2* distanceBetweenPoints < length) {
j += distanceBetweenPoints;
x3 = x0 + j * dxn;
/* the drawing end point.*/
y3 = y0 + j * dyn;
}
else {
/* last point */
break;
}
paintDecoration(g, gstroke, x2, y2, x3, y3);
x2 = x3;
y2 = y3;
}
paintDecoration(g, gstroke, x2, y2, x1, y1);
x2 = x1;
y2 = y1;
x0 = x1;
y0 = y1;
}
}
protected void fillPolygon(final Polygon p, Graphics2D g) {
g.fillPolygon(p);
g.drawPolygon(p);
}
protected void paintDecoration(Graphics2D g, Graphics2D gstroke, double x0, double y0, double x1, double y1) {
double dx, dy;
dx = x1 - x0;
dy = y1 - y0;
final double length = Math.sqrt(dx * dx + dy * dy);
double dxn, dyn;
dxn = dx / length;
dyn = dy / length;
paintDecoration(g, gstroke, x0, y0, x1, y1, dx, dy, dxn, dyn);
}
abstract protected void paintDecoration(Graphics2D g, Graphics2D gstroke, double x0, double y0, double x1, double y1,
double dx, double dy, double dxn, double dyn);
protected double calcDistanceBetweenPoints() {
final double distanceBetweenPoints = getDistanceToConvexHull();
return distanceBetweenPoints;
}
protected double random(double min) {
return (min + (1-min) * random.nextDouble());
}
}