package de.unisiegen.gtitool.ui.jgraph; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JComponent; import javax.swing.ToolTipManager; import org.jgraph.JGraph; import org.jgraph.graph.DefaultGraphModel; import de.unisiegen.gtitool.core.entities.State; import de.unisiegen.gtitool.core.entities.Transition; import de.unisiegen.gtitool.ui.model.DefaultMachineModel; /** * Special {@link JGraph}. * * @author Benjamin Mies * @author Christian Fehler * @version $Id:GPCellViewFactory.java 910 2008-05-16 00:31:21Z fehler $ */ public class JGTIGraph extends JGraph implements Printable { /** * The loop {@link Transition} with 100 percent zzom factor. */ private static BufferedImage LOOP_TRANSITION_100 = null; /** * The loop {@link Transition} with 150 percent zzom factor. */ private static BufferedImage LOOP_TRANSITION_150 = null; /** * The loop {@link Transition} with 50 percent zzom factor. */ private static BufferedImage LOOP_TRANSITION_50 = null; /** * The serial version uid. */ private static final long serialVersionUID = 6157004880461808684L; static { try { LOOP_TRANSITION_50 = ImageIO .read ( JGTIGraph.class .getResource ( "/de/unisiegen/gtitool/ui/icon/jgraph/loop_transition_50.png" ) );//$NON-NLS-1$ LOOP_TRANSITION_100 = ImageIO .read ( JGTIGraph.class .getResource ( "/de/unisiegen/gtitool/ui/icon/jgraph/loop_transition_100.png" ) );//$NON-NLS-1$ LOOP_TRANSITION_150 = ImageIO .read ( JGTIGraph.class .getResource ( "/de/unisiegen/gtitool/ui/icon/jgraph/loop_transition_150.png" ) );//$NON-NLS-1$ } catch ( IOException exc ) { exc.printStackTrace (); System.exit ( 1 ); } } /** * The {@link DefaultMachineModel}. */ private DefaultMachineModel defaultMachineModel = null; /** * The end {@link Point} which is used to render the painted * {@link Transition}. */ private Point endPoint = null; /** * The height of the graph. */ private double graphHeight = 0; /** * The width of the graph. */ private double graphWidth = 0; /** * The bottom margin. */ private int marginBottom = 50; /** * The left margin. */ private int marginLeft = 50; /** * The right margin. */ private int marginRight = 50; /** * The top margin. */ private int marginTop = 50; /** * The page count of a row. */ private int pagesPerRow = 0; /** * The {@link StateView} which is used to render the painted * {@link Transition}. */ private StateView stateView = null; /** * Allocates a new {@link JGTIGraph}. * * @param defaultGraphModel The {@link DefaultGraphModel}. */ public JGTIGraph ( DefaultGraphModel defaultGraphModel ) { super ( defaultGraphModel ); ToolTipManager.sharedInstance ().registerComponent ( this ); } /** * Allocates a new {@link JGTIGraph}. * * @param defaultMachineModel The {@link DefaultMachineModel}. * @param defaultGraphModel The {@link DefaultGraphModel}. */ public JGTIGraph ( DefaultMachineModel defaultMachineModel, DefaultGraphModel defaultGraphModel ) { this ( defaultGraphModel ); this.defaultMachineModel = defaultMachineModel; } /** * {@inheritDoc} * * @see JGraph#getToolTipText(MouseEvent) */ @Override public String getToolTipText ( MouseEvent event ) { Object cell = getFirstCellForLocation ( event.getX (), event.getY () ); if ( cell instanceof DefaultStateView ) { DefaultStateView defaultStateView = ( DefaultStateView ) cell; State state = defaultStateView.getState (); // only if the short name is used if ( !state.isShortNameUsed () ) { return null; } // translate int x = event.getX () - ( int ) defaultStateView.getPositionX (); int y = event.getY () - ( int ) defaultStateView.getPositionY (); int width = ( int ) defaultStateView.getWidth (); int height = ( int ) defaultStateView.getHeight (); // clip x = x < 0 ? 0 : x; x = x > width ? width : x; y = y < 0 ? 0 : y; y = y > height ? height : y; // only if the mouse is over the state name int inset = 8; if ( ( x > inset ) && ( x < ( width - inset ) ) && ( y > ( ( height / 2 ) - inset ) ) && ( y < ( ( height / 2 ) + inset ) ) ) { return state.toPrettyString ().toHTMLString (); } } return null; } /** * Returns the used bounds. * * @return The used bounds. */ public final Rectangle getUsedBounds () { int minX = Integer.MAX_VALUE; int maxX = 0; int minY = Integer.MAX_VALUE; int maxY = 0; for ( Object object : DefaultGraphModel.getAll ( getModel () ) ) { if ( object instanceof DefaultStateView ) { DefaultStateView current = ( DefaultStateView ) object; int x = ( int ) current.getPositionX (); int y = ( int ) current.getPositionY (); int width = ( int ) current.getWidth (); int height = ( int ) current.getHeight (); minX = Math.min ( minX, x ); maxX = Math.max ( maxX, x + width ); minY = Math.min ( minY, y ); maxY = Math.max ( maxY, y + height ); } else if ( object instanceof DefaultTransitionView ) { DefaultTransitionView current = ( DefaultTransitionView ) object; Rectangle bounds = current.getTransition ().getLabelBounds (); int x = bounds.x; int y = bounds.y; int width = bounds.width; int height = bounds.height; minX = Math.min ( minX, x ); maxX = Math.max ( maxX, x + width ); minY = Math.min ( minY, y ); maxY = Math.max ( maxY, y + height ); } else if ( object instanceof DefaultNodeView ) { DefaultNodeView current = ( DefaultNodeView ) object; int x = current.getX (); int y = current.getY (); int width = current.getWidth (); int height = current.getHeight (); minX = Math.min ( minX, x ); maxX = Math.max ( maxX, x + width ); minY = Math.min ( minY, y ); maxY = Math.max ( maxY, y + height ); } } return new Rectangle ( minX, minY, maxX - minX, maxY - minY ); } /** * {@inheritDoc} * * @see JComponent#paintComponent(Graphics) */ @Override protected void paintComponent ( Graphics graphics ) { super.paintComponent ( graphics ); Graphics2D g = ( Graphics2D ) graphics; if ( ( this.stateView != null ) && ( this.endPoint != null ) ) { Point2D startPoint = this.stateView.getPerimeterPoint ( null, null, new Point ( ( int ) ( this.endPoint.x / this.scale ), ( int ) ( this.endPoint.y / this.scale ) ) ); int x1 = ( int ) startPoint.getX (); int y1 = ( int ) startPoint.getY (); int x2 = ( int ) ( this.endPoint.x / this.scale ); int y2 = ( int ) ( this.endPoint.y / this.scale ); Rectangle2D bounds = this.stateView.getBounds (); if ( bounds.contains ( x2, y2 ) && this.stateView.isSelectionAllowed ( this.endPoint.x, this.endPoint.y, this.scale ) ) { int x = ( int ) bounds.getX (); int y = ( int ) bounds.getY (); int width = ( int ) bounds.getWidth (); if ( this.stateView.getCell () instanceof DefaultStateView ) { State state = ( ( DefaultStateView ) this.stateView.getCell () ) .getState (); if ( state.isLoopTransition () ) { for ( Transition current : state.getTransitionBegin () ) { if ( current.getStateEnd () == state ) { current.setSelected ( true ); } } return; } if ( state.isStartState () ) { x += StateView.START_OFFSET; width -= StateView.START_OFFSET; } } if ( this.scale == 0.5 ) { x = ( int ) ( ( x * 0.5 ) + ( width * 0.5 ) / 2 - 6 ); y = ( int ) ( y * 0.5 - LOOP_TRANSITION_50.getHeight () + 1 ); g.drawImage ( LOOP_TRANSITION_50, x, y, null ); } else if ( this.scale == 1.0 ) { x = x + width / 2 - 12; y = y - LOOP_TRANSITION_100.getHeight () + 1; g.drawImage ( LOOP_TRANSITION_100, x, y, null ); } else if ( this.scale == 1.5 ) { x = ( int ) ( ( x * 1.5 ) + ( width * 1.5 ) / 2 - 18 ); y = ( int ) ( y * 1.5 - LOOP_TRANSITION_150.getHeight () + 1 ); g.drawImage ( LOOP_TRANSITION_150, x, y, null ); } else { throw new IllegalArgumentException ( "unsupported scale" ); //$NON-NLS-1$ } } else { if ( this.defaultMachineModel != null ) { for ( Transition current : this.defaultMachineModel.getMachine () .getTransition () ) { current.setSelected ( false ); } } g.scale ( this.scale, this.scale ); g.drawLine ( x1, y1, x2, y2 ); int dx = x2 - x1; int dy = y2 - y1; double lenght = Math.sqrt ( Math.pow ( dx, 2 ) + Math.pow ( dy, 2 ) ); if ( lenght > 0 ) { if ( ( dx >= 0 ) && ( dy >= 0 ) ) { double cos = dx / lenght; double acos = Math.acos ( cos ); g.rotate ( acos, x2, y2 ); } else if ( ( dx >= 0 ) && ( dy < 0 ) ) { double cos = -dx / lenght; double acos = Math.acos ( cos ); g.rotate ( acos - Math.PI, x2, y2 ); } else if ( ( dx < 0 ) && ( dy >= 0 ) ) { double cos = dx / lenght; double acos = Math.acos ( cos ); g.rotate ( acos, x2, y2 ); } else if ( ( dx < 0 ) && ( dy < 0 ) ) { double cos = -dx / lenght; double acos = Math.acos ( cos ); g.rotate ( acos - Math.PI, x2, y2 ); } } g.fillPolygon ( new int [] { x2 - 5, x2 - 5, x2 }, new int [] { y2 + 5, y2 - 5, y2 }, 3 ); } } } /** * {@inheritDoc} * * @see Printable#print(Graphics, PageFormat, int) */ public int print ( Graphics graphics, PageFormat pageFormat, int pageIndex ) { boolean print = false; int pageWidth = ( int ) pageFormat.getWidth (); int pageHeight = ( int ) pageFormat.getHeight (); if ( pageIndex == 0 ) { int width = pageWidth - this.marginRight; int height = pageHeight - this.marginBottom; graphics.setClip ( 0, 0, width, height ); } else { int width = pageWidth - this.marginRight - this.marginLeft; int height = pageHeight - this.marginBottom - this.marginTop; graphics.setClip ( this.marginLeft, this.marginTop, width, height ); } Rectangle rect = getUsedBounds (); this.graphWidth = rect.getWidth () + rect.x; this.graphHeight = rect.getHeight () + rect.y; int x = 0; int y = 0; if ( ( pageIndex * ( pageWidth - this.marginRight - this.marginLeft ) ) < this.graphWidth ) { x = - ( pageIndex * pageWidth ); x = x + ( pageIndex * this.marginLeft + this.marginRight * ( pageIndex + 1 ) ); graphics.translate ( x, 0 + this.marginTop ); print = true; this.pagesPerRow = pageIndex + 1; } else { int row = pageIndex / this.pagesPerRow; if ( ( row * pageHeight ) < this.graphHeight ) { x = - ( ( ( pageIndex - row * this.pagesPerRow ) ) * pageWidth ); y = - ( row * pageHeight ); x = x + ( ( pageIndex - row * this.pagesPerRow ) * this.marginLeft + this.marginRight * ( pageIndex - row * this.pagesPerRow + 1 ) ); y = y + ( row * this.marginTop + this.marginBottom * ( row + 1 ) ); graphics.translate ( x, y ); print = true; } } printAll ( graphics ); if ( print ) { return Printable.PAGE_EXISTS; } return Printable.NO_SUCH_PAGE; } /** * Resets the painted {@link Transition}. */ public final void resetPaintedTransition () { this.stateView = null; this.endPoint = null; repaint (); } /** * Sets the marginBottom. * * @param marginBottom The marginBottom to set. * @see #marginBottom */ public final void setMarginBottom ( int marginBottom ) { this.marginBottom = marginBottom; } /** * Sets the marginLeft. * * @param marginLeft The marginLeft to set. * @see #marginLeft */ public final void setMarginLeft ( int marginLeft ) { this.marginLeft = marginLeft; } /** * Sets the marginRight. * * @param marginRight The marginRight to set. * @see #marginRight */ public final void setMarginRight ( int marginRight ) { this.marginRight = marginRight; } /** * Sets the marginTop. * * @param marginTop The marginTop to set. * @see #marginTop */ public final void setMarginTop ( int marginTop ) { this.marginTop = marginTop; } /** * Sets the painted {@link Transition}. * * @param stateView The used {@link StateView}. * @param endPoint The end {@link Point}. */ public final void setPaintedTransition ( StateView stateView, Point endPoint ) { if ( stateView == null ) { throw new IllegalArgumentException ( "state view is null" ); //$NON-NLS-1$ } this.stateView = stateView; if ( endPoint == null ) { throw new IllegalArgumentException ( "end point is null" ); //$NON-NLS-1$ } this.endPoint = endPoint; repaint (); } /** * {@inheritDoc} * * @see JGraph#updateUI() */ @Override public final void updateUI () { setUI ( new JGTIBasicGraphUI () ); invalidate (); } }