View Javadoc

1   /*
2    * Copyright (c) 2005, the JUNG Project and the Regents of the University 
3    * of California
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * http://jung.sourceforge.net/license.txt for a description.
9    * Created on Mar 8, 2005
10   *
11   */
12  package edu.uci.ics.jung.visualization.control;
13  
14  import java.awt.Color;
15  import java.awt.Cursor;
16  import java.awt.Graphics;
17  import java.awt.Graphics2D;
18  import java.awt.Point;
19  import java.awt.event.InputEvent;
20  import java.awt.event.MouseEvent;
21  import java.awt.event.MouseListener;
22  import java.awt.event.MouseMotionListener;
23  import java.awt.geom.Point2D;
24  import java.awt.geom.Rectangle2D;
25  import java.util.Collection;
26  
27  import javax.swing.JComponent;
28  
29  import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
30  import edu.uci.ics.jung.algorithms.layout.Layout;
31  import edu.uci.ics.jung.visualization.Layer;
32  import edu.uci.ics.jung.visualization.VisualizationViewer;
33  import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
34  import edu.uci.ics.jung.visualization.picking.PickedState;
35  
36  /** 
37   * PickingGraphMousePlugin supports the picking of graph elements
38   * with the mouse. MouseButtonOne picks a single vertex
39   * or edge, and MouseButtonTwo adds to the set of selected Vertices
40   * or EdgeType. If a Vertex is selected and the mouse is dragged while
41   * on the selected Vertex, then that Vertex will be repositioned to
42   * follow the mouse until the button is released.
43   * 
44   * @author Tom Nelson
45   */
46  public class PickingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
47      implements MouseListener, MouseMotionListener {
48  
49  	/**
50  	 * the picked Vertex, if any
51  	 */
52      protected V vertex;
53      
54      /**
55       * the picked Edge, if any
56       */
57      protected E edge;
58      
59      /**
60       * the x distance from the picked vertex center to the mouse point
61       */
62      protected double offsetx;
63      
64      /**
65       * the y distance from the picked vertex center to the mouse point
66       */
67      protected double offsety;
68      
69      /**
70       * controls whether the Vertices may be moved with the mouse
71       */
72      protected boolean locked;
73      
74      /**
75       * additional modifiers for the action of adding to an existing
76       * selection
77       */
78      protected int addToSelectionModifiers;
79      
80      /**
81       * used to draw a rectangle to contain picked vertices
82       */
83      protected Rectangle2D rect = new Rectangle2D.Float();
84      
85      /**
86       * the Paintable for the lens picking rectangle
87       */
88      protected Paintable lensPaintable;
89      
90      /**
91       * color for the picking rectangle
92       */
93      protected Color lensColor = Color.cyan;
94      
95      /**
96  	 * create an instance with default settings
97  	 */
98  	public PickingGraphMousePlugin() {
99  	    this(InputEvent.BUTTON1_MASK, InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK);
100 	}
101 
102 	/**
103 	 * create an instance with overides
104 	 * @param selectionModifiers for primary selection
105 	 * @param addToSelectionModifiers for additional selection
106 	 */
107     public PickingGraphMousePlugin(int selectionModifiers, int addToSelectionModifiers) {
108         super(selectionModifiers);
109         this.addToSelectionModifiers = addToSelectionModifiers;
110         this.lensPaintable = new LensPaintable();
111         this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
112     }
113     
114     /**
115      * @return Returns the lensColor.
116      */
117     public Color getLensColor() {
118         return lensColor;
119     }
120 
121     /**
122      * @param lensColor The lensColor to set.
123      */
124     public void setLensColor(Color lensColor) {
125         this.lensColor = lensColor;
126     }
127 
128     /**
129      * a Paintable to draw the rectangle used to pick multiple
130      * Vertices
131      * @author Tom Nelson
132      *
133      */
134     class LensPaintable implements Paintable {
135 
136         public void paint(Graphics g) {
137             Color oldColor = g.getColor();
138             g.setColor(lensColor);
139             ((Graphics2D)g).draw(rect);
140             g.setColor(oldColor);
141         }
142 
143         public boolean useTransform() {
144             return false;
145         }
146     }
147 
148 	/**
149 	 * For primary modifiers (default, MouseButton1):
150 	 * pick a single Vertex or Edge that
151      * is under the mouse pointer. If no Vertex or edge is under
152      * the pointer, unselect all picked Vertices and edges, and
153      * set up to draw a rectangle for multiple selection
154      * of contained Vertices.
155      * For additional selection (default Shift+MouseButton1):
156      * Add to the selection, a single Vertex or Edge that is
157      * under the mouse pointer. If a previously picked Vertex
158      * or Edge is under the pointer, it is un-picked.
159      * If no vertex or Edge is under the pointer, set up
160      * to draw a multiple selection rectangle (as above)
161      * but do not unpick previously picked elements.
162 	 * 
163 	 * @param e the event
164 	 */
165     @SuppressWarnings("unchecked")
166     public void mousePressed(MouseEvent e) {
167         down = e.getPoint();
168         VisualizationViewer<V,E> vv = (VisualizationViewer)e.getSource();
169         GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
170         PickedState<V> pickedVertexState = vv.getPickedVertexState();
171         PickedState<E> pickedEdgeState = vv.getPickedEdgeState();
172         if(pickSupport != null && pickedVertexState != null) {
173             Layout<V,E> layout = vv.getGraphLayout();
174             if(e.getModifiers() == modifiers) {
175                 rect.setFrameFromDiagonal(down,down);
176                 // p is the screen point for the mouse event
177                 Point2D ip = e.getPoint();
178 
179                 vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY());
180                 if(vertex != null) {
181                     if(pickedVertexState.isPicked(vertex) == false) {
182                     	pickedVertexState.clear();
183                     	pickedVertexState.pick(vertex, true);
184                     }
185                     // layout.getLocation applies the layout transformer so
186                     // q is transformed by the layout transformer only
187                     Point2D q = layout.transform(vertex);
188                     // transform the mouse point to graph coordinate system
189                     Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip);
190 
191                     offsetx = (float) (gp.getX()-q.getX());
192                     offsety = (float) (gp.getY()-q.getY());
193                 } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) {
194                     pickedEdgeState.clear();
195                     pickedEdgeState.pick(edge, true);
196                 } else {
197                     vv.addPostRenderPaintable(lensPaintable);
198                 	pickedEdgeState.clear();
199                     pickedVertexState.clear();
200                 }
201                 
202             } else if(e.getModifiers() == addToSelectionModifiers) {
203                 vv.addPostRenderPaintable(lensPaintable);
204                 rect.setFrameFromDiagonal(down,down);
205                 Point2D ip = e.getPoint();
206                 vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY());
207                 if(vertex != null) {
208                     boolean wasThere = pickedVertexState.pick(vertex, !pickedVertexState.isPicked(vertex));
209                     if(wasThere) {
210                         vertex = null;
211                     } else {
212 
213                         // layout.getLocation applies the layout transformer so
214                         // q is transformed by the layout transformer only
215                         Point2D q = layout.transform(vertex);
216                         // translate mouse point to graph coord system
217                         Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip);
218 
219                         offsetx = (float) (gp.getX()-q.getX());
220                         offsety = (float) (gp.getY()-q.getY());
221                     }
222                 } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) {
223                     pickedEdgeState.pick(edge, !pickedEdgeState.isPicked(edge));
224                 }
225             }
226         }
227         if(vertex != null) e.consume();
228     }
229 
230     /**
231 	 * If the mouse is dragging a rectangle, pick the
232 	 * Vertices contained in that rectangle
233 	 * 
234 	 * clean up settings from mousePressed
235 	 */
236     @SuppressWarnings("unchecked")
237     public void mouseReleased(MouseEvent e) {
238         VisualizationViewer<V,E> vv = (VisualizationViewer)e.getSource();
239         if(e.getModifiers() == modifiers) {
240             if(down != null) {
241                 Point2D out = e.getPoint();
242 
243                 if(vertex == null && heyThatsTooClose(down, out, 5) == false) {
244                     pickContainedVertices(vv, down, out, true);
245                 }
246             }
247         } else if(e.getModifiers() == this.addToSelectionModifiers) {
248             if(down != null) {
249                 Point2D out = e.getPoint();
250 
251                 if(vertex == null && heyThatsTooClose(down,out,5) == false) {
252                     pickContainedVertices(vv, down, out, false);
253                 }
254             }
255         }
256         down = null;
257         vertex = null;
258         edge = null;
259         rect.setFrame(0,0,0,0);
260         vv.removePostRenderPaintable(lensPaintable);
261         vv.repaint();
262     }
263     
264     /**
265 	 * If the mouse is over a picked vertex, drag all picked
266 	 * vertices with the mouse.
267 	 * If the mouse is not over a Vertex, draw the rectangle
268 	 * to select multiple Vertices
269 	 * 
270 	 */
271     @SuppressWarnings("unchecked")
272     public void mouseDragged(MouseEvent e) {
273         if(locked == false) {
274             VisualizationViewer<V,E> vv = (VisualizationViewer)e.getSource();
275             if(vertex != null) {
276                 Point p = e.getPoint();
277                 Point2D graphPoint = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(p);
278                 Point2D graphDown = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
279                 Layout<V,E> layout = vv.getGraphLayout();
280                 double dx = graphPoint.getX()-graphDown.getX();
281                 double dy = graphPoint.getY()-graphDown.getY();
282                 PickedState<V> ps = vv.getPickedVertexState();
283                 
284                 for(V v : ps.getPicked()) {
285                     Point2D vp = layout.transform(v);
286                     vp.setLocation(vp.getX()+dx, vp.getY()+dy);
287                     layout.setLocation(v, vp);
288                 }
289                 down = p;
290 
291             } else {
292                 Point2D out = e.getPoint();
293                 if(e.getModifiers() == this.addToSelectionModifiers ||
294                         e.getModifiers() == modifiers) {
295                     rect.setFrameFromDiagonal(down,out);
296                 }
297             }
298             if(vertex != null) e.consume();
299             vv.repaint();
300         }
301     }
302     
303     /**
304      * rejects picking if the rectangle is too small, like
305      * if the user meant to select one vertex but moved the
306      * mouse slightly
307      * @param p
308      * @param q
309      * @param min
310      * @return
311      */
312     private boolean heyThatsTooClose(Point2D p, Point2D q, double min) {
313         return Math.abs(p.getX()-q.getX()) < min &&
314                 Math.abs(p.getY()-q.getY()) < min;
315     }
316     
317     /**
318      * pick the vertices inside the rectangle created from points
319      * 'down' and 'out'
320      *
321      */
322     protected void pickContainedVertices(VisualizationViewer<V,E> vv, Point2D down, Point2D out, boolean clear) {
323         
324         Layout<V,E> layout = vv.getGraphLayout();
325         PickedState<V> pickedVertexState = vv.getPickedVertexState();
326         
327         Rectangle2D pickRectangle = new Rectangle2D.Double();
328         pickRectangle.setFrameFromDiagonal(down,out);
329          
330         if(pickedVertexState != null) {
331             if(clear) {
332             	pickedVertexState.clear();
333             }
334             GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
335 
336             Collection<V> picked = pickSupport.getVertices(layout, pickRectangle);
337             for(V v : picked) {
338             	pickedVertexState.pick(v, true);
339             }
340         }
341     }
342 
343     public void mouseClicked(MouseEvent e) {
344     }
345 
346     public void mouseEntered(MouseEvent e) {
347         JComponent c = (JComponent)e.getSource();
348         c.setCursor(cursor);
349     }
350 
351     public void mouseExited(MouseEvent e) {
352         JComponent c = (JComponent)e.getSource();
353         c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
354     }
355 
356     public void mouseMoved(MouseEvent e) {
357     }
358 
359     /**
360      * @return Returns the locked.
361      */
362     public boolean isLocked() {
363         return locked;
364     }
365 
366     /**
367      * @param locked The locked to set.
368      */
369     public void setLocked(boolean locked) {
370         this.locked = locked;
371     }
372 }