package edu.uci.ics.jung.visualization.control; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.geom.CubicCurve2D; import java.awt.geom.Point2D; import javax.swing.JComponent; import org.apache.commons.collections15.Factory; import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.graph.DirectedGraph; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.UndirectedGraph; import edu.uci.ics.jung.graph.util.EdgeType; import edu.uci.ics.jung.visualization.VisualizationServer; import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.util.ArrowFactory; /** * A plugin that can create vertices, undirected edges, and directed edges * using mouse gestures. * * @author Tom Nelson * */ public class EditingGraphMousePlugin<V,E> extends AbstractGraphMousePlugin implements MouseListener, MouseMotionListener { protected V startVertex; protected Point2D down; protected CubicCurve2D rawEdge = new CubicCurve2D.Float(); protected Shape edgeShape; protected Shape rawArrowShape; protected Shape arrowShape; protected VisualizationServer.Paintable edgePaintable; protected VisualizationServer.Paintable arrowPaintable; protected EdgeType edgeIsDirected; protected Factory<V> vertexFactory; protected Factory<E> edgeFactory; public EditingGraphMousePlugin(Factory<V> vertexFactory, Factory<E> edgeFactory) { this(MouseEvent.BUTTON1_MASK, vertexFactory, edgeFactory); } /** * create instance and prepare shapes for visual effects * @param modifiers */ public EditingGraphMousePlugin(int modifiers, Factory<V> vertexFactory, Factory<E> edgeFactory) { super(modifiers); this.vertexFactory = vertexFactory; this.edgeFactory = edgeFactory; rawEdge.setCurve(0.0f, 0.0f, 0.33f, 100, .66f, -50, 1.0f, 0.0f); rawArrowShape = ArrowFactory.getNotchedArrow(20, 16, 8); edgePaintable = new EdgePaintable(); arrowPaintable = new ArrowPaintable(); this.cursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); } /** * Overridden to be more flexible, and pass events with * key combinations. The default responds to both ButtonOne * and ButtonOne+Shift */ @Override public boolean checkModifiers(MouseEvent e) { return (e.getModifiers() & modifiers) != 0; } /** * If the mouse is pressed in an empty area, create a new vertex there. * If the mouse is pressed on an existing vertex, prepare to create * an edge from that vertex to another */ @SuppressWarnings("unchecked") public void mousePressed(MouseEvent e) { if(checkModifiers(e)) { final VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>)e.getSource(); final Point2D p = e.getPoint(); GraphElementAccessor<V,E> pickSupport = vv.getPickSupport(); if(pickSupport != null) { Graph<V,E> graph = vv.getModel().getGraphLayout().getGraph(); // set default edge type if(graph instanceof DirectedGraph) { edgeIsDirected = EdgeType.DIRECTED; } else { edgeIsDirected = EdgeType.UNDIRECTED; } final V vertex = pickSupport.getVertex(vv.getModel().getGraphLayout(), p.getX(), p.getY()); if(vertex != null) { // get ready to make an edge startVertex = vertex; down = e.getPoint(); transformEdgeShape(down, down); vv.addPostRenderPaintable(edgePaintable); if((e.getModifiers() & MouseEvent.SHIFT_MASK) != 0 && vv.getModel().getGraphLayout().getGraph() instanceof UndirectedGraph == false) { edgeIsDirected = EdgeType.DIRECTED; } if(edgeIsDirected == EdgeType.DIRECTED) { transformArrowShape(down, e.getPoint()); vv.addPostRenderPaintable(arrowPaintable); } } else { // make a new vertex V newVertex = vertexFactory.create(); Layout<V,E> layout = vv.getModel().getGraphLayout(); graph.addVertex(newVertex); layout.setLocation(newVertex, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(e.getPoint())); } } vv.repaint(); } } /** * If startVertex is non-null, and the mouse is released over an * existing vertex, create an undirected edge from startVertex to * the vertex under the mouse pointer. If shift was also pressed, * create a directed edge instead. */ @SuppressWarnings("unchecked") public void mouseReleased(MouseEvent e) { if(checkModifiers(e)) { final VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>)e.getSource(); final Point2D p = e.getPoint(); Layout<V,E> layout = vv.getModel().getGraphLayout(); GraphElementAccessor<V,E> pickSupport = vv.getPickSupport(); if(pickSupport != null) { final V vertex = pickSupport.getVertex(layout, p.getX(), p.getY()); if(vertex != null && startVertex != null) { Graph<V,E> graph = vv.getGraphLayout().getGraph(); graph.addEdge(edgeFactory.create(), startVertex, vertex, edgeIsDirected); vv.repaint(); } } startVertex = null; down = null; edgeIsDirected = EdgeType.UNDIRECTED; vv.removePostRenderPaintable(edgePaintable); vv.removePostRenderPaintable(arrowPaintable); } } /** * If startVertex is non-null, stretch an edge shape between * startVertex and the mouse pointer to simulate edge creation */ @SuppressWarnings("unchecked") public void mouseDragged(MouseEvent e) { if(checkModifiers(e)) { if(startVertex != null) { transformEdgeShape(down, e.getPoint()); if(edgeIsDirected == EdgeType.DIRECTED) { transformArrowShape(down, e.getPoint()); } } VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>)e.getSource(); vv.repaint(); } } /** * code lifted from PluggableRenderer to move an edge shape into an * arbitrary position */ private void transformEdgeShape(Point2D down, Point2D out) { float x1 = (float) down.getX(); float y1 = (float) down.getY(); float x2 = (float) out.getX(); float y2 = (float) out.getY(); AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1); float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); float dist = (float) Math.sqrt(dx*dx + dy*dy); xform.scale(dist / rawEdge.getBounds().getWidth(), 1.0); edgeShape = xform.createTransformedShape(rawEdge); } private void transformArrowShape(Point2D down, Point2D out) { float x1 = (float) down.getX(); float y1 = (float) down.getY(); float x2 = (float) out.getX(); float y2 = (float) out.getY(); AffineTransform xform = AffineTransform.getTranslateInstance(x2, y2); float dx = x2-x1; float dy = y2-y1; float thetaRadians = (float) Math.atan2(dy, dx); xform.rotate(thetaRadians); arrowShape = xform.createTransformedShape(rawArrowShape); } /** * Used for the edge creation visual effect during mouse drag */ class EdgePaintable implements VisualizationServer.Paintable { public void paint(Graphics g) { if(edgeShape != null) { Color oldColor = g.getColor(); g.setColor(Color.black); ((Graphics2D)g).draw(edgeShape); g.setColor(oldColor); } } public boolean useTransform() { return false; } } /** * Used for the directed edge creation visual effect during mouse drag */ class ArrowPaintable implements VisualizationServer.Paintable { public void paint(Graphics g) { if(arrowShape != null) { Color oldColor = g.getColor(); g.setColor(Color.black); ((Graphics2D)g).fill(arrowShape); g.setColor(oldColor); } } public boolean useTransform() { return false; } } public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(cursor); } public void mouseExited(MouseEvent e) { JComponent c = (JComponent)e.getSource(); c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public void mouseMoved(MouseEvent e) {} }