0001: /*
0002: * @(#)EdgeRenderer.java 1.0 03-JUL-04
0003: *
0004: * Copyright (c) 2001-2005 Gaudenz Alder
0005: *
0006: * See LICENSE file in distribution for licensing details of this source file
0007: */
0008: package org.jgraph.graph;
0009:
0010: import java.awt.BasicStroke;
0011: import java.awt.Color;
0012: import java.awt.Component;
0013: import java.awt.Dimension;
0014: import java.awt.Font;
0015: import java.awt.FontMetrics;
0016: import java.awt.GradientPaint;
0017: import java.awt.Graphics;
0018: import java.awt.Graphics2D;
0019: import java.awt.Polygon;
0020: import java.awt.Rectangle;
0021: import java.awt.RenderingHints;
0022: import java.awt.Shape;
0023: import java.awt.geom.Ellipse2D;
0024: import java.awt.geom.GeneralPath;
0025: import java.awt.geom.Point2D;
0026: import java.awt.geom.Rectangle2D;
0027: import java.awt.image.BufferedImage;
0028: import java.io.Serializable;
0029: import java.lang.ref.WeakReference;
0030: import java.util.Map;
0031:
0032: import javax.swing.JComponent;
0033: import javax.swing.UIManager;
0034:
0035: import org.jgraph.JGraph;
0036: import org.jgraph.util.Bezier;
0037: import org.jgraph.util.Spline2D;
0038:
0039: /**
0040: * This renderer displays entries that implement the CellView interface.
0041: *
0042: * @version 1.0 1/1/02
0043: * @author Gaudenz Alder
0044: */
0045:
0046: public class EdgeRenderer extends JComponent implements
0047: CellViewRenderer, Serializable {
0048:
0049: /** Static Graphics used for Font Metrics */
0050: protected static transient Graphics fontGraphics;
0051:
0052: // Headless environment does not allow graphics
0053: static {
0054: try {
0055: fontGraphics = new BufferedImage(1, 1,
0056: BufferedImage.TYPE_INT_RGB).getGraphics();
0057: } catch (Error e) {
0058: // No font graphics
0059: fontGraphics = null;
0060: }
0061: }
0062:
0063: /** A switch for painting the extra labels */
0064: public boolean simpleExtraLabels = true;
0065:
0066: /** Override this if you want the extra labels to appear in a special fontJ */
0067: public Font extraLabelFont = null;
0068:
0069: /** Reference to the font metrics of the above */
0070: protected transient FontMetrics metrics;
0071:
0072: /** Cache the current graph for drawing */
0073: protected transient WeakReference graph;
0074:
0075: /** Cache the current edgeview for drawing */
0076: protected transient EdgeView view;
0077:
0078: /** Painting attributes of the current edgeview */
0079: protected transient int beginDeco, endDeco, beginSize, endSize,
0080: lineStyle;
0081:
0082: /** Width of the current edge view */
0083: protected transient float lineWidth;
0084:
0085: /**
0086: * Boolean attributes of the current edgeview. Fill flags are checked for
0087: * valid decorations.
0088: */
0089: protected transient boolean labelBorder, beginFill, endFill, focus,
0090: selected, preview, opaque, childrenSelected,
0091: labelTransformEnabled, isMoveBelowZero;
0092:
0093: /**
0094: * Color attributes of the current edgeview. This components foreground is
0095: * set to the edgecolor, the fontColor is in an extra variable. If the
0096: * fontColor is null, the current foreground is used. The default background
0097: * instead is used for text and is not visible if the label is not visible
0098: * or if opaque is true.
0099: */
0100: protected transient Color borderColor, defaultForeground,
0101: defaultBackground, fontColor;
0102:
0103: /** Contains the current dash pattern. Null means no pattern. */
0104: protected transient float[] lineDash;
0105:
0106: /** Contains the current dash offset. Null means no offset. */
0107: protected transient float dashOffset = 0.0f;
0108:
0109: /** The gradient color of the edge */
0110: protected transient Color gradientColor = null;
0111:
0112: /** The color of the graph grid */
0113: protected transient Color gridColor = null;
0114:
0115: /** The color of the second available handle */
0116: protected transient Color lockedHandleColor = null;
0117:
0118: /** The color of highlighted cells */
0119: protected transient Color highlightColor = null;
0120:
0121: /* THIS CODE WAS ADDED BY MARTIN KRUEGER 10/20/2003 */
0122:
0123: protected Bezier bezier;
0124:
0125: protected Spline2D spline;
0126:
0127: /* END */
0128:
0129: /**
0130: * Constructs a renderer that may be used to render edges.
0131: */
0132: public EdgeRenderer() {
0133: defaultForeground = UIManager.getColor("Tree.textForeground");
0134: defaultBackground = UIManager.getColor("Tree.textBackground");
0135: }
0136:
0137: /**
0138: * Sets view to work with, caching necessary values until the next call of
0139: * this method or until some other methods with explicitly specified
0140: * different view
0141: */
0142: void setView(CellView value) {
0143: if (value instanceof EdgeView) {
0144: view = (EdgeView) value;
0145: installAttributes(view);
0146: } else {
0147: view = null;
0148: }
0149: }
0150:
0151: /**
0152: * Configure and return the renderer based on the passed in components. The
0153: * value is typically set from messaging the graph with
0154: * <code>convertValueToString</code>.
0155: *
0156: * @param graph
0157: * the graph that that defines the rendering context.
0158: * @param view
0159: * the cell view that should be rendered.
0160: * @param sel
0161: * whether the object is selected.
0162: * @param focus
0163: * whether the object has the focus.
0164: * @param preview
0165: * whether we are drawing a preview.
0166: * @return the component used to render the value.
0167: */
0168: public Component getRendererComponent(JGraph graph, CellView view,
0169: boolean sel, boolean focus, boolean preview) {
0170: if (view instanceof EdgeView && graph != null) {
0171: this .gridColor = graph.getGridColor();
0172: this .lockedHandleColor = graph.getLockedHandleColor();
0173: this .highlightColor = graph.getHighlightColor();
0174: this .isMoveBelowZero = graph.isMoveBelowZero();
0175: this .graph = new WeakReference(graph);
0176: this .focus = focus;
0177: this .selected = sel;
0178: this .preview = preview;
0179: this .childrenSelected = graph.getSelectionModel()
0180: .isChildrenSelected(view.getCell());
0181: setView(view);
0182: return this ;
0183: }
0184: return null;
0185: }
0186:
0187: /**
0188: * Returns true if the edge shape intersects the given rectangle.
0189: */
0190: public boolean intersects(JGraph graph, CellView value,
0191: Rectangle rect) {
0192: if (value instanceof EdgeView && graph != null && value != null) {
0193: setView(value);
0194:
0195: // If we have two control points, we can get rid of hit
0196: // detection on do an intersection test on the two diagonals
0197: // of rect and the line between the two points
0198: EdgeView edgeView = (EdgeView) value;
0199: if (edgeView.getPointCount() == 2) {
0200: Point2D p0 = edgeView.getPoint(0);
0201: Point2D p1 = edgeView.getPoint(1);
0202: if (rect.intersectsLine(p0.getX(), p0.getY(),
0203: p1.getX(), p1.getY()))
0204: return true;
0205: } else {
0206: Graphics2D g2 = (Graphics2D) graph.getGraphics();
0207: if (g2.hit(rect, view.getShape(), true))
0208: return true;
0209: }
0210: Rectangle2D r = getLabelBounds(graph, view);
0211: if (r != null && r.intersects(rect))
0212: return true;
0213: Object[] labels = GraphConstants.getExtraLabels(view
0214: .getAllAttributes());
0215: if (labels != null) {
0216: for (int i = 0; i < labels.length; i++) {
0217: r = getExtraLabelBounds(graph, view, i);
0218: if (r != null && r.intersects(rect))
0219: return true;
0220: }
0221: }
0222: }
0223: return false;
0224: }
0225:
0226: /**
0227: * Returns the bounds of the edge shape.
0228: */
0229: public Rectangle2D getBounds(CellView value) {
0230: if (value instanceof EdgeView && value != null) {
0231: // No need to call setView as getPaintBounds will
0232: view = (EdgeView) value;
0233: Rectangle2D r = getPaintBounds(view);
0234: JGraph graph = null;
0235: if (this .graph != null) {
0236: graph = (JGraph) this .graph.get();
0237: }
0238: Rectangle2D rect = getLabelBounds(graph, view);
0239: if (rect != null)
0240: Rectangle2D.union(r, rect, r);
0241: Object[] labels = GraphConstants.getExtraLabels(view
0242: .getAllAttributes());
0243: if (labels != null) {
0244: for (int i = 0; i < labels.length; i++) {
0245: rect = getExtraLabelBounds(graph, view, i);
0246: if (rect != null)
0247: Rectangle2D.union(r, rect, r);
0248: }
0249: }
0250: int b = (int) Math.ceil(lineWidth);
0251: r.setFrame(r.getX() - b, r.getY() - b,
0252: r.getWidth() + 2 * b, r.getHeight() + 2 * b);
0253: return r;
0254: }
0255: return null;
0256: }
0257:
0258: private boolean isLabelTransformEnabled() {
0259: return labelTransformEnabled;
0260: }
0261:
0262: /**
0263: * Estimates whether the transform for label should be applied. With the
0264: * transform, the label will be painted along the edge. To apply transform,
0265: * rotate graphics by the angle returned from {@link #getLabelAngle}
0266: *
0267: * @return true, if transform can be applied, false otherwise
0268: */
0269: private boolean isLabelTransform(String label) {
0270: if (!isLabelTransformEnabled()) {
0271: return false;
0272: }
0273: Point2D p = getLabelPosition(view);
0274: if (p != null && label != null && label.length() > 0) {
0275: int sw = metrics.stringWidth(label);
0276: Point2D p1 = view.getPoint(0);
0277: Point2D p2 = view.getPoint(view.getPointCount() - 1);
0278: double length = Math.sqrt((p2.getX() - p1.getX())
0279: * (p2.getX() - p1.getX()) + (p2.getY() - p1.getY())
0280: * (p2.getY() - p1.getY()));
0281: if (!(length <= Double.NaN || length < sw)) {
0282: return true;
0283: }
0284: }
0285: return false;
0286: }
0287:
0288: /**
0289: * Calculates the angle at which graphics should be rotated to paint label
0290: * along the edge. Before calling this method always check that transform
0291: * should be applied using {@linkisLabelTransform}
0292: *
0293: * @return the value of the angle, 0 if the angle is zero or can't be
0294: * calculated
0295: */
0296: private double getLabelAngle(String label) {
0297: Point2D p = getLabelPosition(view);
0298: double angle = 0;
0299: if (p != null && label != null && label.length() > 0) {
0300: int sw = metrics.stringWidth(label);
0301: // Note: For control points you may want to choose other
0302: // points depending on the segment the label is in.
0303: Point2D p1 = view.getPoint(0);
0304: Point2D p2 = view.getPoint(view.getPointCount() - 1);
0305: // Length of the edge
0306: double length = Math.sqrt((p2.getX() - p1.getX())
0307: * (p2.getX() - p1.getX()) + (p2.getY() - p1.getY())
0308: * (p2.getY() - p1.getY()));
0309: if (!(length <= Double.NaN || length < sw)) { // Label fits into
0310: // edge's length
0311:
0312: // To calculate projections of edge
0313: double cos = (p2.getX() - p1.getX()) / length;
0314: double sin = (p2.getY() - p1.getY()) / length;
0315:
0316: // Determine angle
0317: angle = Math.acos(cos);
0318: if (sin < 0) { // Second half
0319: angle = 2 * Math.PI - angle;
0320: }
0321: }
0322: if (angle > Math.PI / 2 && angle <= Math.PI * 3 / 2) {
0323: angle -= Math.PI;
0324: }
0325: }
0326: return angle;
0327: }
0328:
0329: /**
0330: * Returns the label bounds of the specified view in the given graph.
0331: */
0332: public Rectangle2D getLabelBounds(JGraph paintingContext,
0333: EdgeView view) {
0334: if (paintingContext == null && graph != null) {
0335: JGraph graph = (JGraph) this .graph.get();
0336: paintingContext = graph;
0337: }
0338: // No need to call setView as getLabelPosition will
0339: String label = (paintingContext != null) ? paintingContext
0340: .convertValueToString(view) : String.valueOf(view
0341: .getCell());
0342: if (label != null) {
0343: Point2D p = getLabelPosition(view);
0344: Dimension d = getLabelSize(view, label);
0345: return getLabelBounds(p, d, label);
0346: } else {
0347: return null;
0348: }
0349: }
0350:
0351: /**
0352: * Returns the label bounds of the specified view in the given graph. Note:
0353: * The index is the position of the String object for the label in the extra
0354: * labels array of the view.
0355: */
0356: public Rectangle2D getExtraLabelBounds(JGraph paintingContext,
0357: EdgeView view, int index) {
0358: if (paintingContext == null && graph != null) {
0359: JGraph graph = (JGraph) this .graph.get();
0360: paintingContext = graph;
0361: }
0362: setView(view);
0363: Object[] labels = GraphConstants.getExtraLabels(view
0364: .getAllAttributes());
0365: if (labels != null && index < labels.length) {
0366: Point2D p = getExtraLabelPosition(this .view, index);
0367: Dimension d = getExtraLabelSize(paintingContext, this .view,
0368: index);
0369: String label = (paintingContext != null) ? paintingContext
0370: .convertValueToString(labels[index]) : String
0371: .valueOf(labels[index]);
0372: return getLabelBounds(p, d, label);
0373: }
0374: return new Rectangle2D.Double(getX(), getY(), 0, 0);
0375: }
0376:
0377: /**
0378: * Returns the label bounds of the specified view in the given graph.
0379: */
0380: public Rectangle2D getLabelBounds(Point2D p, Dimension d,
0381: String label) {
0382: if (label != null && isLabelTransform(label)) {
0383: // With transform label is rotated, so we should
0384: // rotate the rectangle (sw, sh) and return the
0385: // bounding rectangle
0386: double angle = getLabelAngle(label);
0387: if (angle < 0)
0388: angle = -angle;
0389: if (angle > Math.PI / 2)
0390: angle %= Math.PI / 2;
0391: double yside = Math.abs(Math.cos(angle) * d.height
0392: + Math.sin(angle) * d.width);
0393: double xside = Math.abs(d.width * Math.cos(angle)
0394: + d.height * Math.sin(angle));
0395: // Getting maximum is not good, but I don't want to be
0396: // drown in calculations
0397: if (xside > yside)
0398: yside = xside;
0399: if (yside > xside)
0400: xside = yside;
0401: angle = getLabelAngle(label);
0402:
0403: // Increasing by height is safe, but I think the precise
0404: // value is font.descent layed on edge vector and
0405: // projected on each axis
0406: d.width = (int) xside + d.height;
0407: d.height = (int) yside + d.height;
0408: }
0409: if (p != null && d != null) {
0410: double x = Math.max(0, p.getX() - (d.width / 2));
0411: double y = Math.max(0, p.getY() - (d.height / 2));
0412: return new Rectangle2D.Double(x, y, d.width + 1,
0413: d.height + 1);
0414: }
0415: return null;
0416: }
0417:
0418: /**
0419: * Returns the label position of the specified view in the given graph.
0420: */
0421: public Point2D getLabelPosition(EdgeView view) {
0422: setView(view);
0423: return getLabelPosition(view.getLabelPosition());
0424: }
0425:
0426: /**
0427: * Returns the label position of the specified view in the given graph.
0428: */
0429: public Point2D getExtraLabelPosition(EdgeView view, int index) {
0430: setView(view);
0431: Point2D[] pts = GraphConstants.getExtraLabelPositions(view
0432: .getAllAttributes());
0433: if (pts != null && index < pts.length)
0434: return getLabelPosition(pts[index]);
0435: return null;
0436: }
0437:
0438: /**
0439: * Returns the label position of the specified view in the given graph.
0440: */
0441: protected Point2D getLabelPosition(Point2D pos) {
0442: Rectangle2D tmp = getPaintBounds(view);
0443: int unit = GraphConstants.PERMILLE;
0444: Point2D p0 = view.getPoint(0);
0445: if (pos != null && tmp != null) {
0446: if (!isLabelTransformEnabled()) {
0447: return getRelativeLabelPosition(view, pos);
0448: } else {
0449: Point2D vector = view.getLabelVector();
0450: double dx = vector.getX();
0451: double dy = vector.getY();
0452: double len = Math.sqrt(dx * dx + dy * dy);
0453: if (len > 0) {
0454: double x = p0.getX() + (dx * pos.getX() / unit);
0455: double y = p0.getY() + (dy * pos.getX() / unit);
0456: x += (-dy * pos.getY() / len);
0457: y += (dx * pos.getY() / len);
0458: return new Point2D.Double(x, y);
0459: } else {
0460: return new Point2D.Double(p0.getX() + pos.getX(),
0461: p0.getY() + pos.getY());
0462: }
0463: }
0464: }
0465: return null;
0466: }
0467:
0468: protected Point2D getRelativeLabelPosition(EdgeView edge,
0469: Point2D geometry) {
0470: int pointCount = edge.getPointCount();
0471:
0472: double length = 0;
0473: double[] segments = new double[pointCount];
0474: Point2D pt = edge.getPoint(0);
0475:
0476: if (pt != null) {
0477: for (int i = 1; i < pointCount; i++) {
0478: Point2D tmp = edge.getPoint(i);
0479:
0480: if (tmp != null) {
0481: double dx = pt.getX() - tmp.getX();
0482: double dy = pt.getY() - tmp.getY();
0483:
0484: double segment = Math.sqrt(dx * dx + dy * dy);
0485:
0486: segments[i - 1] = segment;
0487: length += segment;
0488: pt = tmp;
0489: }
0490: }
0491:
0492: double x = geometry.getX() / GraphConstants.PERMILLE;
0493: double y = geometry.getY();
0494:
0495: double dist = x * length;
0496: length = 0;
0497:
0498: int index = 1;
0499: double segment = segments[0];
0500:
0501: while (dist > length + segment && index < pointCount - 1) {
0502: length += segment;
0503: segment = segments[index++];
0504: }
0505:
0506: double factor = (dist - length) / segment;
0507:
0508: Point2D p0 = edge.getPoint(index - 1);
0509: Point2D pe = edge.getPoint(index);
0510:
0511: if (p0 != null && pe != null) {
0512: double dx = pe.getX() - p0.getX();
0513: double dy = pe.getY() - p0.getY();
0514:
0515: double nx = dy / segment;
0516: double ny = dx / segment;
0517:
0518: double offsetX = 0;
0519: double offsetY = 0;
0520:
0521: Point2D offset = GraphConstants.getOffset(edge
0522: .getAllAttributes());
0523:
0524: if (offset != null) {
0525: offsetX = offset.getX();
0526: offsetY = offset.getY();
0527: }
0528:
0529: x = p0.getX() + dx * factor + nx * y + offsetX;
0530: y = p0.getY() + dy * factor + ny * y + offsetY;
0531:
0532: return new Point2D.Double(x, y);
0533: }
0534: }
0535:
0536: return null;
0537: }
0538:
0539: /**
0540: * Returns the label size of the specified view in the given graph.
0541: */
0542: public Dimension getExtraLabelSize(JGraph paintingContext,
0543: EdgeView view, int index) {
0544: Object[] labels = GraphConstants.getExtraLabels(view
0545: .getAllAttributes());
0546: if (labels != null && index < labels.length) {
0547: String label = (paintingContext != null) ? paintingContext
0548: .convertValueToString(labels[index]) : String
0549: .valueOf(labels[index]);
0550: return getLabelSize(view, label);
0551: }
0552: return null;
0553: }
0554:
0555: /**
0556: * Returns the label size of the specified view in the given graph.
0557: */
0558: public Dimension getLabelSize(EdgeView view, String label) {
0559: if (label != null && fontGraphics != null) {
0560: fontGraphics.setFont(GraphConstants.getFont(view
0561: .getAllAttributes()));
0562: metrics = fontGraphics.getFontMetrics();
0563: int sw = metrics.stringWidth(label);
0564: int sh = metrics.getHeight();
0565: return new Dimension(sw, sh);
0566: }
0567: return null;
0568: }
0569:
0570: /**
0571: * Installs the attributes of specified cell in this renderer instance. This
0572: * means, retrieve every published key from the cells hashtable and set
0573: * global variables or superclass properties accordingly.
0574: *
0575: * @param view
0576: * the cell view to retrieve the attribute values from.
0577: */
0578: protected void installAttributes(CellView view) {
0579: Map map = view.getAllAttributes();
0580: beginDeco = GraphConstants.getLineBegin(map);
0581: beginSize = GraphConstants.getBeginSize(map);
0582: beginFill = GraphConstants.isBeginFill(map)
0583: && isFillable(beginDeco);
0584: endDeco = GraphConstants.getLineEnd(map);
0585: endSize = GraphConstants.getEndSize(map);
0586: endFill = GraphConstants.isEndFill(map) && isFillable(endDeco);
0587: lineWidth = GraphConstants.getLineWidth(map);
0588: Edge.Routing routing = GraphConstants.getRouting(map);
0589: lineStyle = (routing != null && view instanceof EdgeView) ? routing
0590: .getPreferredLineStyle((EdgeView) view)
0591: : Edge.Routing.NO_PREFERENCE;
0592: if (lineStyle == Edge.Routing.NO_PREFERENCE)
0593: lineStyle = GraphConstants.getLineStyle(map);
0594: lineDash = GraphConstants.getDashPattern(map);
0595: dashOffset = GraphConstants.getDashOffset(map);
0596: borderColor = GraphConstants.getBorderColor(map);
0597: Color foreground = GraphConstants.getLineColor(map);
0598: setForeground((foreground != null) ? foreground
0599: : defaultForeground);
0600: Color background = GraphConstants.getBackground(map);
0601: setBackground((background != null) ? background
0602: : defaultBackground);
0603: Color gradientColor = GraphConstants.getGradientColor(map);
0604: setGradientColor(gradientColor);
0605: setOpaque(GraphConstants.isOpaque(map));
0606: setFont(GraphConstants.getFont(map));
0607: Color tmp = GraphConstants.getForeground(map);
0608: fontColor = (tmp != null) ? tmp : getForeground();
0609: labelTransformEnabled = GraphConstants.isLabelAlongEdge(map);
0610: }
0611:
0612: protected boolean isFillable(int decoration) {
0613: return !(decoration == GraphConstants.ARROW_SIMPLE
0614: || decoration == GraphConstants.ARROW_LINE || decoration == GraphConstants.ARROW_DOUBLELINE);
0615: }
0616:
0617: /**
0618: * Returns the bounds of the edge shape without label
0619: */
0620: public Rectangle2D getPaintBounds(EdgeView view) {
0621: Rectangle2D rec = null;
0622: setView(view);
0623: if (view.getShape() != null)
0624: rec = view.getShape().getBounds();
0625: else
0626: rec = new Rectangle2D.Double(0, 0, 0, 0);
0627: return rec;
0628: }
0629:
0630: /**
0631: * Paint the renderer.
0632: */
0633: public void paint(Graphics g) {
0634: if (view.isLeaf()) {
0635: Shape edgeShape = view.getShape();
0636: // Sideeffect: beginShape, lineShape, endShape
0637: if (edgeShape != null) {
0638: Graphics2D g2 = (Graphics2D) g;
0639: g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
0640: RenderingHints.VALUE_STROKE_PURE);
0641: int c = BasicStroke.CAP_BUTT;
0642: int j = BasicStroke.JOIN_MITER;
0643: setOpaque(false);
0644: super .paint(g);
0645: translateGraphics(g);
0646: g.setColor(getForeground());
0647: if (lineWidth > 0) {
0648: g2.setStroke(new BasicStroke(lineWidth, c, j));
0649: if (gradientColor != null && !preview) {
0650: g2.setPaint(new GradientPaint(0, 0,
0651: getBackground(), getWidth(),
0652: getHeight(), gradientColor, true));
0653: }
0654: if (view.beginShape != null) {
0655: if (beginFill)
0656: g2.fill(view.beginShape);
0657: g2.draw(view.beginShape);
0658: }
0659: if (view.endShape != null) {
0660: if (endFill)
0661: g2.fill(view.endShape);
0662: g2.draw(view.endShape);
0663: }
0664: if (lineDash != null) // Dash For Line Only
0665: g2.setStroke(new BasicStroke(lineWidth, c, j,
0666: 10.0f, lineDash, dashOffset));
0667: if (view.lineShape != null)
0668: g2.draw(view.lineShape);
0669: }
0670:
0671: if (selected) { // Paint Selected
0672: g2.setStroke(GraphConstants.SELECTION_STROKE);
0673: g2.setColor(highlightColor);
0674: if (view.beginShape != null)
0675: g2.draw(view.beginShape);
0676: if (view.lineShape != null)
0677: g2.draw(view.lineShape);
0678: if (view.endShape != null)
0679: g2.draw(view.endShape);
0680: }
0681: g2.setStroke(new BasicStroke(1));
0682: g.setFont((extraLabelFont != null) ? extraLabelFont
0683: : getFont());
0684: Object[] labels = GraphConstants.getExtraLabels(view
0685: .getAllAttributes());
0686: JGraph graph = (JGraph) this .graph.get();
0687: if (labels != null) {
0688: for (int i = 0; i < labels.length; i++)
0689: paintLabel(g, graph
0690: .convertValueToString(labels[i]),
0691: getExtraLabelPosition(view, i),
0692: false || !simpleExtraLabels);
0693: }
0694: if (graph.getEditingCell() != view.getCell()) {
0695: g.setFont(getFont());
0696: Object label = graph.convertValueToString(view);
0697: if (label != null) {
0698: paintLabel(g, label.toString(),
0699: getLabelPosition(view), true);
0700: }
0701: }
0702: }
0703: } else {
0704: paintSelectionBorder(g);
0705: }
0706: }
0707:
0708: /**
0709: * Provided for subclassers to paint a selection border.
0710: */
0711: protected void paintSelectionBorder(Graphics g) {
0712: ((Graphics2D) g).setStroke(GraphConstants.SELECTION_STROKE);
0713: if (childrenSelected)
0714: g.setColor(gridColor);
0715: else if (focus && selected)
0716: g.setColor(lockedHandleColor);
0717: else if (selected)
0718: g.setColor(highlightColor);
0719: if (childrenSelected || selected) {
0720: Dimension d = getSize();
0721: g.drawRect(0, 0, d.width - 1, d.height - 1);
0722: }
0723: }
0724:
0725: // This if for subclassers that to not want the graphics
0726: // to be relative to the top, left corner of this component.
0727: // Note: Override this method with an empty implementation
0728: // if you want absolute positions for your edges
0729: protected void translateGraphics(Graphics g) {
0730: g.translate(-getX(), -getY());
0731: }
0732:
0733: /**
0734: * Paint the specified label for the current edgeview.
0735: */
0736: protected void paintLabel(Graphics g, String label, Point2D p,
0737: boolean mainLabel) {
0738: if (p != null && label != null && label.length() > 0
0739: && metrics != null) {
0740: int sw = metrics.stringWidth(label);
0741: int sh = metrics.getHeight();
0742: Graphics2D g2 = (Graphics2D) g;
0743: boolean applyTransform = isLabelTransform(label);
0744: double angle = 0;
0745: int dx = -sw / 2;
0746: int offset = isMoveBelowZero || applyTransform ? 0 : Math
0747: .min(0, (int) (dx + p.getX()));
0748:
0749: g2.translate(p.getX() - offset, p.getY());
0750: if (applyTransform) {
0751: angle = getLabelAngle(label);
0752: g2.rotate(angle);
0753: }
0754: if (isOpaque() && mainLabel) {
0755: g.setColor(getBackground());
0756: g.fillRect(-sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2);
0757: }
0758: if (borderColor != null && mainLabel) {
0759: g.setColor(borderColor);
0760: g.drawRect(-sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2);
0761: }
0762:
0763: int dy = +sh / 4;
0764: g.setColor(fontColor);
0765: if (applyTransform && borderColor == null && !isOpaque()) {
0766: // Shift label perpendicularly by the descent so it
0767: // doesn't cross the line.
0768: dy = -metrics.getDescent();
0769: }
0770: g.drawString(label, dx, dy);
0771: if (applyTransform) {
0772: // Undo the transform
0773: g2.rotate(-angle);
0774: }
0775: g2.translate(-p.getX() + offset, -p.getY());
0776: }
0777: }
0778:
0779: /**
0780: * Returns the shape that represents the current edge in the context of the
0781: * current graph. This method sets the global beginShape, lineShape and
0782: * endShape variables as a side-effect.
0783: */
0784: protected Shape createShape() {
0785: int n = view.getPointCount();
0786: if (n > 1) {
0787: // Following block may modify static vars as side effect (Flyweight
0788: // Design)
0789: EdgeView tmp = view;
0790: Point2D[] p = null;
0791: p = new Point2D[n];
0792: for (int i = 0; i < n; i++) {
0793: Point2D pt = tmp.getPoint(i);
0794: if (pt == null)
0795: return null; // exit
0796: p[i] = new Point2D.Double(pt.getX(), pt.getY());
0797: }
0798:
0799: // End of Side-Effect Block
0800: // Undo Possible MT-Side Effects
0801: if (view != tmp) {
0802: view = tmp;
0803: installAttributes(view);
0804: }
0805: // End of Undo
0806: if (view.sharedPath == null) {
0807: view.sharedPath = new GeneralPath(
0808: GeneralPath.WIND_NON_ZERO, n);
0809: } else {
0810: view.sharedPath.reset();
0811: }
0812: view.beginShape = view.lineShape = view.endShape = null;
0813: Point2D p0 = p[0];
0814: Point2D pe = p[n - 1];
0815: Point2D p1 = p[1];
0816: Point2D p2 = p[n - 2];
0817:
0818: if (lineStyle == GraphConstants.STYLE_BEZIER && n > 2) {
0819: bezier = new Bezier(p);
0820: p2 = bezier.getPoint(bezier.getPointCount() - 1);
0821: } else if (lineStyle == GraphConstants.STYLE_SPLINE
0822: && n > 2) {
0823: spline = new Spline2D(p);
0824: double[] point = spline.getPoint(0.9875);
0825: // Extrapolate p2 away from the end point, pe, to avoid integer
0826: // rounding errors becoming too large when creating the line end
0827: double scaledX = pe.getX()
0828: - ((pe.getX() - point[0]) * 128);
0829: double scaledY = pe.getY()
0830: - ((pe.getY() - point[1]) * 128);
0831: p2.setLocation(scaledX, scaledY);
0832: }
0833:
0834: if (beginDeco != GraphConstants.ARROW_NONE) {
0835: view.beginShape = createLineEnd(beginSize, beginDeco,
0836: p1, p0);
0837: }
0838: if (endDeco != GraphConstants.ARROW_NONE) {
0839: view.endShape = createLineEnd(endSize, endDeco, p2, pe);
0840: }
0841: view.sharedPath
0842: .moveTo((float) p0.getX(), (float) p0.getY());
0843: /* THIS CODE WAS ADDED BY MARTIN KRUEGER 10/20/2003 */
0844: if (lineStyle == GraphConstants.STYLE_BEZIER && n > 2) {
0845: Point2D[] b = bezier.getPoints();
0846: view.sharedPath.quadTo((float) b[0].getX(),
0847: (float) b[0].getY(), (float) p1.getX(),
0848: (float) p1.getY());
0849: for (int i = 2; i < n - 1; i++) {
0850: Point2D b0 = b[2 * i - 3];
0851: Point2D b1 = b[2 * i - 2];
0852: view.sharedPath.curveTo((float) b0.getX(),
0853: (float) b0.getY(), (float) b1.getX(),
0854: (float) b1.getY(), (float) p[i].getX(),
0855: (float) p[i].getY());
0856: }
0857: view.sharedPath.quadTo((float) b[b.length - 1].getX(),
0858: (float) b[b.length - 1].getY(),
0859: (float) p[n - 1].getX(), (float) p[n - 1]
0860: .getY());
0861: } else if (lineStyle == GraphConstants.STYLE_SPLINE
0862: && n > 2) {
0863: for (double t = 0; t <= 1; t += 0.0125) {
0864: double[] xy = spline.getPoint(t);
0865: view.sharedPath
0866: .lineTo((float) xy[0], (float) xy[1]);
0867: }
0868: }
0869: /* END */
0870: else {
0871: for (int i = 1; i < n - 1; i++)
0872: view.sharedPath.lineTo((float) p[i].getX(),
0873: (float) p[i].getY());
0874: view.sharedPath.lineTo((float) pe.getX(), (float) pe
0875: .getY());
0876: }
0877: view.sharedPath
0878: .moveTo((float) pe.getX(), (float) pe.getY());
0879: if (view.endShape == null && view.beginShape == null) {
0880: // With no end decorations the line shape is the same as the
0881: // shared path and memory
0882: view.lineShape = view.sharedPath;
0883: } else {
0884: view.lineShape = (GeneralPath) view.sharedPath.clone();
0885: if (view.endShape != null)
0886: view.sharedPath.append(view.endShape, true);
0887: if (view.beginShape != null)
0888: view.sharedPath.append(view.beginShape, true);
0889: }
0890: return view.sharedPath;
0891: }
0892: return null;
0893: }
0894:
0895: /**
0896: * Paint the current view's direction. Sets tmpPoint as a side-effect such
0897: * that the invoking method can use it to determine the connection point to
0898: * this decoration.
0899: */
0900: protected Shape createLineEnd(int size, int style, Point2D src,
0901: Point2D dst) {
0902: if (src == null || dst == null)
0903: return null;
0904: int d = (int) Math.max(1, dst.distance(src));
0905: int ax = (int) -(size * (dst.getX() - src.getX()) / d);
0906: int ay = (int) -(size * (dst.getY() - src.getY()) / d);
0907: if (style == GraphConstants.ARROW_DIAMOND) {
0908: Polygon poly = new Polygon();
0909: poly.addPoint((int) dst.getX(), (int) dst.getY());
0910: poly.addPoint((int) (dst.getX() + ax / 2 + ay / 3),
0911: (int) (dst.getY() + ay / 2 - ax / 3));
0912: Point2D last = (Point2D) dst.clone();
0913: dst.setLocation(dst.getX() + ax, dst.getY() + ay);
0914: poly.addPoint((int) dst.getX(), (int) dst.getY());
0915: poly.addPoint((int) (last.getX() + ax / 2 - ay / 3),
0916: (int) (last.getY() + ay / 2 + ax / 3));
0917: return poly;
0918:
0919: } else if (style == GraphConstants.ARROW_TECHNICAL
0920: || style == GraphConstants.ARROW_CLASSIC) {
0921: Polygon poly = new Polygon();
0922: poly.addPoint((int) dst.getX(), (int) dst.getY());
0923: poly.addPoint((int) (dst.getX() + ax + ay / 2), (int) (dst
0924: .getY()
0925: + ay - ax / 2));
0926: Point2D last = (Point2D) dst.clone();
0927: if (style == GraphConstants.ARROW_CLASSIC) {
0928: dst.setLocation((int) (dst.getX() + ax * 2 / 3),
0929: (int) (dst.getY() + ay * 2 / 3));
0930: poly.addPoint((int) dst.getX(), (int) dst.getY());
0931: } else if (style == GraphConstants.ARROW_DIAMOND) {
0932: dst.setLocation(dst.getX() + 2 * ax, dst.getY() + 2
0933: * ay);
0934: poly.addPoint((int) dst.getX(), (int) dst.getY());
0935: } else
0936: dst.setLocation((int) (dst.getX() + ax), (int) (dst
0937: .getY() + ay));
0938: poly.addPoint((int) (last.getX() + ax - ay / 2),
0939: (int) (last.getY() + ay + ax / 2));
0940: return poly;
0941:
0942: } else if (style == GraphConstants.ARROW_SIMPLE) {
0943: GeneralPath path = new GeneralPath(
0944: GeneralPath.WIND_NON_ZERO, 4);
0945: path.moveTo((float) (dst.getX() + ax + ay / 2),
0946: (float) (dst.getY() + ay - ax / 2));
0947: path.lineTo((float) dst.getX(), (float) dst.getY());
0948: path.lineTo((float) (dst.getX() + ax - ay / 2),
0949: (float) (dst.getY() + ay + ax / 2));
0950: return path;
0951:
0952: } else if (style == GraphConstants.ARROW_CIRCLE) {
0953: Ellipse2D ellipse = new Ellipse2D.Float((float) (dst.getX()
0954: + ax / 2 - size / 2),
0955: (float) (dst.getY() + ay / 2 - size / 2), size,
0956: size);
0957: dst.setLocation(dst.getX() + ax, dst.getY() + ay);
0958: return ellipse;
0959:
0960: } else if (style == GraphConstants.ARROW_LINE
0961: || style == GraphConstants.ARROW_DOUBLELINE) {
0962: GeneralPath path = new GeneralPath(
0963: GeneralPath.WIND_NON_ZERO, 4);
0964: path.moveTo((float) (dst.getX() + ax / 2 + ay / 2),
0965: (float) (dst.getY() + ay / 2 - ax / 2));
0966: path.lineTo((float) (dst.getX() + ax / 2 - ay / 2),
0967: (float) (dst.getY() + ay / 2 + ax / 2));
0968: if (style == GraphConstants.ARROW_DOUBLELINE) {
0969: path.moveTo((float) (dst.getX() + ax / 3 + ay / 2),
0970: (float) (dst.getY() + ay / 3 - ax / 2));
0971: path.lineTo((float) (dst.getX() + ax / 3 - ay / 2),
0972: (float) (dst.getY() + ay / 3 + ax / 2));
0973: }
0974: return path;
0975: }
0976: return null;
0977: }
0978:
0979: /**
0980: * @return Returns the gradientColor.
0981: */
0982: public Color getGradientColor() {
0983: return gradientColor;
0984: }
0985:
0986: /**
0987: * @param gradientColor
0988: * The gradientColor to set.
0989: */
0990: public void setGradientColor(Color gradientColor) {
0991: this .gradientColor = gradientColor;
0992: }
0993:
0994: /**
0995: * Overridden for performance reasons. See the <a
0996: * href="#override">Implementation Note </a> for more information.
0997: */
0998: public void validate() {
0999: }
1000:
1001: /**
1002: * Overridden for performance reasons. See the <a
1003: * href="#override">Implementation Note </a> for more information.
1004: */
1005: public void revalidate() {
1006: }
1007:
1008: /**
1009: * Overridden for performance reasons. See the <a
1010: * href="#override">Implementation Note </a> for more information.
1011: */
1012: public void repaint(long tm, int x, int y, int width, int height) {
1013: }
1014:
1015: /**
1016: * Overridden for performance reasons. See the <a
1017: * href="#override">Implementation Note </a> for more information.
1018: */
1019: public void repaint(Rectangle r) {
1020: }
1021:
1022: /**
1023: * Overridden for performance reasons. See the <a
1024: * href="#override">Implementation Note </a> for more information.
1025: */
1026: protected void firePropertyChange(String propertyName,
1027: Object oldValue, Object newValue) {
1028: // Strings get interned...
1029: if (propertyName == "text")
1030: super .firePropertyChange(propertyName, oldValue, newValue);
1031: }
1032:
1033: /**
1034: * Overridden for performance reasons. See the <a
1035: * href="#override">Implementation Note </a> for more information.
1036: */
1037: public void firePropertyChange(String propertyName, byte oldValue,
1038: byte newValue) {
1039: }
1040:
1041: /**
1042: * Overridden for performance reasons. See the <a
1043: * href="#override">Implementation Note </a> for more information.
1044: */
1045: public void firePropertyChange(String propertyName, char oldValue,
1046: char newValue) {
1047: }
1048:
1049: /**
1050: * Overridden for performance reasons. See the <a
1051: * href="#override">Implementation Note </a> for more information.
1052: */
1053: public void firePropertyChange(String propertyName, short oldValue,
1054: short newValue) {
1055: }
1056:
1057: /**
1058: * Overridden for performance reasons. See the <a
1059: * href="#override">Implementation Note </a> for more information.
1060: */
1061: public void firePropertyChange(String propertyName, int oldValue,
1062: int newValue) {
1063: }
1064:
1065: /**
1066: * Overridden for performance reasons. See the <a
1067: * href="#override">Implementation Note </a> for more information.
1068: */
1069: public void firePropertyChange(String propertyName, long oldValue,
1070: long newValue) {
1071: }
1072:
1073: /**
1074: * Overridden for performance reasons. See the <a
1075: * href="#override">Implementation Note </a> for more information.
1076: */
1077: public void firePropertyChange(String propertyName, float oldValue,
1078: float newValue) {
1079: }
1080:
1081: /**
1082: * Overridden for performance reasons. See the <a
1083: * href="#override">Implementation Note </a> for more information.
1084: */
1085: public void firePropertyChange(String propertyName,
1086: double oldValue, double newValue) {
1087: }
1088:
1089: /**
1090: * Overridden for performance reasons. See the <a
1091: * href="#override">Implementation Note </a> for more information.
1092: */
1093: public void firePropertyChange(String propertyName,
1094: boolean oldValue, boolean newValue) {
1095: }
1096:
1097: }
|