001: /*
002: * $Id: JGraphpadVertexRenderer.java,v 1.17 2007/07/23 08:20:53 gaudenz Exp $
003: * Copyright (c) 2001-2005, Gaudenz Alder
004: *
005: * All rights reserved.
006: *
007: * This file is licensed under the JGraph software license, a copy of which
008: * will have been provided to you in the file LICENSE at the root of your
009: * installation directory. If you are unable to locate this file please
010: * contact JGraph sales for another copy.
011: */
012: package com.jgraph.pad.graph;
013:
014: import java.awt.BasicStroke;
015: import java.awt.Color;
016: import java.awt.Component;
017: import java.awt.Dimension;
018: import java.awt.GradientPaint;
019: import java.awt.Graphics;
020: import java.awt.Graphics2D;
021: import java.awt.Image;
022: import java.awt.Point;
023: import java.awt.Polygon;
024: import java.awt.Rectangle;
025: import java.awt.Shape;
026: import java.awt.Stroke;
027: import java.awt.geom.Area;
028: import java.awt.geom.Ellipse2D;
029: import java.awt.geom.Point2D;
030: import java.awt.geom.Rectangle2D;
031: import java.util.Map;
032:
033: import javax.swing.BorderFactory;
034: import javax.swing.Icon;
035: import javax.swing.ImageIcon;
036: import javax.swing.JComponent;
037: import javax.swing.JLabel;
038: import javax.swing.JTextPane;
039: import javax.swing.border.Border;
040: import javax.swing.text.SimpleAttributeSet;
041: import javax.swing.text.StyleConstants;
042: import javax.swing.text.StyledDocument;
043:
044: import org.jgraph.JGraph;
045: import org.jgraph.graph.AbstractCellView;
046: import org.jgraph.graph.CellView;
047: import org.jgraph.graph.DefaultGraphModel;
048: import org.jgraph.graph.GraphConstants;
049: import org.jgraph.graph.VertexRenderer;
050: import org.jgraph.graph.VertexView;
051:
052: /**
053: * Universal renderer for vertices and groups. This implementation supports
054: * drawing rectangles, circles, diamonds and rounded rectangles and optionally
055: * paints a gradient background, stretched background image and folding icon.
056: */
057: public class JGraphpadVertexRenderer extends VertexRenderer {
058:
059: /**
060: * Client property for JGraph to control the display of the folding icons.
061: * Default is true, eg if the property is missing the icons are painted. To
062: * switch this feature off, use the following code:
063: *
064: * <PRE>
065: *
066: * graph.putClientProperty(
067: * JGraphpadVertexRenderer.CLIENTPROPERTY_SHOWFOLDINGICONS, new
068: * Boolean(false));
069: *
070: * </PRE>
071: */
072: public static String CLIENTPROPERTY_SHOWFOLDINGICONS = "showFoldingIcons";
073:
074: /**
075: * Defines a dimension of width and height 0.
076: */
077: public static Dimension ZERO_DIMENSION = new Dimension(0, 0);
078:
079: /**
080: * Defines the default inset to render rich text.
081: */
082: public static int INSET = 4;
083:
084: /**
085: * Defines the root handle size and location.
086: */
087: public static Rectangle handle = new Rectangle(0, 0, 7, 7);
088:
089: /**
090: * Holds a reference to fetch the correct cell value from the model in
091: * paint. This should go into getComponentRenderer.
092: */
093: protected JGraph graph;
094:
095: /**
096: * Defines the shape constants to be used as values for the
097: * {@link JGraphpadGraphConstants#VERTEXSHAPE} attributes.
098: */
099: public static final int SHAPE_RECTANGLE = 0, SHAPE_CIRCLE = 1,
100: SHAPE_DIAMOND = 2, SHAPE_ROUNDED = 3, SHAPE_CYLINDER = 4,
101: SHAPE_TRIANGLE = 5;
102:
103: /**
104: * Holds the text pane to be used for rich text rendering.
105: */
106: public static JTextPane textPane = new JTextPane();
107:
108: /**
109: * Holds the wrapper renderer used for heavyweights.
110: */
111: protected static JComponent wrapperRenderer;
112:
113: /**
114: * Holds the user object of the current cell.
115: */
116: protected Object userObject = null;
117:
118: /**
119: * Holds the shape of the current view.
120: */
121: protected int shape = 0;
122:
123: /**
124: * Specifies whether the current view is a rich text value, and if the image
125: * should be stretched.
126: */
127: protected boolean isRichText = false, stretchImage = false,
128: isEditing = false, showFoldingIcons = true,
129: isGroup = false;
130:
131: /**
132: * Holds the background and foreground of the graph.
133: */
134: protected Color graphBackground = Color.white,
135: graphForeground = Color.black;
136:
137: /**
138: * References the value component of the user object if one exists.
139: */
140: protected Component valueComponent;
141:
142: /**
143: * Holds the area to be painted for the cylinder shape.
144: */
145: protected Area cylinderArea = null;
146:
147: /**
148: * Holds the shape to be painted for diamond cells.
149: */
150: protected Polygon diamond = null;
151:
152: /**
153: * Holds the round rect arc size for rounded rectangles.
154: */
155: protected int roundRectArc = 0;
156:
157: /**
158: * Specified if a heavyweight should be painted. Default is true.
159: */
160: protected transient boolean showHeavyweight = true;
161:
162: /**
163: * Constructs a new vertex renderer.
164: */
165: public JGraphpadVertexRenderer() {
166: textPane.setOpaque(false);
167: textPane.setBorder(BorderFactory.createEmptyBorder(INSET,
168: INSET, INSET, INSET));
169:
170: // Makes sure the heavyweights is never returned directly,
171: // so that the real component is never touched directly.
172: wrapperRenderer = new JComponent() {
173: public void paint(Graphics g) {
174: if (showHeavyweight) {
175: valueComponent.setSize(getSize());
176: if (!isEditing)
177: valueComponent.paint(g);
178: } else {
179: g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
180: g.drawLine(0, 0, getWidth() - 1, getHeight() - 1);
181: g.drawLine(getWidth() - 1, 0, 0, getHeight() - 1);
182: }
183: }
184: };
185: wrapperRenderer.setDoubleBuffered(false);
186: }
187:
188: /**
189: * Overrides the parent implementation to return the value component stored
190: * in the user object instead of this renderer if a value component exists.
191: * This applies some of the values installed to this renderer to the value
192: * component (border, opaque) if the latter is a JComponent.
193: *
194: * @return Returns a configured renderer for the specified view.
195: */
196: public Component getRendererComponent(JGraph graph, CellView view,
197: boolean sel, boolean focus, boolean preview) {
198: this .graph = graph;
199: Component c = super .getRendererComponent(graph, view, sel,
200: focus, preview);
201: graphBackground = graph.getBackground();
202: graphForeground = graph.getForeground();
203: isEditing = graph.getEditingCell() == view.getCell();
204: isGroup = DefaultGraphModel.isGroup(graph.getModel(), view
205: .getCell());
206: Boolean bool = (Boolean) graph
207: .getClientProperty(CLIENTPROPERTY_SHOWFOLDINGICONS);
208: if (bool != null)
209: showFoldingIcons = bool.booleanValue();
210: else
211: showFoldingIcons = true;
212: if (valueComponent != null) {
213: valueComponent.setSize(getSize());
214: valueComponent.setBackground(getBackground());
215:
216: // Shows a cross for all scaled heavyweight previews
217: // for faster preview performance
218: showHeavyweight = !preview || graph.getScale() == 1;
219:
220: // Applies the configured properties to the value component
221: if (valueComponent instanceof JComponent) {
222: JComponent comp = (JComponent) valueComponent;
223: if (comp.getBorder() == null) {
224: comp.setBorder(getBorder());
225: }
226: comp.setOpaque(isOpaque());
227:
228: // Workaround for locking problem in windows
229: comp.setDoubleBuffered(false);
230:
231: // Do not wrap component hierachies
232: if (comp.getComponentCount() > 0 && !isEditing
233: && showHeavyweight) {
234: return comp;
235: }
236: }
237: // Do not return the component directly, return a wrapper
238: // so that the real component is never touched directly.
239: return wrapperRenderer;
240: }
241: return c;
242: }
243:
244: /**
245: * Paints the renderer component for the configured view. This
246: * implementation consists of three parts: painting the background, gradient
247: * and stretched image, painting the content by doing a supercall and
248: * calling the rich text renderer if required, and finally paint the border,
249: * selection border and the folding handle.
250: *
251: * @param g
252: * The graphics to paint the cell to.
253: */
254: public void paint(Graphics g) {
255: Border previousBorder = getBorder();
256: paintBackground(g);
257:
258: // Limits all further painting to the clipping region of the actual
259: // shape so that images and text are cropped at the shape bounds.
260: // The clipping region is intersected with the "dirty" region.
261: Dimension d = getSize();
262: int b = borderWidth;
263: Shape previousShape = cropToShape(g);
264: if (shape == SHAPE_DIAMOND || shape == SHAPE_TRIANGLE) {
265: Area clip = new Area(diamond);
266: clip.intersect(new Area(previousShape));
267: g.setClip(clip);
268: } else if (shape == SHAPE_CYLINDER) {
269: Area clip = new Area(cylinderArea);
270: cylinderArea.intersect(new Area(previousShape));
271: g.setClip(clip);
272: } else if (shape == SHAPE_CIRCLE) {
273: Area clip = new Area(new java.awt.geom.Ellipse2D.Float(-b,
274: -b, d.width + b, d.height + b));
275: clip.intersect(new Area(previousShape));
276: g.setClip(clip);
277: }
278:
279: // Stretched images appear in the background of the cell. This sets
280: // the icon to null after painting and takes over all image painting
281: // in the case of stretched images as the superclass does not support
282: // it. No images are drawn for previews to speedup the display.
283: if (stretchImage) {
284: Image img = null;
285: Icon icon = getIcon();
286: if (icon != null)
287: img = ((ImageIcon) icon).getImage();
288: if (img != null && !preview)
289: g.drawImage(img, 0, 0, d.width - 1, d.height - 1, this );
290: setIcon(null);
291: }
292:
293: // Makes sure that no border, background or selection border is painted
294: // by the superclass' paint in case we already did that or will do that
295: // later. This makes sure the superclass' only paints the label and
296: // image.
297: boolean wasSelected = selected;
298: boolean wasOpaque = isOpaque();
299: if (shape != SHAPE_RECTANGLE) {
300: setBorder(null);
301: setOpaque(false);
302: selected = false;
303: }
304:
305: // Makes sure the superclass' paint method paints doesn't paint the
306: // label in case we use a rich text box internally to paint the text.
307: if (isRichText)
308: setText("");
309:
310: // Calls the superclass paint method and restores the previous border
311: // and selection state.
312: super .paint(g);
313: setBorder(previousBorder);
314: setOpaque(wasOpaque);
315: selected = wasSelected;
316:
317: // Paints the rich text foreground which is not painted by the
318: // superclass. The code below applies the vertical aligment of
319: // this renderer to the painted rich text by translating the
320: // graphics by the required amount. This is trick to implement
321: // vertical alignment in rich text panes which is otherwise
322: // not supported.
323: if (isRichText)
324: paintRichText(g);
325:
326: // Finished painting of all content within the shape clipping region so
327: // restore the original clipping region.
328: g.setClip(previousShape);
329:
330: // Paints the border for all non-rectangular shapes using the geometry
331: // defined for painting the background.
332: Graphics2D g2 = (Graphics2D) g;
333: Stroke previousStroke = g2.getStroke();
334: if (shape != SHAPE_RECTANGLE && getBorder() != null) {
335: g.setColor(bordercolor);
336: g2.setStroke(new BasicStroke(b));
337: paintShapeBorder(g);
338: }
339:
340: // Paints the selection border for all non-rectangular shapes using the
341: // geometry defined for painting the background.
342: if (selected || childrenSelected) {
343: if (childrenSelected)
344: g.setColor(gridColor);
345: else
346: g.setColor(highlightColor);
347: g2.setStroke(GraphConstants.SELECTION_STROKE);
348: paintShapeBorder(g);
349: }
350:
351: // Restores the previous stroke and paints the folding icon
352: g2.setStroke(previousStroke);
353: if (showFoldingIcons)
354: paintFoldingIcon(g);
355: }
356:
357: /**
358: * Limits all further painting to the clipping region of the actual
359: * shape so that images and text are cropped at the shape bounds.
360: * The clipping region is intersected with the "dirty" region.
361: *
362: * @param g
363: * The graphics to crop.
364: * @return The shape before cropping
365: */
366: protected Shape cropToShape(Graphics g) {
367: Shape previousShape = g.getClip();
368: Dimension d = getSize();
369: int b = borderWidth;
370: if (shape == SHAPE_DIAMOND || shape == SHAPE_TRIANGLE) {
371: Area clip = new Area(diamond);
372: clip.intersect(new Area(previousShape));
373: g.setClip(clip);
374: } else if (shape == SHAPE_CYLINDER) {
375: Area clip = new Area(cylinderArea);
376: cylinderArea.intersect(new Area(previousShape));
377: g.setClip(clip);
378: } else if (shape == SHAPE_CIRCLE) {
379: Area clip = new Area(new java.awt.geom.Ellipse2D.Float(-b,
380: -b, d.width + b, d.height + b));
381: clip.intersect(new Area(previousShape));
382: g.setClip(clip);
383: }
384:
385: return previousShape;
386: }
387:
388: /**
389: * Utility method to paint the background for all non-rectangular shapes.
390: *
391: * @param g
392: * The graphics to paint the background to.
393: */
394: protected void paintBackground(Graphics g) {
395: Dimension d = getSize();
396: int b = borderWidth;
397:
398: // Paints the background if the shape is not a rectangle.
399: // Rectangles are handled by the superclass' paint method.
400: if (shape != SHAPE_RECTANGLE) {
401:
402: // Prepares the shapes geometries for painting the background
403: // and border. This does not do any actual painting but
404: // constructs the mathematical diamond shape.
405: if (shape == SHAPE_DIAMOND) {
406: int width = d.width - b;
407: int height = d.height - b;
408: int halfWidth = (d.width - b) / 2;
409: int halfHeight = (d.height - b) / 2;
410: int[] xpoints = { halfWidth, width, halfWidth, 0 };
411: int[] ypoints = { 0, halfHeight, height, halfHeight };
412: diamond = new Polygon(xpoints, ypoints, 4);
413: }
414:
415: if (shape == SHAPE_TRIANGLE) {
416: int width = d.width - b;
417: int height = d.height - b;
418: int halfHeight = (d.height - b) / 2;
419: int[] xpoints = { 0, width, 0 };
420: int[] ypoints = { 0, halfHeight, height };
421: diamond = new Polygon(xpoints, ypoints, 3);
422: }
423:
424: // Computes the area for the cylinder (db-style)
425: else if (shape == SHAPE_CYLINDER) {
426: int h4 = (int) (d.getHeight() / 4);
427: int r = d.width - b - 1;
428: cylinderArea = new Area(new Rectangle(b, (h4 - b) / 2
429: + b, r, d.height - h4 - b));
430: cylinderArea.add(new Area(new Ellipse2D.Double(b, b, r,
431: h4 - b)));
432: cylinderArea.add(new Area(new Ellipse2D.Double(b,
433: d.height - h4 - b, r, h4)));
434: }
435:
436: // Computes the rounded rect arc for rounded rectangles. This
437: // is expensive sothe result is cached.
438: else if (shape == SHAPE_ROUNDED)
439: roundRectArc = getArcSize(d.width - b, d.height - b);
440:
441: // Paints the gradient background or filled background by
442: // using the shape geometries created above and the colors
443: // that were set in installAttributes.
444: if (isOpaque()) {
445: g.setColor(super .getBackground());
446:
447: // Paints the gradient background only if we're not in
448: // preview mode to speedup the previews.
449: if (gradientColor != null && !preview)
450: ((Graphics2D) g).setPaint(new GradientPaint(0, 0,
451: getBackground(), getWidth(), getHeight(),
452: gradientColor, true));
453:
454: // Paints the actual background using the proper shapes.
455: // Painting the rectangle background is handled by the
456: // superclass' paint method.
457: if (shape == SHAPE_CIRCLE)
458: g.fillOval(b - 1, b - 1, d.width - b, d.height - b);
459: else if (shape == SHAPE_CYLINDER)
460: ((Graphics2D) g).fill(cylinderArea);
461: else if (shape == SHAPE_DIAMOND
462: || shape == SHAPE_TRIANGLE)
463: g.fillPolygon(diamond);
464: else if (shape == SHAPE_ROUNDED)
465: g.fillRoundRect(b / 2, b / 2, d.width
466: - (int) (b * 1.5), d.height
467: - (int) (b * 1.5), roundRectArc,
468: roundRectArc);
469: }
470: }
471: }
472:
473: /**
474: * Utility method to paint the rich text content for rich text values. This
475: * implementation simulates rich text vertical alignment by translating the
476: * graphics before painting the textPane.
477: *
478: * @param g
479: * The graphics to paint the rich text content to.
480: */
481: protected void paintRichText(Graphics g) {
482: textPane.setSize(getSize());
483: int yoffset = 0;
484:
485: // Computes the vertical offset to match the vertical alignment
486: if (getVerticalAlignment() == CENTER)
487: yoffset = (int) ((getHeight() - textPane.getPreferredSize()
488: .getHeight()) / 2)
489: + 2 * INSET;
490: else if (getVerticalAlignment() == BOTTOM)
491: yoffset = (int) (getHeight()
492: - textPane.getPreferredSize().getHeight() + 3 * INSET);
493: g.translate(0, yoffset);
494: textPane.paint(g);
495: g.translate(0, -yoffset);
496: }
497:
498: /**
499: * Utility method to paint the border for all non-rectangular shapes.
500: *
501: * @param g
502: * The graphics to paint the border to.
503: */
504: protected void paintShapeBorder(Graphics g) {
505: Dimension d = getSize();
506: int b = borderWidth;
507: if (shape == SHAPE_CIRCLE)
508: g.drawOval(b - 1, b - 1, d.width - b, d.height - b);
509: else if (shape == SHAPE_CYLINDER) {
510: int h4 = (int) (d.getHeight() / 4);
511: int r = d.width - b - 1;
512: g.drawOval(b, b, r, h4 - b);
513: g.drawLine(b, (h4 - b) / 2 + 2 + b, b, d.height - (h4 - b)
514: / 2 - 2 - b);
515: g.drawLine(d.width - (b + 1) / 2, (h4 - b) / 2 + 2 + b,
516: d.width - (b + 1) / 2, d.height - (h4 - b) / 2 - 2
517: - b);
518: g.drawArc(b, d.height - h4 - b, r, h4, 0,
519: (isOpaque()) ? -180 : 360);
520: } else if (shape == SHAPE_DIAMOND || shape == SHAPE_TRIANGLE)
521: g.drawPolygon(diamond);
522: else if (shape == SHAPE_ROUNDED)
523: g.drawRoundRect(b / 2, b / 2,
524: d.width - (int) (b * 1.5) - 1, d.height
525: - (int) (b * 1.5), roundRectArc,
526: roundRectArc);
527: }
528:
529: /**
530: * Utility method to paint the folding icon for groups.
531: *
532: * @param g
533: * The graphics to paint the border to.
534: */
535: protected void paintFoldingIcon(Graphics g) {
536: if (isGroup) {
537: g.setColor(graphBackground);
538: g.fill3DRect(handle.x, handle.y, handle.width,
539: handle.height, true);
540: g.setColor(graphForeground);
541: g.drawRect(handle.x, handle.y, handle.width, handle.height);
542: int h2 = handle.y + handle.height / 2;
543: g.drawLine(handle.x + 1, h2, handle.x + handle.width - 2,
544: h2);
545: if (view.isLeaf()) {
546: int w2 = handle.x + handle.width / 2;
547: g.drawLine(w2, handle.y + 1, w2, handle.y
548: + handle.height - 2);
549: }
550: }
551: }
552:
553: /**
554: * Returns an appropriate arc for the corners of the rectangle for boundary
555: * size cases of width and height. The arc width of a rectangle is 1/5th of
556: * the larger of the two of the dimensions passed in, but at most 1/2 of the
557: * smaller of the two. 1/5 because it looks nice and 1/2 so the arc can
558: * complete in the given dimension
559: *
560: * @param width
561: * The width to compute the arc size for.
562: * @param height
563: * The height to compute the arc size for.
564: * @return Returns the arc size.
565: */
566: public static int getArcSize(int width, int height) {
567: int arcSize;
568: if (width <= height) {
569: arcSize = height / 5;
570: if (arcSize > (width / 2))
571: arcSize = width / 2;
572: } else {
573: arcSize = width / 5;
574: if (arcSize > (height / 2))
575: arcSize = height / 2;
576: }
577: return arcSize;
578: }
579:
580: /**
581: * Overrides the parent's implementation to return the perimeter points for
582: * non-rectangular shapes, namely diamonds and circles. The source point is
583: * typically ignored and the center point is used instead.
584: *
585: * @param view
586: * The view to return the perimeter point for.
587: * @param source
588: * The location of the start point of the line to be intersected
589: * with the boundaries.
590: * @param p
591: * The location of the end point of the line to be intersected
592: * with the boundaries.
593: */
594: public Point2D getPerimeterPoint(VertexView view, Point2D source,
595: Point2D p) {
596: int shape = JGraphpadGraphConstants.getVertexShape(view
597: .getAllAttributes());
598: if (shape == SHAPE_DIAMOND) {
599: return getDiamondPerimeterPoint(view, source, p);
600: } else if (shape == SHAPE_CIRCLE) {
601: return getCirclePerimeterPoint(view, source, p);
602: } else if (shape == SHAPE_TRIANGLE) {
603: return getTrianglePerimeterPoint(view, source, p);
604: }
605: return super .getPerimeterPoint(view, source, p);
606: }
607:
608: /**
609: * Utility method to return the perimeter point for a circle.
610: *
611: * @param view
612: * The view that defines the bounds of the circle.
613: * @param source
614: * The start point of theline to intersect with the circle.
615: * @param p
616: * The end point of the line to intersect with the circle.
617: * @return The interaction of the circle and the line between source and p.
618: */
619: public Point2D getCirclePerimeterPoint(VertexView view,
620: Point2D source, Point2D p) {
621: Rectangle2D r = view.getBounds();
622:
623: double x = r.getX();
624: double y = r.getY();
625: double a = (r.getWidth() + 1) / 2;
626: double b = (r.getHeight() + 1) / 2;
627:
628: // x0,y0 - center of ellipse
629: double x0 = x + a;
630: double y0 = y + b;
631:
632: // x1, y1 - point
633: double x1 = p.getX();
634: double y1 = p.getY();
635:
636: // Calculates straight line equation through point and ellipse center
637: // y = d * x + h
638: double dx = x1 - x0;
639: double dy = y1 - y0;
640:
641: if (dx == 0)
642: return new Point((int) x0, (int) (y0 + b * dy
643: / Math.abs(dy)));
644:
645: double d = dy / dx;
646: double h = y0 - d * x0;
647:
648: // Calculates intersection
649: double e = a * a * d * d + b * b;
650: double f = -2 * x0 * e;
651: double g = a * a * d * d * x0 * x0 + b * b * x0 * x0 - a * a
652: * b * b;
653:
654: double det = Math.sqrt(f * f - 4 * e * g);
655:
656: // Two solutions (perimeter points)
657: double xout1 = (-f + det) / (2 * e);
658: double xout2 = (-f - det) / (2 * e);
659: double yout1 = d * xout1 + h;
660: double yout2 = d * xout2 + h;
661:
662: double dist1 = Math.sqrt(Math.pow((xout1 - x1), 2)
663: + Math.pow((yout1 - y1), 2));
664: double dist2 = Math.sqrt(Math.pow((xout2 - x1), 2)
665: + Math.pow((yout2 - y1), 2));
666:
667: // Correct solution
668: double xout, yout;
669:
670: if (dist1 < dist2) {
671: xout = xout1;
672: yout = yout1;
673: } else {
674: xout = xout2;
675: yout = yout2;
676: }
677:
678: return new Point2D.Double(xout, yout);
679: }
680:
681: /**
682: * Utility method to return the perimeter point for a diamond.
683: *
684: * @param view
685: * The view that defines the bounds of the diamond.
686: * @param source
687: * The start point of theline to intersect with the diamond.
688: * @param p
689: * The end point of the line to intersect with the diamond.
690: * @return The interaction of the diamond and the line between source and p.
691: */
692: public Point2D getDiamondPerimeterPoint(VertexView view,
693: Point2D source, Point2D p) {
694: Rectangle2D bounds = view.getBounds();
695: Point2D center = AbstractCellView.getCenterPoint(view);
696: double halfwidth = bounds.getWidth() / 2;
697: double halfheight = bounds.getHeight() / 2;
698: Point2D top = new Point2D.Double(center.getX(), center.getY()
699: - halfheight);
700: Point2D bottom = new Point2D.Double(center.getX(), center
701: .getY()
702: + halfheight);
703: Point2D left = new Point2D.Double(center.getX() - halfwidth,
704: center.getY());
705: Point2D right = new Point2D.Double(center.getX() + halfwidth,
706: center.getY());
707:
708: // Special case for intersecting the diamond's points
709: if (center.getX() == p.getX())
710: if (center.getY() > p.getY()) // top point
711: return (top);
712: else
713: return (bottom); // bottom point
714: if (center.getY() == p.getY())
715: if (center.getX() > p.getX()) // left point
716: return (left);
717: else
718: return (right); // right point
719:
720: // In which quadrant will the intersection be?
721: // set the slope and offset of the border line accordingly
722: Point2D i;
723: if (p.getX() < center.getX())
724: if (p.getY() < center.getY())
725: i = intersection(p, center, top, left);
726: else
727: i = intersection(p, center, bottom, left);
728: else if (p.getY() < center.getY())
729: i = intersection(p, center, top, right);
730: else
731: i = intersection(p, center, bottom, right);
732: return i;
733: }
734:
735: /**
736: * Utility method to return the perimeter point for a triangle.
737: *
738: * @param view
739: * The view that defines the bounds of the diamond.
740: * @param source
741: * The start point of theline to intersect with the diamond.
742: * @param p
743: * The end point of the line to intersect with the diamond.
744: * @return The interaction of the diamond and the line between source and p.
745: */
746: public Point2D getTrianglePerimeterPoint(VertexView view,
747: Point2D source, Point2D p) {
748: Rectangle2D bounds = view.getBounds();
749:
750: double x = bounds.getX();
751: double y = bounds.getY();
752: double width = bounds.getWidth();
753: double height = bounds.getHeight();
754: double xCenter = x + width / 2;
755: double yCenter = y + height / 2;
756: Point2D center = AbstractCellView.getCenterPoint(view);
757: Point2D top = new Point2D.Double(x, y);
758: Point2D bottom = new Point2D.Double(x, y + height);
759: Point2D right = new Point2D.Double(x + width, yCenter);
760:
761: // Compute angle
762: double dx = p.getX() - xCenter;
763: double dy = p.getY() - yCenter;
764: double alpha = Math.atan2(dy, dx);
765: double t = Math.atan2(height, width);
766: Point2D i;
767: if (alpha < -Math.PI + t || alpha > Math.PI - t) { // Left edge
768: i = new Point2D.Double(x, yCenter - width * Math.tan(alpha)
769: / 2);
770: } else if (yCenter > p.getY()) { // Top Slope
771: i = intersection(p, center, top, right);
772: } else { // Bottom Slope
773: i = intersection(p, center, bottom, right);
774: }
775: return i;
776: }
777:
778: /**
779: * Find the point of intersection of two straight lines (which follow the
780: * equation y=mx+b) one line is an incoming edge and the other is one side
781: * of the diamond.
782: *
783: * @param lineOneStart
784: * The start point of the first line.
785: * @param lineOneEnd
786: * The end point of the first line.
787: * @param lineTwoStart
788: * The start point of the second line.
789: * @param lineTwoEnd
790: * The end point of the second line.
791: * @return Returns the intersection point between the first and the second
792: * line.
793: */
794: protected Point2D intersection(Point2D lineOneStart,
795: Point2D lineOneEnd, Point2D lineTwoStart, Point2D lineTwoEnd) {
796: // m = delta y / delta x, the slope of a line
797: // b = y - mx, the axis intercept
798: double m1 = (double) (lineOneEnd.getY() - lineOneStart.getY())
799: / (double) (lineOneEnd.getX() - lineOneStart.getX());
800: double b1 = lineOneStart.getY() - m1 * lineOneStart.getX();
801: double m2 = (double) (lineTwoEnd.getY() - lineTwoStart.getY())
802: / (double) (lineTwoEnd.getX() - lineTwoStart.getX());
803: double b2 = lineTwoStart.getY() - m2 * lineTwoStart.getX();
804: double xinter = (b1 - b2) / (m2 - m1);
805: double yinter = m1 * xinter + b1;
806: Point2D intersection = new Point2D.Double(xinter, yinter);
807: return intersection;
808: }
809:
810: /**
811: * Overrides the parent's implementation to return a slightly larger
812: * preferred size for circles and rounded rectangles.
813: *
814: * @return Returns the preferreds size for the current view.
815: */
816: public Dimension getPreferredSize() {
817: Dimension d = super .getPreferredSize();
818: if (shape == SHAPE_CIRCLE) {
819: d.width += d.width / 8;
820: d.height += d.height / 2;
821: } else if (shape == SHAPE_ROUNDED)
822: d.width += d.height / 5;
823: else if (isRichText) {
824: textPane.setSize(ZERO_DIMENSION);
825: return textPane.getPreferredSize();
826: } else if (valueComponent != null)
827: return valueComponent.getPreferredSize();
828: return d;
829: }
830:
831: /**
832: * Resets attributes that would affect rendering if the
833: * {@link #installAttributes(CellView)} is not being called, which is the
834: * case if the view is a group and it's groupOpaque attribute is set to
835: * false.
836: */
837: protected void resetAttributes() {
838: super .resetAttributes();
839: shape = JGraphpadGraphConstants.getVertexShape(view
840: .getAllAttributes());
841: isRichText = false;
842: valueComponent = null;
843: }
844:
845: /**
846: * Extends the parent's method to configure the renderer for displaying the
847: * specified view.
848: *
849: * @param view
850: * The view to configure the renderer for.
851: */
852: public void installAttributes(CellView view) {
853: super .installAttributes(view);
854: Map map = view.getAllAttributes();
855: shape = JGraphpadGraphConstants.getVertexShape(view
856: .getAllAttributes());
857: stretchImage = JGraphpadGraphConstants.isStretchImage(map);
858:
859: // Adds the inset as an empty border to the existing border.
860: int i = GraphConstants.getInset(map);
861: Border insetBorder = (i > 0) ? BorderFactory.createEmptyBorder(
862: i, i, i, i) : null;
863: if (insetBorder != null) {
864: if (getBorder() == null)
865: setBorder(insetBorder);
866: else
867: setBorder(BorderFactory.createCompoundBorder(
868: getBorder(), insetBorder));
869: }
870:
871: // Configures the rich text or component value
872: userObject = graph.getModel().getValue(view.getCell());
873: if (userObject instanceof JGraphpadBusinessObject) {
874: JGraphpadBusinessObject obj = (JGraphpadBusinessObject) userObject;
875: isRichText = obj.isRichText();
876: valueComponent = (obj.isComponent()) ? (Component) obj
877: .getValue() : null;
878: } else {
879: isRichText = false;
880: valueComponent = null;
881: }
882:
883: // Configures the rich text box for rendering the rich text
884: if (isRichText) {
885: StyledDocument document = (StyledDocument) textPane
886: .getDocument();
887: ((JGraphpadRichTextValue) ((JGraphpadBusinessObject) userObject)
888: .getValue()).insertInto(document);
889:
890: // Applies the inset to the rich text renderer
891: if (insetBorder != null)
892: textPane.setBorder(insetBorder);
893: else
894: textPane.setBorder(BorderFactory.createEmptyBorder(
895: INSET, INSET, INSET, INSET));
896:
897: // Uses the label's alignment and sets it on the text pane to work
898: // around the problem of the text pane alignments not being stored.
899: // Note: As a consequence a text pane can only have one alignment
900: // for all text it contains. It is not possible to align the
901: // paragraphs individually.
902: int align = getHorizontalAlignment();
903: SimpleAttributeSet sas = new SimpleAttributeSet();
904: align = (align == JLabel.CENTER) ? StyleConstants.ALIGN_CENTER
905: : (align == JLabel.RIGHT) ? StyleConstants.ALIGN_RIGHT
906: : StyleConstants.ALIGN_LEFT;
907: StyleConstants.setAlignment(sas, align);
908: document.setParagraphAttributes(0, document.getLength(),
909: sas, true);
910: }
911: }
912:
913: /**
914: * Detects whether or not a point has hit the folding icon. This
915: * implementation never returns true if the
916: * {@link #CLIENTPROPERTY_SHOWFOLDINGICONS} is not set on the enclosing
917: * graph.
918: *
919: * @param pt
920: * The point to check
921: * @return Returns true if <code>pt</code> intersects with the folding
922: * icon.
923: */
924: public boolean inHitRegion(Point2D pt) {
925: if (showFoldingIcons)
926: return handle.contains(Math.max(0, pt.getX() - 1), Math
927: .max(0, pt.getY() - 1));
928: return false;
929: }
930:
931: }
|