/**
* $Id: mxGraphics2DCanvas.java,v 1.183 2010-07-18 19:47:20 david Exp $
* Copyright (c) 2007-2010, Gaudenz Alder, David Benson
*/
package com.mxgraph.canvas;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import javax.swing.CellRendererPane;
import com.mxgraph.shape.mxActorShape;
import com.mxgraph.shape.mxArrowShape;
import com.mxgraph.shape.mxCloudShape;
import com.mxgraph.shape.mxConnectorShape;
import com.mxgraph.shape.mxCurveShape;
import com.mxgraph.shape.mxCylinderShape;
import com.mxgraph.shape.mxDefaultTextShape;
import com.mxgraph.shape.mxDoubleEllipseShape;
import com.mxgraph.shape.mxEllipseShape;
import com.mxgraph.shape.mxHexagonShape;
import com.mxgraph.shape.mxHtmlTextShape;
import com.mxgraph.shape.mxIShape;
import com.mxgraph.shape.mxITextShape;
import com.mxgraph.shape.mxImageShape;
import com.mxgraph.shape.mxLabelShape;
import com.mxgraph.shape.mxLineShape;
import com.mxgraph.shape.mxRectangleShape;
import com.mxgraph.shape.mxRhombusShape;
import com.mxgraph.shape.mxSwimlaneShape;
import com.mxgraph.shape.mxTriangleShape;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
/**
* An implementation of a canvas that uses Graphics2D for painting.
*/
public class mxGraphics2DCanvas extends mxBasicCanvas
{
/**
*
*/
public static final String TEXT_SHAPE_DEFAULT = "default";
/**
*
*/
public static final String TEXT_SHAPE_HTML = "html";
/**
* Maps from names to mxIVertexShape instances.
*/
protected static Map<String, mxIShape> shapes = new HashMap<String, mxIShape>();
/**
* Maps from names to mxITextShape instances. There are currently three different
* hardcoded text shapes available here: default, html and wrapped.
*/
protected static Map<String, mxITextShape> textShapes = new HashMap<String, mxITextShape>();
/**
* Static initializer.
*/
static
{
putShape(mxConstants.SHAPE_ACTOR, new mxActorShape());
putShape(mxConstants.SHAPE_ARROW, new mxArrowShape());
putShape(mxConstants.SHAPE_CLOUD, new mxCloudShape());
putShape(mxConstants.SHAPE_CONNECTOR, new mxConnectorShape());
putShape(mxConstants.SHAPE_CYLINDER, new mxCylinderShape());
putShape(mxConstants.SHAPE_CURVE, new mxCurveShape());
putShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, new mxDoubleEllipseShape());
putShape(mxConstants.SHAPE_ELLIPSE, new mxEllipseShape());
putShape(mxConstants.SHAPE_HEXAGON, new mxHexagonShape());
putShape(mxConstants.SHAPE_IMAGE, new mxImageShape());
putShape(mxConstants.SHAPE_LABEL, new mxLabelShape());
putShape(mxConstants.SHAPE_LINE, new mxLineShape());
putShape(mxConstants.SHAPE_RECTANGLE, new mxRectangleShape());
putShape(mxConstants.SHAPE_RHOMBUS, new mxRhombusShape());
putShape(mxConstants.SHAPE_SWIMLANE, new mxSwimlaneShape());
putShape(mxConstants.SHAPE_TRIANGLE, new mxTriangleShape());
putTextShape(TEXT_SHAPE_DEFAULT, new mxDefaultTextShape());
putTextShape(TEXT_SHAPE_HTML, new mxHtmlTextShape());
}
/**
* Cache for images.
*/
protected Hashtable<String, Image> imageCache = new Hashtable<String, Image>();
/**
* Specifies if linefeeds should be replaced with breaks in HTML markup.
* Default is true.
*/
protected boolean replaceHtmlLinefeeds = true;
/**
* Optional renderer pane to be used for HTML label rendering.
*/
protected CellRendererPane rendererPane;
/**
* Global graphics handle to the image.
*/
protected Graphics2D g;
/**
* Constructs a new graphics canvas with an empty graphics object.
*/
public mxGraphics2DCanvas()
{
this(null);
}
/**
* Constructs a new graphics canvas for the given graphics object.
*/
public mxGraphics2DCanvas(Graphics2D g)
{
this.g = g;
// Initializes the cell renderer pane for drawing HTML markup
try
{
rendererPane = new CellRendererPane();
}
catch (Exception e)
{
// ignore
}
}
/**
*
*/
public static void putShape(String name, mxIShape shape)
{
shapes.put(name, shape);
}
/**
*
*/
public mxIShape getShape(Map<String, Object> style)
{
String name = mxUtils.getString(style, mxConstants.STYLE_SHAPE, null);
return shapes.get(name);
}
/**
*
*/
public static void putTextShape(String name, mxITextShape shape)
{
textShapes.put(name, shape);
}
/**
*
*/
public mxITextShape getTextShape(Map<String, Object> style, boolean html)
{
String name;
if (html)
{
name = TEXT_SHAPE_HTML;
}
else
{
name = TEXT_SHAPE_DEFAULT;
}
return textShapes.get(name);
}
/**
*
*/
public CellRendererPane getRendererPane()
{
return rendererPane;
}
/**
* Returns replaceHtmlLinefeeds
*/
public boolean isReplaceHtmlLinefeeds()
{
return replaceHtmlLinefeeds;
}
/**
* Returns replaceHtmlLinefeeds
*/
public void setReplaceHtmlLinefeeds(boolean value)
{
replaceHtmlLinefeeds = value;
}
/**
* Returns the graphics object for this canvas.
*/
public Graphics2D getGraphics()
{
return g;
}
/**
* Sets the graphics object for this canvas.
*/
public void setGraphics(Graphics2D g)
{
this.g = g;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawCell()
*/
public Object drawCell(mxCellState state)
{
Map<String, Object> style = state.getStyle();
mxIShape shape = getShape(style);
if (g != null && shape != null)
{
// Creates a temporary graphics instance for drawing this shape
float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY,
100);
Graphics2D previousGraphics = g;
g = createTemporaryGraphics(style, opacity, state);
// Paints the shape and restores the graphics object
shape.paintShape(this, state);
g.dispose();
g = previousGraphics;
}
return shape;
}
/*
* (non-Javadoc)
* @see com.mxgraph.canvas.mxICanvas#drawLabel()
*/
public Object drawLabel(String text, mxCellState state, boolean html)
{
Map<String, Object> style = state.getStyle();
mxITextShape shape = getTextShape(style, html);
if (g != null && shape != null && drawLabels && text != null
&& text.length() > 0)
{
// Creates a temporary graphics instance for drawing this shape
float opacity = mxUtils.getFloat(style,
mxConstants.STYLE_TEXT_OPACITY, 100);
Graphics2D previousGraphics = g;
g = createTemporaryGraphics(style, opacity, null);
// Draws the label background and border
Color bg = mxUtils.getColor(style,
mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
Color border = mxUtils.getColor(style,
mxConstants.STYLE_LABEL_BORDERCOLOR);
paintRectangle(state.getLabelBounds().getRectangle(), bg, border);
// Paints the label and restores the graphics object
shape.paintShape(this, text, state, style);
g.dispose();
g = previousGraphics;
}
return shape;
}
/**
* Returns an image instance for the given URL. If the URL has
* been loaded before than an instance of the same instance is
* returned as in the previous call.
*/
public Image loadImage(String image)
{
Image img = imageCache.get(image);
if (img == null)
{
img = mxUtils.loadImage(image);
if (img != null)
{
imageCache.put(image, img);
}
}
return img;
}
/**
*
*/
public void flushImageCache()
{
imageCache.clear();
}
/**
*
*/
public void drawImage(Rectangle bounds, String imageUrl)
{
if (imageUrl != null)
{
Image img = loadImage(imageUrl);
if (img != null)
{
g.drawImage(img, bounds.x, bounds.y, bounds.width,
bounds.height, null);
}
}
}
/**
*
*/
public void paintPolyline(mxPoint[] points, boolean rounded)
{
if (points != null && points.length > 1)
{
mxPoint pt = points[0];
mxPoint pe = points[points.length - 1];
double arcSize = mxConstants.LINE_ARCSIZE * scale;
GeneralPath path = new GeneralPath();
path.moveTo((float) pt.getX(), (float) pt.getY());
// Draws the line segments
for (int i = 1; i < points.length - 1; i++)
{
mxPoint tmp = points[i];
double dx = pt.getX() - tmp.getX();
double dy = pt.getY() - tmp.getY();
if ((rounded && i < points.length - 1) && (dx != 0 || dy != 0))
{
// Draws a line from the last point to the current
// point with a spacing of size off the current point
// into direction of the last point
double dist = Math.sqrt(dx * dx + dy * dy);
double nx1 = dx * Math.min(arcSize, dist / 2) / dist;
double ny1 = dy * Math.min(arcSize, dist / 2) / dist;
double x1 = tmp.getX() + nx1;
double y1 = tmp.getY() + ny1;
path.lineTo((float) x1, (float) y1);
// Draws a curve from the last point to the current
// point with a spacing of size off the current point
// into direction of the next point
mxPoint next = points[i + 1];
dx = next.getX() - tmp.getX();
dy = next.getY() - tmp.getY();
dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
double nx2 = dx * Math.min(arcSize, dist / 2) / dist;
double ny2 = dy * Math.min(arcSize, dist / 2) / dist;
double x2 = tmp.getX() + nx2;
double y2 = tmp.getY() + ny2;
path.quadTo((float) tmp.getX(), (float) tmp.getY(),
(float) x2, (float) y2);
tmp = new mxPoint(x2, y2);
}
else
{
path.lineTo((float) tmp.getX(), (float) tmp.getY());
}
pt = tmp;
}
path.lineTo((float) pe.getX(), (float) pe.getY());
g.draw(path);
}
}
/**
*
*/
public void paintRectangle(Rectangle bounds, Color background, Color border)
{
if (background != null)
{
g.setColor(background);
fillShape(bounds);
}
if (border != null)
{
g.setColor(border);
g.draw(bounds);
}
}
/**
*
*/
public void fillShape(Shape shape)
{
fillShape(shape, false);
}
/**
*
*/
public void fillShape(Shape shape, boolean shadow)
{
int shadowOffsetX = (shadow) ? mxConstants.SHADOW_OFFSETX : 0;
int shadowOffsetY = (shadow) ? mxConstants.SHADOW_OFFSETY : 0;
if (shadow)
{
// Saves the state and configures the graphics object
Paint p = g.getPaint();
Color previousColor = g.getColor();
g.setColor(mxConstants.SHADOW_COLOR);
g.translate(shadowOffsetX, shadowOffsetY);
// Paints the shadow
fillShape(shape, false);
// Restores the state of the graphics object
g.translate(-shadowOffsetX, -shadowOffsetY);
g.setColor(previousColor);
g.setPaint(p);
}
g.fill(shape);
}
/**
*
*/
public Stroke createStroke(Map<String, Object> style)
{
double width = mxUtils
.getFloat(style, mxConstants.STYLE_STROKEWIDTH, 1)
* scale;
boolean dashed = mxUtils.isTrue(style, mxConstants.STYLE_DASHED);
if (dashed)
{
float[] dashPattern = mxUtils.getFloatArray(style,
mxConstants.STYLE_DASH_PATTERN,
mxConstants.DEFAULT_DASHED_PATTERN);
float[] scaledDashPattern = new float[dashPattern.length];
for (int i = 0; i < dashPattern.length; i++)
{
scaledDashPattern[i] = (float) (dashPattern[i] * scale);
}
return new BasicStroke((float) width, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10.0f, scaledDashPattern, 0.0f);
}
else
{
return new BasicStroke((float) width);
}
}
/**
*
*/
public Paint createFillPaint(mxRectangle bounds, Map<String, Object> style)
{
Color fillColor = mxUtils.getColor(style, mxConstants.STYLE_FILLCOLOR);
Paint fillPaint = null;
if (fillColor != null)
{
Color gradientColor = mxUtils.getColor(style,
mxConstants.STYLE_GRADIENTCOLOR);
if (gradientColor != null)
{
String gradientDirection = mxUtils.getString(style,
mxConstants.STYLE_GRADIENT_DIRECTION);
float x1 = (float) bounds.getX();
float y1 = (float) bounds.getY();
float x2 = (float) bounds.getX();
float y2 = (float) bounds.getY();
if (gradientDirection == null
|| gradientDirection
.equals(mxConstants.DIRECTION_SOUTH))
{
y2 = (float) (bounds.getY() + bounds.getHeight());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_EAST))
{
x2 = (float) (bounds.getX() + bounds.getWidth());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_NORTH))
{
y1 = (float) (bounds.getY() + bounds.getHeight());
}
else if (gradientDirection.equals(mxConstants.DIRECTION_WEST))
{
x1 = (float) (bounds.getX() + bounds.getWidth());
}
fillPaint = new GradientPaint(x1, y1, fillColor, x2, y2,
gradientColor, true);
}
}
return fillPaint;
}
/**
*
*/
public Graphics2D createTemporaryGraphics(Map<String, Object> style,
float opacity, mxRectangle bounds)
{
Graphics2D temporaryGraphics = (Graphics2D) g.create();
// Applies the default translate
temporaryGraphics.translate(translate.x, translate.y);
// Applies the rotation on the graphics object
if (bounds != null)
{
double rotation = mxUtils.getDouble(style,
mxConstants.STYLE_ROTATION, 0);
if (rotation != 0)
{
temporaryGraphics.rotate(Math.toRadians(rotation), bounds
.getCenterX(), bounds.getCenterY());
}
}
// Applies the opacity to the graphics object
if (opacity != 100)
{
temporaryGraphics.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, opacity / 100));
}
return temporaryGraphics;
}
}