/* * $Id$ * Copyright (c) * 2001-2007 Gaudenz Alder See LICENSE file in distribution for licensing * details of this source file */ // MODIFYBEGIN package de.unisiegen.gtitool.ui.jgraph; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.Map; import javax.swing.UIManager; import org.jgraph.JGraph; import org.jgraph.graph.CellView; import org.jgraph.graph.CellViewRenderer; import org.jgraph.graph.Edge; import org.jgraph.graph.EdgeView; import org.jgraph.graph.GraphConstants; import org.jgraph.util.Bezier; import org.jgraph.util.Spline2D; import de.unisiegen.gtitool.core.entities.Transition; import de.unisiegen.gtitool.core.parser.style.PrettyString; import de.unisiegen.gtitool.core.parser.style.PrettyToken; import de.unisiegen.gtitool.core.preferences.listener.ColorChangedAdapter; import de.unisiegen.gtitool.ui.preferences.PreferenceManager; /** * This renderer displays entries that implement the CellView interface. * * @version 1.0 1/1/02 * @author Gaudenz Alder */ @SuppressWarnings ( "all" ) public class EdgeRenderer extends org.jgraph.graph.EdgeRenderer implements CellViewRenderer, Serializable { // MODIFYEND /** Static Graphics used for Font Metrics */ protected static transient Graphics fontGraphics; /** * When zooming a graph the font size jumps at certain zoom levels rather than * scaling smoothly. Sometimes the zoom on the font is more than the component * zoom and cropping occurs. This buffer allows for the maximum occurance of * this */ public static double LABELWIDTHBUFFER = 1.1; // Headless environment does not allow graphics static { try { fontGraphics = new BufferedImage ( 1, 1, BufferedImage.TYPE_INT_RGB ) .getGraphics (); } catch ( Error e ) { // No font graphics fontGraphics = null; } } /** A switch for painting the extra labels */ public boolean simpleExtraLabels = true; /** Override this if you want the extra labels to appear in a special fontJ */ public Font extraLabelFont = null; /** Reference to the font metrics of the above */ protected transient FontMetrics metrics; /** Cache the current graph for drawing */ protected transient WeakReference graph; /** Cache the current edgeview for drawing */ protected transient EdgeView view; /** Painting attributes of the current edgeview */ protected transient int beginDeco, endDeco, beginSize, endSize, lineStyle; /** Width of the current edge view */ protected transient float lineWidth; /** * Boolean attributes of the current edgeview. Fill flags are checked for * valid decorations. */ protected transient boolean labelBorder, beginFill, endFill, focus, selected, preview, opaque, childrenSelected, labelTransformEnabled, isMoveBelowZero; /** * Color attributes of the current edgeview. This components foreground is set * to the edgecolor, the fontColor is in an extra variable. If the fontColor * is null, the current foreground is used. The default background instead is * used for text and is not visible if the label is not visible or if opaque * is true. */ protected transient Color borderColor, defaultForeground, defaultBackground, fontColor; /** Contains the current dash pattern. Null means no pattern. */ protected transient float [] lineDash; /** Contains the current dash offset. Null means no offset. */ protected transient float dashOffset = 0.0f; /** The gradient color of the edge */ protected transient Color gradientColor = null; /** The color of the graph grid */ protected transient Color gridColor = null; /** The color of the second available handle */ protected transient Color lockedHandleColor = null; /** The color of highlighted cells */ protected transient Color highlightColor = null; /** Cached bezier curve */ protected transient Bezier bezier; /** Cached spline curve */ protected transient Spline2D spline; // MODIFYBEGIN /** * The error {@link Transition} color. */ protected Color preferenceTransitionError; /** * The active {@link Transition} color. */ protected Color preferenceTransitionActive; /** * The selected {@link Transition} color. */ protected Color preferenceTransitionSelected; /** * The normal {@link Transition} color. */ protected Color preferenceTransition; // MODIFYEND /** * Constructs a renderer that may be used to render edges. */ public EdgeRenderer () { this.defaultForeground = UIManager.getColor ( "Tree.textForeground" ); this.defaultBackground = UIManager.getColor ( "Tree.textBackground" ); // MODIFYBEGIN this.preferenceTransition = PreferenceManager.getInstance () .getColorItemTransition ().getColor (); this.preferenceTransitionError = PreferenceManager.getInstance () .getColorItemTransitionError ().getColor (); this.preferenceTransitionActive = PreferenceManager.getInstance () .getColorItemTransitionActive ().getColor (); this.preferenceTransitionSelected = PreferenceManager.getInstance () .getColorItemTransitionSelected ().getColor (); PreferenceManager.getInstance ().addColorChangedListener ( new ColorChangedAdapter () { public void colorChangedTransition ( Color newColor ) { EdgeRenderer.this.preferenceTransition = newColor; } public void colorChangedTransitionActive ( Color newColor ) { EdgeRenderer.this.preferenceTransitionActive = newColor; } public void colorChangedTransitionError ( Color newColor ) { EdgeRenderer.this.preferenceTransitionError = newColor; } public void colorChangedTransitionSelected ( Color newColor ) { EdgeRenderer.this.preferenceTransitionSelected = newColor; } } ); // MODIFYEND } /** * Paint the current view's direction. Sets tmpPoint as a side-effect such * that the invoking method can use it to determine the connection point to * this decoration. */ protected Shape createLineEnd ( int size, int style, Point2D src, Point2D dst ) { if ( ( src == null ) || ( dst == null ) ) { return null; } int d = ( int ) Math.max ( 1, dst.distance ( src ) ); int ax = ( int ) - ( size * ( dst.getX () - src.getX () ) / d ); int ay = ( int ) - ( size * ( dst.getY () - src.getY () ) / d ); if ( style == GraphConstants.ARROW_DIAMOND ) { Polygon poly = new Polygon (); poly.addPoint ( ( int ) dst.getX (), ( int ) dst.getY () ); poly.addPoint ( ( int ) ( dst.getX () + ax / 2 + ay / 3 ), ( int ) ( dst .getY () + ay / 2 - ax / 3 ) ); Point2D last = ( Point2D ) dst.clone (); dst.setLocation ( dst.getX () + ax, dst.getY () + ay ); poly.addPoint ( ( int ) dst.getX (), ( int ) dst.getY () ); poly.addPoint ( ( int ) ( last.getX () + ax / 2 - ay / 3 ), ( int ) ( last.getY () + ay / 2 + ax / 3 ) ); return poly; } else if ( ( style == GraphConstants.ARROW_TECHNICAL ) || ( style == GraphConstants.ARROW_CLASSIC ) ) { Polygon poly = new Polygon (); poly.addPoint ( ( int ) dst.getX (), ( int ) dst.getY () ); poly.addPoint ( ( int ) ( dst.getX () + ax + ay / 2 ), ( int ) ( dst .getY () + ay - ax / 2 ) ); Point2D last = ( Point2D ) dst.clone (); if ( style == GraphConstants.ARROW_CLASSIC ) { dst.setLocation ( ( int ) ( dst.getX () + ax * 2 / 3 ), ( int ) ( dst .getY () + ay * 2 / 3 ) ); poly.addPoint ( ( int ) dst.getX (), ( int ) dst.getY () ); } else if ( style == GraphConstants.ARROW_DIAMOND ) { dst.setLocation ( dst.getX () + 2 * ax, dst.getY () + 2 * ay ); poly.addPoint ( ( int ) dst.getX (), ( int ) dst.getY () ); } else { dst.setLocation ( ( int ) ( dst.getX () + ax ), ( int ) ( dst.getY () + ay ) ); } poly.addPoint ( ( int ) ( last.getX () + ax - ay / 2 ), ( int ) ( last .getY () + ay + ax / 2 ) ); return poly; } else if ( style == GraphConstants.ARROW_SIMPLE ) { GeneralPath path = new GeneralPath ( GeneralPath.WIND_NON_ZERO, 4 ); path.moveTo ( ( float ) ( dst.getX () + ax + ay / 2 ), ( float ) ( dst .getY () + ay - ax / 2 ) ); path.lineTo ( ( float ) dst.getX (), ( float ) dst.getY () ); path.lineTo ( ( float ) ( dst.getX () + ax - ay / 2 ), ( float ) ( dst .getY () + ay + ax / 2 ) ); return path; } else if ( style == GraphConstants.ARROW_CIRCLE ) { Ellipse2D ellipse = new Ellipse2D.Float ( ( float ) ( dst.getX () + ax / 2 - size / 2 ), ( float ) ( dst.getY () + ay / 2 - size / 2 ), size, size ); dst.setLocation ( dst.getX () + ax, dst.getY () + ay ); return ellipse; } else if ( ( style == GraphConstants.ARROW_LINE ) || ( style == GraphConstants.ARROW_DOUBLELINE ) ) { GeneralPath path = new GeneralPath ( GeneralPath.WIND_NON_ZERO, 4 ); path.moveTo ( ( float ) ( dst.getX () + ax / 2 + ay / 2 ), ( float ) ( dst.getY () + ay / 2 - ax / 2 ) ); path.lineTo ( ( float ) ( dst.getX () + ax / 2 - ay / 2 ), ( float ) ( dst.getY () + ay / 2 + ax / 2 ) ); if ( style == GraphConstants.ARROW_DOUBLELINE ) { path.moveTo ( ( float ) ( dst.getX () + ax / 3 + ay / 2 ), ( float ) ( dst.getY () + ay / 3 - ax / 2 ) ); path.lineTo ( ( float ) ( dst.getX () + ax / 3 - ay / 2 ), ( float ) ( dst.getY () + ay / 3 + ax / 2 ) ); } return path; } return null; } /** * Returns the shape that represents the current edge in the context of the * current graph. This method sets the global beginShape, lineShape and * endShape variables as a side-effect. */ protected Shape createShape () { int n = this.view.getPointCount (); if ( n > 1 ) { // Following block may modify static vars as side effect (Flyweight // Design) EdgeView tmp = this.view; Point2D [] p = null; p = new Point2D [ n ]; for ( int i = 0 ; i < n ; i++ ) { Point2D pt = tmp.getPoint ( i ); if ( pt == null ) { return null; // exit } p [ i ] = new Point2D.Double ( pt.getX (), pt.getY () ); } // End of Side-Effect Block // Undo Possible MT-Side Effects if ( this.view != tmp ) { this.view = tmp; installAttributes ( this.view ); } // End of Undo if ( this.view.sharedPath == null ) { this.view.sharedPath = new GeneralPath ( GeneralPath.WIND_NON_ZERO, n ); } else { this.view.sharedPath.reset (); } this.view.beginShape = this.view.lineShape = this.view.endShape = null; Point2D p0 = p [ 0 ]; Point2D pe = p [ n - 1 ]; Point2D p1 = p [ 1 ]; Point2D p2 = p [ n - 2 ]; if ( ( this.lineStyle == GraphConstants.STYLE_BEZIER ) && ( n > 2 ) ) { this.bezier = new Bezier ( p ); p2 = this.bezier.getPoint ( this.bezier.getPointCount () - 1 ); } else if ( ( this.lineStyle == GraphConstants.STYLE_SPLINE ) && ( n > 2 ) ) { this.spline = new Spline2D ( p ); double [] point = this.spline.getPoint ( 0.9875 ); // Extrapolate p2 away from the end point, pe, to avoid integer // rounding errors becoming too large when creating the line end double scaledX = pe.getX () - ( ( pe.getX () - point [ 0 ] ) * 128 ); double scaledY = pe.getY () - ( ( pe.getY () - point [ 1 ] ) * 128 ); p2.setLocation ( scaledX, scaledY ); } if ( this.beginDeco != GraphConstants.ARROW_NONE ) { this.view.beginShape = createLineEnd ( this.beginSize, this.beginDeco, p1, p0 ); } if ( this.endDeco != GraphConstants.ARROW_NONE ) { this.view.endShape = createLineEnd ( this.endSize, this.endDeco, p2, pe ); } this.view.sharedPath.moveTo ( ( float ) p0.getX (), ( float ) p0.getY () ); /* THIS CODE WAS ADDED BY MARTIN KRUEGER 10/20/2003 */ if ( ( this.lineStyle == GraphConstants.STYLE_BEZIER ) && ( n > 2 ) ) { Point2D [] b = this.bezier.getPoints (); this.view.sharedPath.quadTo ( ( float ) b [ 0 ].getX (), ( float ) b [ 0 ].getY (), ( float ) p1.getX (), ( float ) p1 .getY () ); for ( int i = 2 ; i < n - 1 ; i++ ) { Point2D b0 = b [ 2 * i - 3 ]; Point2D b1 = b [ 2 * i - 2 ]; this.view.sharedPath.curveTo ( ( float ) b0.getX (), ( float ) b0 .getY (), ( float ) b1.getX (), ( float ) b1.getY (), ( float ) p [ i ].getX (), ( float ) p [ i ].getY () ); } this.view.sharedPath.quadTo ( ( float ) b [ b.length - 1 ].getX (), ( float ) b [ b.length - 1 ].getY (), ( float ) p [ n - 1 ].getX (), ( float ) p [ n - 1 ].getY () ); } else if ( ( this.lineStyle == GraphConstants.STYLE_SPLINE ) && ( n > 2 ) ) { for ( double t = 0 ; t <= 1 ; t += 0.0125 ) { double [] xy = this.spline.getPoint ( t ); this.view.sharedPath.lineTo ( ( float ) xy [ 0 ], ( float ) xy [ 1 ] ); } } /* END */ else { for ( int i = 1 ; i < n - 1 ; i++ ) { this.view.sharedPath.lineTo ( ( float ) p [ i ].getX (), ( float ) p [ i ].getY () ); } this.view.sharedPath.lineTo ( ( float ) pe.getX (), ( float ) pe .getY () ); } this.view.sharedPath.moveTo ( ( float ) pe.getX (), ( float ) pe.getY () ); if ( ( this.view.endShape == null ) && ( this.view.beginShape == null ) ) { // With no end decorations the line shape is the same as the // shared path and memory this.view.lineShape = this.view.sharedPath; } else { this.view.lineShape = ( GeneralPath ) this.view.sharedPath.clone (); if ( this.view.endShape != null ) { this.view.sharedPath.append ( this.view.endShape, true ); } if ( this.view.beginShape != null ) { this.view.sharedPath.append ( this.view.beginShape, true ); } } return this.view.sharedPath; } return null; } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, boolean oldValue, boolean newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, byte oldValue, byte newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, char oldValue, char newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, double oldValue, double newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, float oldValue, float newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, int oldValue, int newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, long oldValue, long newValue ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ protected void firePropertyChange ( String propertyName, Object oldValue, Object newValue ) { // Strings get interned... if ( propertyName == "text" ) { super.firePropertyChange ( propertyName, oldValue, newValue ); } } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void firePropertyChange ( String propertyName, short oldValue, short newValue ) { } /** * Returns the bounds of the edge shape. */ public Rectangle2D getBounds ( CellView value ) { if ( ( value instanceof EdgeView ) && ( value != null ) ) { // No need to call setView as getPaintBounds will this.view = ( EdgeView ) value; Rectangle2D r = getPaintBounds ( this.view ); JGraph graph = null; if ( this.graph != null ) { graph = ( JGraph ) this.graph.get (); } Rectangle2D rect = getLabelBounds ( graph, this.view ); if ( rect != null ) { Rectangle2D.union ( r, rect, r ); } Object [] labels = GraphConstants.getExtraLabels ( this.view .getAllAttributes () ); if ( labels != null ) { for ( int i = 0 ; i < labels.length ; i++ ) { rect = getExtraLabelBounds ( graph, this.view, i ); if ( rect != null ) { Rectangle2D.union ( r, rect, r ); } } } int b = ( int ) Math.ceil ( this.lineWidth ); r.setFrame ( r.getX () - b, r.getY () - b, r.getWidth () + 2 * b, r .getHeight () + 2 * b ); return r; } return null; } /** * Returns the label bounds of the specified view in the given graph. Note: * The index is the position of the String object for the label in the extra * labels array of the view. */ public Rectangle2D getExtraLabelBounds ( JGraph paintingContext, EdgeView view, int index ) { if ( ( paintingContext == null ) && ( this.graph != null ) ) { JGraph graph = ( JGraph ) this.graph.get (); paintingContext = graph; } setView ( view ); Object [] labels = GraphConstants .getExtraLabels ( view.getAllAttributes () ); if ( ( labels != null ) && ( index < labels.length ) ) { Point2D p = getExtraLabelPosition ( this.view, index ); Dimension d = getExtraLabelSize ( paintingContext, this.view, index ); String label = ( paintingContext != null ) ? paintingContext .convertValueToString ( labels [ index ] ) : String .valueOf ( labels [ index ] ); return getLabelBounds ( p, d, label ); } return new Rectangle2D.Double ( getX (), getY (), 0, 0 ); } /** * Returns the label position of the specified view in the given graph. */ public Point2D getExtraLabelPosition ( EdgeView view, int index ) { setView ( view ); Point2D [] pts = GraphConstants.getExtraLabelPositions ( view .getAllAttributes () ); if ( ( pts != null ) && ( index < pts.length ) ) { return getLabelPosition ( pts [ index ] ); } return null; } /** * Returns the label size of the specified view in the given graph. */ public Dimension getExtraLabelSize ( JGraph paintingContext, EdgeView view, int index ) { Object [] labels = GraphConstants .getExtraLabels ( view.getAllAttributes () ); if ( ( labels != null ) && ( index < labels.length ) ) { String label = ( paintingContext != null ) ? paintingContext .convertValueToString ( labels [ index ] ) : String .valueOf ( labels [ index ] ); return getLabelSize ( view, label ); } return null; } /** * @return Returns the gradientColor. */ public Color getGradientColor () { return this.gradientColor; } /** * Calculates the angle at which graphics should be rotated to paint label * along the edge. Before calling this method always check that transform * should be applied using {@linkisLabelTransform} * * @return the value of the angle, 0 if the angle is zero or can't be * calculated */ private double getLabelAngle ( String label ) { Point2D p = getLabelPosition ( this.view ); double angle = 0; if ( ( p != null ) && ( label != null ) && ( label.length () > 0 ) ) { int sw = this.metrics.stringWidth ( label ); // Note: For control points you may want to choose other // points depending on the segment the label is in. Point2D p1 = this.view.getPoint ( 0 ); Point2D p2 = this.view.getPoint ( this.view.getPointCount () - 1 ); // Length of the edge double length = Math.sqrt ( ( p2.getX () - p1.getX () ) * ( p2.getX () - p1.getX () ) + ( p2.getY () - p1.getY () ) * ( p2.getY () - p1.getY () ) ); if ( ! ( ( length <= Double.NaN ) || ( length < sw ) ) ) { // Label fits into // edge's length // To calculate projections of edge double cos = ( p2.getX () - p1.getX () ) / length; double sin = ( p2.getY () - p1.getY () ) / length; // Determine angle angle = Math.acos ( cos ); if ( sin < 0 ) { // Second half angle = 2 * Math.PI - angle; } } if ( ( angle > Math.PI / 2 ) && ( angle <= Math.PI * 3 / 2 ) ) { angle -= Math.PI; } } return angle; } /** * Returns the label bounds of the specified view in the given graph. */ public Rectangle2D getLabelBounds ( JGraph paintingContext, EdgeView view ) { if ( ( paintingContext == null ) && ( this.graph != null ) ) { JGraph graph = ( JGraph ) this.graph.get (); paintingContext = graph; } // No need to call setView as getLabelPosition will String label = ( paintingContext != null ) ? paintingContext .convertValueToString ( view ) : String.valueOf ( view.getCell () ); if ( label != null ) { Point2D p = getLabelPosition ( view ); Dimension d = getLabelSize ( view, label ); return getLabelBounds ( p, d, label ); } else { return null; } } /** * Returns the label bounds of the specified view in the given graph. */ public Rectangle2D getLabelBounds ( Point2D p, Dimension d, String label ) { if ( ( label != null ) && isLabelTransform ( label ) ) { // With transform label is rotated, so we should // rotate the rectangle (sw, sh) and return the // bounding rectangle double angle = getLabelAngle ( label ); if ( angle < 0 ) { angle = -angle; } if ( angle > Math.PI / 2 ) { angle %= Math.PI / 2; } double yside = Math.abs ( Math.cos ( angle ) * d.height + Math.sin ( angle ) * d.width ); double xside = Math.abs ( d.width * Math.cos ( angle ) + d.height * Math.sin ( angle ) ); // Getting maximum is not good, but I don't want to be // drown in calculations if ( xside > yside ) { yside = xside; } if ( yside > xside ) { xside = yside; } angle = getLabelAngle ( label ); // Increasing by height is safe, but I think the precise // value is font.descent layed on edge vector and // projected on each axis d.width = ( int ) xside + d.height; d.height = ( int ) yside + d.height; } if ( ( p != null ) && ( d != null ) ) { double x = Math.max ( 0, p.getX () - ( d.width / 2 ) ); double y = Math.max ( 0, p.getY () - ( d.height / 2 ) ); return new Rectangle2D.Double ( x, y, d.width + 1, d.height + 1 ); } return null; } /** * Returns the label position of the specified view in the given graph. */ public Point2D getLabelPosition ( EdgeView view ) { setView ( view ); return getLabelPosition ( view.getLabelPosition () ); } /** * Returns the label position of the specified view in the given graph. */ protected Point2D getLabelPosition ( Point2D pos ) { Rectangle2D tmp = getPaintBounds ( this.view ); int unit = GraphConstants.PERMILLE; Point2D p0 = this.view.getPoint ( 0 ); if ( ( pos != null ) && ( tmp != null ) && ( p0 != null ) ) { if ( !isLabelTransformEnabled () ) { // MODIFYBEGIN Point2D result = this.view.getAbsoluteLabelPositionFromRelative ( pos ); result.setLocation ( result.getX (), result.getY () - 8 ); return result; // MODIFYEND } else { Point2D vector = this.view.getLabelVector (); double dx = vector.getX (); double dy = vector.getY (); double len = Math.sqrt ( dx * dx + dy * dy ); if ( len > 0 ) { double x = p0.getX () + ( dx * pos.getX () / unit ); double y = p0.getY () + ( dy * pos.getX () / unit ); x += ( -dy * pos.getY () / len ); y += ( dx * pos.getY () / len ); return new Point2D.Double ( x, y ); } else { return new Point2D.Double ( p0.getX () + pos.getX (), p0.getY () + pos.getY () ); } } } return null; } /** * Returns the label size of the specified view in the given graph. */ public Dimension getLabelSize ( EdgeView view, String label ) { if ( ( label != null ) && ( fontGraphics != null ) ) { fontGraphics .setFont ( GraphConstants.getFont ( view.getAllAttributes () ) ); this.metrics = fontGraphics.getFontMetrics (); int sw = ( int ) ( this.metrics.stringWidth ( label ) * LABELWIDTHBUFFER ); int sh = this.metrics.getHeight (); return new Dimension ( sw, sh ); } return null; } /** * Returns the bounds of the edge shape without label */ public Rectangle2D getPaintBounds ( EdgeView view ) { Rectangle2D rec = null; setView ( view ); if ( view.getShape () != null ) { rec = view.getShape ().getBounds (); } else { rec = new Rectangle2D.Double ( 0, 0, 0, 0 ); } return rec; } /** * Configure and return the renderer based on the passed in components. The * value is typically set from messaging the graph with * <code>convertValueToString</code>. * * @param graph the graph that that defines the rendering context. * @param view the cell view that should be rendered. * @param sel whether the object is selected. * @param focus whether the object has the focus. * @param preview whether we are drawing a preview. * @return the component used to render the value. */ public Component getRendererComponent ( JGraph graph, CellView view, boolean sel, boolean focus, boolean preview ) { if ( ( view instanceof EdgeView ) && ( graph != null ) ) { this.gridColor = graph.getGridColor (); this.lockedHandleColor = graph.getLockedHandleColor (); this.highlightColor = graph.getHighlightColor (); this.isMoveBelowZero = graph.isMoveBelowZero (); this.graph = new WeakReference ( graph ); this.focus = focus; this.selected = sel; this.preview = preview; this.childrenSelected = graph.getSelectionModel ().isChildrenSelected ( view.getCell () ); setView ( view ); return this; } return null; } /** * Installs the attributes of specified cell in this renderer instance. This * means, retrieve every published key from the cells hashtable and set global * variables or superclass properties accordingly. * * @param view the cell view to retrieve the attribute values from. */ protected void installAttributes ( CellView view ) { Map map = view.getAllAttributes (); this.beginDeco = GraphConstants.getLineBegin ( map ); this.beginSize = GraphConstants.getBeginSize ( map ); this.beginFill = GraphConstants.isBeginFill ( map ) && isFillable ( this.beginDeco ); this.endDeco = GraphConstants.getLineEnd ( map ); this.endSize = GraphConstants.getEndSize ( map ); this.endFill = GraphConstants.isEndFill ( map ) && isFillable ( this.endDeco ); this.lineWidth = GraphConstants.getLineWidth ( map ); Edge.Routing routing = GraphConstants.getRouting ( map ); this.lineStyle = ( ( routing != null ) && ( view instanceof EdgeView ) ) ? routing .getPreferredLineStyle ( ( EdgeView ) view ) : Edge.Routing.NO_PREFERENCE; if ( this.lineStyle == Edge.Routing.NO_PREFERENCE ) { this.lineStyle = GraphConstants.getLineStyle ( map ); } this.lineDash = GraphConstants.getDashPattern ( map ); this.dashOffset = GraphConstants.getDashOffset ( map ); this.borderColor = GraphConstants.getBorderColor ( map ); Color foreground = GraphConstants.getLineColor ( map ); setForeground ( ( foreground != null ) ? foreground : this.defaultForeground ); Color background = GraphConstants.getBackground ( map ); setBackground ( ( background != null ) ? background : this.defaultBackground ); Color gradientColor = GraphConstants.getGradientColor ( map ); setGradientColor ( gradientColor ); setOpaque ( GraphConstants.isOpaque ( map ) ); setFont ( GraphConstants.getFont ( map ) ); Color tmp = GraphConstants.getForeground ( map ); this.fontColor = ( tmp != null ) ? tmp : getForeground (); this.labelTransformEnabled = GraphConstants.isLabelAlongEdge ( map ); } /** * Returns true if the edge shape intersects the given rectangle. */ public boolean intersects ( JGraph graph, CellView value, Rectangle rect ) { if ( ( value instanceof EdgeView ) && ( graph != null ) && ( value != null ) ) { setView ( value ); // If we have two control points, we can get rid of hit // detection on do an intersection test on the two diagonals // of rect and the line between the two points EdgeView edgeView = ( EdgeView ) value; if ( edgeView.getPointCount () == 2 ) { Point2D p0 = edgeView.getPoint ( 0 ); Point2D p1 = edgeView.getPoint ( 1 ); if ( rect.intersectsLine ( p0.getX (), p0.getY (), p1.getX (), p1 .getY () ) ) { return true; } } else { Graphics2D g2 = ( Graphics2D ) graph.getGraphics (); if ( g2.hit ( rect, this.view.getShape (), true ) ) { return true; } } Rectangle2D r = getLabelBounds ( graph, this.view ); if ( ( r != null ) && r.intersects ( rect ) ) { return true; } Object [] labels = GraphConstants.getExtraLabels ( this.view .getAllAttributes () ); if ( labels != null ) { for ( int i = 0 ; i < labels.length ; i++ ) { r = getExtraLabelBounds ( graph, this.view, i ); if ( ( r != null ) && r.intersects ( rect ) ) { return true; } } } } return false; } protected boolean isFillable ( int decoration ) { return ! ( ( decoration == GraphConstants.ARROW_SIMPLE ) || ( decoration == GraphConstants.ARROW_LINE ) || ( decoration == GraphConstants.ARROW_DOUBLELINE ) ); } /** * Estimates whether the transform for label should be applied. With the * transform, the label will be painted along the edge. To apply transform, * rotate graphics by the angle returned from {@link #getLabelAngle} * * @return true, if transform can be applied, false otherwise */ private boolean isLabelTransform ( String label ) { if ( !isLabelTransformEnabled () ) { return false; } Point2D p = getLabelPosition ( this.view ); if ( ( p != null ) && ( label != null ) && ( label.length () > 0 ) ) { int sw = this.metrics.stringWidth ( label ); Point2D p1 = this.view.getPoint ( 0 ); Point2D p2 = this.view.getPoint ( this.view.getPointCount () - 1 ); double length = Math.sqrt ( ( p2.getX () - p1.getX () ) * ( p2.getX () - p1.getX () ) + ( p2.getY () - p1.getY () ) * ( p2.getY () - p1.getY () ) ); if ( ! ( ( length <= Double.NaN ) || ( length < sw ) ) ) { return true; } } return false; } private boolean isLabelTransformEnabled () { return this.labelTransformEnabled; } /** * Paint the renderer. */ public void paint ( Graphics g ) { if ( this.view.isLeaf () ) { Shape edgeShape = this.view.getShape (); // Sideeffect: beginShape, lineShape, endShape if ( edgeShape != null ) { Graphics2D g2 = ( Graphics2D ) g; g2.setRenderingHint ( RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE ); int c = BasicStroke.CAP_BUTT; int j = BasicStroke.JOIN_MITER; setOpaque ( false ); super.paint ( g ); translateGraphics ( g ); g.setColor ( getForeground () ); // MODIFYBEGIN // Error if ( ( this.view.getCell () instanceof DefaultTransitionView ) && ( ( ( DefaultTransitionView ) this.view.getCell () ) .getTransition ().isError () ) ) { g2.setColor ( this.preferenceTransitionError ); } // Active else if ( ( this.view.getCell () instanceof DefaultTransitionView ) && ( ( ( DefaultTransitionView ) this.view.getCell () ) .getTransition ().isActive () ) ) { g2.setColor ( this.preferenceTransitionActive ); } // Selected else if ( this.selected || ( ( this.view.getCell () instanceof DefaultTransitionView ) && ( ( ( DefaultTransitionView ) this.view .getCell () ).getTransition ().isSelected () ) ) ) { g2.setColor ( this.preferenceTransitionSelected ); } // Normal else { g2.setColor ( this.preferenceTransition ); } // MODIFYEND if ( this.lineWidth > 0 ) { g2.setStroke ( new BasicStroke ( this.lineWidth, c, j ) ); if ( ( this.gradientColor != null ) && !this.preview ) { g2.setPaint ( new GradientPaint ( 0, 0, getBackground (), getWidth (), getHeight (), this.gradientColor, true ) ); } if ( this.view.beginShape != null ) { if ( this.beginFill ) { g2.fill ( this.view.beginShape ); } g2.draw ( this.view.beginShape ); } if ( this.view.endShape != null ) { if ( this.endFill ) { g2.fill ( this.view.endShape ); } g2.draw ( this.view.endShape ); } if ( this.lineDash != null ) { g2.setStroke ( new BasicStroke ( this.lineWidth, c, j, 10.0f, this.lineDash, this.dashOffset ) ); } if ( this.view.lineShape != null ) { g2.draw ( this.view.lineShape ); } } // MODIFYBEGIN // if (selected) { // Paint Selected // g2.setStroke(GraphConstants.SELECTION_STROKE); // g2.setColor(highlightColor); // if (view.beginShape != null) // g2.draw(view.beginShape); // if (view.lineShape != null) // g2.draw(view.lineShape); // if (view.endShape != null) // g2.draw(view.endShape); // } // MODIFYEND g2.setStroke ( new BasicStroke ( 1 ) ); g.setFont ( ( this.extraLabelFont != null ) ? this.extraLabelFont : getFont () ); Object [] labels = GraphConstants.getExtraLabels ( this.view .getAllAttributes () ); JGraph graph = ( JGraph ) this.graph.get (); if ( labels != null ) { // MODIFYBEGIN for ( int i = 0 ; i < labels.length ; i++ ) { if ( this.view.getCell () instanceof DefaultTransitionView ) { Transition transition = ( ( DefaultTransitionView ) this.view .getCell () ).getTransition (); paintTransition ( g, transition, getExtraLabelPosition ( this.view, i ), false || !this.simpleExtraLabels ); } } // MODIFYEND } if ( graph.getEditingCell () != this.view.getCell () ) { g.setFont ( getFont () ); Object label = graph.convertValueToString ( this.view ); if ( label != null ) { // MODIFYBEGIN if ( this.view.getCell () instanceof DefaultTransitionView ) { Transition transition = ( ( DefaultTransitionView ) this.view .getCell () ).getTransition (); paintTransition ( g, transition, getLabelPosition ( this.view ), true ); } // MODIFYEND } } } } else { paintSelectionBorder ( g ); } } /** * Paint the specified label for the current edgeview. */ protected void paintLabel ( Graphics g, String label, Point2D p, boolean mainLabel ) { if ( ( p != null ) && ( label != null ) && ( label.length () > 0 ) && ( this.metrics != null ) ) { int sw = this.metrics.stringWidth ( label ); int sh = this.metrics.getHeight (); Graphics2D g2 = ( Graphics2D ) g; boolean applyTransform = isLabelTransform ( label ); double angle = 0; int dx = -sw / 2; int offset = this.isMoveBelowZero || applyTransform ? 0 : Math.min ( 0, ( int ) ( dx + p.getX () ) ); g2.translate ( p.getX () - offset, p.getY () ); if ( applyTransform ) { angle = getLabelAngle ( label ); g2.rotate ( angle ); } if ( isOpaque () && mainLabel ) { g.setColor ( getBackground () ); g.fillRect ( -sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2 ); } if ( ( this.borderColor != null ) && mainLabel ) { g.setColor ( this.borderColor ); g.drawRect ( -sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2 ); } int dy = +sh / 4; g.setColor ( this.fontColor ); if ( applyTransform && ( this.borderColor == null ) && !isOpaque () ) { // Shift label perpendicularly by the descent so it // doesn't cross the line. dy = -this.metrics.getDescent (); } g.drawString ( label, dx, dy ); if ( applyTransform ) { // Undo the transform g2.rotate ( -angle ); } g2.translate ( -p.getX () + offset, -p.getY () ); } } /** * Provided for subclassers to paint a selection border. */ protected void paintSelectionBorder ( Graphics g ) { ( ( Graphics2D ) g ).setStroke ( GraphConstants.SELECTION_STROKE ); if ( this.childrenSelected ) { g.setColor ( this.gridColor ); } else if ( this.focus && this.selected ) { g.setColor ( this.lockedHandleColor ); } else if ( this.selected ) { g.setColor ( this.highlightColor ); } if ( this.childrenSelected || this.selected ) { Dimension d = getSize (); g.drawRect ( 0, 0, d.width - 1, d.height - 1 ); } } /** * Paint the specified {@link Transition} for the current edgeview. */ protected void paintTransition ( Graphics g, Transition transition, Point2D p, boolean mainLabel ) { PrettyString prettyString = transition.toPrettyString (); String string = prettyString.toString (); // do not print the labels if more than one transition if ( transition.getStateBegin () == transition.getStateEnd () ) { int count = 0; for ( Transition current : transition.getStateBegin () .getTransitionBegin () ) { if ( current.getStateEnd () == transition.getStateEnd () ) { count++ ; } } if ( count > 1 ) { prettyString = new PrettyString (); string = ""; } } if ( ( p != null ) && ( transition != null ) && ( string.length () > 0 ) && ( this.metrics != null ) ) { int sw = this.metrics.stringWidth ( string ); int sh = this.metrics.getHeight (); Graphics2D g2 = ( Graphics2D ) g; boolean applyTransform = isLabelTransform ( string ); double angle = 0; int dx = -sw / 2; int offset = this.isMoveBelowZero || applyTransform ? 0 : Math.min ( 0, ( int ) ( dx + p.getX () ) ); g2.translate ( p.getX () - offset, p.getY () ); if ( applyTransform ) { angle = getLabelAngle ( string ); g2.rotate ( angle ); } if ( isOpaque () && mainLabel ) { g.setColor ( getBackground () ); g.fillRect ( -sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2 ); } if ( ( this.borderColor != null ) && mainLabel ) { g.setColor ( this.borderColor ); g.drawRect ( -sw / 2 - 1, -sh / 2 - 1, sw + 2, sh + 2 ); } int dy = +sh / 4; g.setColor ( this.fontColor ); if ( applyTransform && ( this.borderColor == null ) && !isOpaque () ) { // Shift label perpendicularly by the descent so it // doesn't cross the line. dy = -this.metrics.getDescent (); } // MODIFYBEGIN for ( PrettyToken currentToken : prettyString ) { Font font = null; if ( !currentToken.isBold () && !currentToken.isItalic () ) { font = g.getFont ().deriveFont ( Font.PLAIN ); } else if ( currentToken.isBold () && currentToken.isItalic () ) { font = g.getFont ().deriveFont ( Font.BOLD | Font.ITALIC ); } else if ( currentToken.isBold () ) { font = g.getFont ().deriveFont ( Font.BOLD ); } else if ( currentToken.isItalic () ) { font = g.getFont ().deriveFont ( Font.ITALIC ); } g.setFont ( font ); g.setColor ( currentToken.getColor () ); char [] chars = currentToken.getChar (); for ( int i = 0 ; i < chars.length ; i++ ) { g.drawChars ( chars, i, 1, dx, dy ); dx += this.metrics.charWidth ( chars [ i ] ); } } transition.setLabelBounds ( new Rectangle ( ( int ) ( p.getX () + offset ) - sw / 2 - 1, ( int ) p.getY () - sh / 2 - 1, sw + 2, sh + 2 ) ); // MODIFYEND if ( applyTransform ) { // Undo the transform g2.rotate ( -angle ); } g2.translate ( -p.getX () + offset, -p.getY () ); } } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void repaint ( long tm, int x, int y, int width, int height ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void repaint ( Rectangle r ) { } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void revalidate () { } /** * @param gradientColor The gradientColor to set. */ public void setGradientColor ( Color gradientColor ) { this.gradientColor = gradientColor; } /** * Sets view to work with, caching necessary values until the next call of * this method or until some other methods with explicitly specified different * view */ void setView ( CellView value ) { if ( value instanceof EdgeView ) { this.view = ( EdgeView ) value; installAttributes ( this.view ); } else { this.view = null; } } // This if for subclassers that to not want the graphics // to be relative to the top, left corner of this component. // Note: Override this method with an empty implementation // if you want absolute positions for your edges protected void translateGraphics ( Graphics g ) { g.translate ( -getX (), -getY () ); } /** * Overridden for performance reasons. See the <a * href="#override">Implementation Note </a> for more information. */ public void validate () { } }