1 package de.fzi.wim.guibase.graphview.view;
2
3 import java.util.Iterator;
4 import java.util.ListIterator;
5 import java.util.Collection;
6 import java.util.Map;
7 import java.util.HashMap;
8 import java.util.Set;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.ArrayList;
12 import java.awt.Color;
13 import java.awt.Graphics;
14 import java.awt.Graphics2D;
15 import java.awt.Point;
16 import java.awt.Rectangle;
17 import java.awt.geom.Point2D;
18 import java.awt.event.MouseEvent;
19 import java.awt.event.KeyEvent;
20 import java.awt.event.FocusEvent;
21 import java.awt.event.ComponentEvent;
22 import javax.swing.JPanel;
23 import javax.swing.ToolTipManager;
24 import javax.swing.SwingUtilities;
25
26 import de.fzi.wim.guibase.graphview.graph.*;
27 import de.fzi.wim.guibase.graphview.lens.*;
28
29
30 import org.apache.log4j.Logger;
31
32
33 /***
34 * The panel capable of visualizing the graph.
35 */
36 public class JGraphPane extends JPanel {
37
38 private static final Logger logger = Logger.getLogger(JGraphPane.class);
39
40 /*** The graph being visualized. */
41 protected Graph m_graph;
42 /*** The listener for the graph. */
43 protected GraphListener m_graphListner;
44 /*** The listener for the lens. */
45 protected LensListener m_lensListener;
46 /*** The currently active lens. */
47 protected Lens m_lens;
48 /*** The map of node positions. */
49 protected Map m_nodePositions;
50 /*** The array of registered manipulators in the order they were registered. */
51 protected List m_manipulators;
52 /*** The map of rigestered manipulators keyed by their name. */
53 protected Map m_manipulatorsByName;
54
55 /***
56 * Creates a graph pane.
57 *
58 * @param graph the graph
59 */
60 public JGraphPane(Graph graph) {
61 super(null);
62 m_graphListner=new GraphHandler();
63 m_lensListener=new LensHandler();
64 m_nodePositions=new HashMap();
65 m_manipulators=new ArrayList();
66 m_manipulatorsByName=new HashMap();
67 setGraph(graph);
68 enableEvents(MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK | KeyEvent.KEY_EVENT_MASK | FocusEvent.FOCUS_EVENT_MASK | ComponentEvent.COMPONENT_EVENT_MASK);
69 setBackground(Color.white);
70 ToolTipManager.sharedInstance().setDismissDelay(10000);
71 ToolTipManager.sharedInstance().registerComponent(this);
72 }
73 /***
74 * Adds a manipulator to this pane. If a manipulator with the same name if registered, if is rist removed.
75 *
76 * @param manipulator the manipulator
77 */
78 public void addManipulator(Manipulator manipulator) {
79 removeManipulator(manipulator.getName());
80 m_manipulators.add(manipulator);
81 m_manipulatorsByName.put(manipulator.getName(),manipulator);
82 manipulator.setGraphPane(this);
83 }
84 /***
85 * Returns a manipulator with given name.
86 *
87 * @param name the name of the manpulator
88 * @return the manipulator with given name (or <code>null</code> if the manipualtor with given name is not registered)
89 */
90 public Manipulator getManipulator(String name) {
91 return (Manipulator)m_manipulatorsByName.get(name);
92 }
93 /***
94 * Removes a manipulator with given name.
95 *
96 * @param name the name of the manpulator
97 */
98 public void removeManipulator(String name) {
99 Manipulator manipulator=(Manipulator)m_manipulatorsByName.remove(name);
100 if (manipulator!=null) {
101 m_manipulators.remove(manipulator);
102 manipulator.setGraphPane(null);
103 }
104 }
105 /***
106 * Returns the current lens.
107 *
108 * @return the current lens (may be <code>null</code>)
109 */
110 public Lens getLens() {
111 return m_lens;
112 }
113 /***
114 * Sets the new lens.
115 *
116 * @param lens the new lens
117 */
118 public void setLens(Lens lens) {
119 Lens oldLens=m_lens;
120 if (m_lens!=null)
121 m_lens.removeLensListener(m_lensListener);
122 m_lens=lens;
123 if (m_lens!=null)
124 m_lens.addLensListener(m_lensListener);
125 m_nodePositions.clear();
126 repaint();
127 firePropertyChange("lens",oldLens,m_lens);
128 }
129 /***
130 * Returns the current graph.
131 *
132 * @return the current graph
133 */
134 public Graph getGraph() {
135 return m_graph;
136 }
137 /***
138 * Sets the current graph.
139 *
140 * @param graph the new graph
141 */
142 public void setGraph(Graph graph) {
143 Graph oldGraph=m_graph;
144 if (m_graph!=null)
145 m_graph.removeGraphListener(m_graphListner);
146 m_graph=graph;
147 if (m_graph!=null)
148 m_graph.addGraphListener(m_graphListner);
149 m_nodePositions.clear();
150 repaint();
151 firePropertyChange("graph",oldGraph,m_graph);
152 }
153 /***
154 * Updates the component.
155 *
156 * @param g the graphics
157 */
158 public void paintComponent(Graphics g) {
159 Graphics2D g2d=(Graphics2D)g;
160 Rectangle clipRectangle=g.getClipBounds();
161 Color oldColor=g.getColor();
162 g.setColor(getBackground());
163 g.fillRect(clipRectangle.x,clipRectangle.y,clipRectangle.width,clipRectangle.height);
164 g.setColor(oldColor);
165
166 logger.debug("Clip: "+clipRectangle);
167
168 if (m_graph!=null)
169 synchronized (m_graph) {
170 Rectangle bounds=new Rectangle();
171 Iterator iterator=m_graph.getEdges().iterator();
172 while (iterator.hasNext()) {
173 Edge edge=(Edge)iterator.next();
174 getEdgeScreenBounds(edge,bounds);
175 if (clipRectangle.intersects(bounds))
176 paintEdge(g2d,edge);
177 }
178
179 logger.debug("Painting nodes.");
180
181 iterator=m_graph.getNodes().iterator();
182 while (iterator.hasNext()) {
183 Node node=(Node)iterator.next();
184 getNodeScreenBounds(node,bounds);
185
186 logger.debug("Checking clip for node with bounds "+bounds);
187
188 if (clipRectangle.intersects(bounds))
189 paintNode(g2d,node);
190 }
191 for (int i=0;i<m_manipulators.size();i++)
192 ((Manipulator)m_manipulators.get(i)).paint(g2d);
193 }
194
195 logger.debug("paintComponent().exit");
196
197 }
198 /***
199 * Paints the edge.
200 *
201 * @param g the graphics
202 * @param edge the edge
203 */
204 protected void paintEdge(Graphics2D g,Edge edge) {
205 EdgePainter edgePainter=getPainterForEdge(edge);
206 edgePainter.paintEdge(this,g,edge);
207 }
208 /***
209 * Returns the painter for the edge.
210 *
211 * @param edge the edge
212 * @return the painter for the edge
213 */
214 public EdgePainter getPainterForEdge(Edge edge) {
215 return ArrowEdgePainter.INSTANCE;
216 }
217 /***
218 * Paints the node.
219 *
220 * @param g the graphics
221 * @param node the node
222 */
223 protected void paintNode(Graphics2D g,Node node) {
224 NodePainter nodePainter=getPainterForNode(node);
225 nodePainter.paintNode(this,g,node);
226 }
227 /***
228 * Returns the painter for the node.
229 *
230 * @param node the node
231 * @return the painter for the node
232 */
233 public NodePainter getPainterForNode(Node node) {
234 return RectangleNodePainter.INSTANCE;
235 }
236 /***
237 * Converts a given screen point into a point on the graph.
238 *
239 * @param point the sreen point
240 * @param graphPoint the calculatedpoint in the graph
241 */
242 public void screenToGraphPoint(Point point,Point2D graphPoint) {
243 graphPoint.setLocation(point.x-getWidth()/2,point.y-getHeight()/2);
244 if (m_lens!=null)
245 m_lens.undoLens(graphPoint);
246 }
247 /***
248 * Converts a given graph point into a point on the screen.
249 *
250 * @param graphPoint the point in the graph
251 * @param point the calculated screen point
252 */
253 public void graphToScreenPoint(Point2D graphPoint,Point point) {
254 double oldX=graphPoint.getX();
255 double oldY=graphPoint.getY();
256 if (m_lens!=null)
257 m_lens.applyLens(graphPoint);
258 point.setLocation((int)graphPoint.getX()+getWidth()/2,(int)graphPoint.getY()+getHeight()/2);
259 graphPoint.setLocation(oldX,oldY);
260 }
261 /***
262 * Returns the node at given point, or <code>null</code> if there is no such node.
263 *
264 * @param point the screen point
265 * @return the node at given point (or <code>null</code> if there is no such node)
266 */
267 public Node getNodeAtPoint(Point point) {
268 synchronized (m_graph) {
269 List nodes=m_graph.getNodes();
270 ListIterator iterator=nodes.listIterator(nodes.size());
271 while (iterator.hasPrevious()) {
272 Node node=(Node)iterator.previous();
273 NodePainter nodePainter=getPainterForNode(node);
274 if (nodePainter.isInNode(this,node,point))
275 return node;
276 }
277 return null;
278 }
279 }
280 /***
281 * Returns the set of nodes in given rectangle.
282 *
283 * @param rectangle the rectangle in which the nodes must be located
284 * @return the set of nodes in the region
285 */
286 public Set getNodesInRectangle(Rectangle rectangle) {
287 synchronized (m_graph) {
288 Set nodesInRectangle=new HashSet();
289 Rectangle nodeRectangle=new Rectangle();
290 Iterator nodes=m_graph.getNodes().iterator();
291 while (nodes.hasNext()) {
292 Node node=(Node)nodes.next();
293 getNodeScreenBounds(node,nodeRectangle);
294 if (rectangle.contains(nodeRectangle))
295 nodesInRectangle.add(node);
296 }
297 return nodesInRectangle;
298 }
299 }
300 /***
301 * Returns the edge at given point, or <code>null</code> if there is no such edge.
302 *
303 * @param point the screen point
304 * @return the edge at given point (or <code>null</code> if there is no such edge)
305 */
306 public Edge getNearestEdge(Point point) {
307 Edge nearestEdge=null;
308 double minDistance=4;
309 synchronized (m_graph) {
310 List edges=m_graph.getEdges();
311 ListIterator iterator=edges.listIterator(edges.size());
312 while (iterator.hasPrevious()) {
313 Edge edge=(Edge)iterator.previous();
314 EdgePainter edgePainter=getPainterForEdge(edge);
315 double distance=edgePainter.screenDistanceFromEdge(this,edge,point);
316 if (distance<minDistance) {
317 minDistance=distance;
318 nearestEdge=edge;
319 }
320 }
321 return nearestEdge;
322 }
323 }
324 /***
325 * Returns the position of the node on the screen.
326 *
327 * @param node the node whose on-screen position is required
328 * @return the position of the node on screen
329 */
330 public Point getScreenPointForNode(Node node) {
331 Point point=(Point)m_nodePositions.get(node);
332 if (point==null) {
333 point=new Point();
334 graphToScreenPoint(new Point2D.Double(node.getX(),node.getY()),point);
335 m_nodePositions.put(node,point);
336 }
337 return point;
338 }
339 /***
340 * Updates the map of screen positions of nodes.
341 */
342 protected void updateNodeScreenPositions() {
343 synchronized (m_graph) {
344 Point2D graphPoint=new Point2D.Double();
345 Iterator nodes=m_graph.getNodes().iterator();
346 while (nodes.hasNext()) {
347 Node node=(Node)nodes.next();
348 Point point=(Point)m_nodePositions.get(node);
349 if (point==null) {
350 point=new Point();
351 m_nodePositions.put(node,point);
352 }
353 graphPoint.setLocation(node.getX(),node.getY());
354 graphToScreenPoint(graphPoint,point);
355 }
356 }
357 }
358 /***
359 * Returns the screen bounds of given node.
360 *
361 * @param node the node for which the bounds must be returned
362 * @param nodeScreenRectangle the rectangle receiving the node's coordinates
363 */
364 public void getNodeScreenBounds(Node node,Rectangle nodeScreenRectangle) {
365 NodePainter nodePainter=getPainterForNode(node);
366 nodePainter.getNodeScreenBounds(this,node,nodeScreenRectangle);
367 }
368 /***
369 * Repaints the given node.
370 *
371 * @param node the node that needs to be repainted
372 */
373 public void repaintNode(Node node) {
374 Rectangle nodeScreenRectangle=new Rectangle();
375 getNodeScreenBounds(node,nodeScreenRectangle);
376 repaint(nodeScreenRectangle);
377 }
378 /***
379 * Returns the screen bounds of given edge.
380 *
381 * @param edge the edge for which the bounds must be returned
382 * @param edgeScreenRectangle the rectangle receiving the edge's coordinates
383 */
384 public void getEdgeScreenBounds(Edge edge,Rectangle edgeScreenRectangle) {
385 EdgePainter edgePainter=getPainterForEdge(edge);
386 edgePainter.getEdgeScreenBounds(this,edge,edgeScreenRectangle);
387 }
388 /***
389 * Repaints the given edge.
390 *
391 * @param edge the edge that needs to be repainted
392 */
393 public void repaintEdge(Edge edge) {
394 Rectangle edgeScreenRectangle=new Rectangle();
395 getEdgeScreenBounds(edge,edgeScreenRectangle);
396 edgeScreenRectangle.grow(5,5);
397 repaint(edgeScreenRectangle);
398 }
399 /***
400 * Returns the text for the tool-tip.
401 *
402 * @param event the mouse event
403 * @return the text for the tool-tip
404 */
405 public String getToolTipText(MouseEvent event) {
406 Point point=event.getPoint();
407 Node node=getNodeAtPoint(point);
408 if (node!=null) {
409 NodePainter nodePainter=getPainterForNode(node);
410 String toolTipText=nodePainter.getToolTipText(this,node,point);
411 if (toolTipText!=null)
412 return toolTipText;
413 }
414 return super.getToolTipText(event);
415 }
416 /***
417 * Processes the component event.
418 *
419 * @param e the event
420 */
421 protected void processComponentEvent(ComponentEvent e) {
422 super.processComponentEvent(e);
423 updateNodeScreenPositions();
424 repaint();
425 }
426 /***
427 * Processes the mouse event.
428 *
429 * @param e the event
430 */
431 protected void processMouseEvent(MouseEvent e) {
432 super.processMouseEvent(e);
433 switch (e.getID()) {
434 case MouseEvent.MOUSE_PRESSED:
435 if (!hasFocus() && isRequestFocusEnabled())
436 requestFocus();
437 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
438 ((Manipulator)m_manipulators.get(i)).mousePressed(e);
439 break;
440 case MouseEvent.MOUSE_RELEASED:
441 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
442 ((Manipulator)m_manipulators.get(i)).mouseReleased(e);
443 break;
444 case MouseEvent.MOUSE_CLICKED:
445 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
446 ((Manipulator)m_manipulators.get(i)).mouseClicked(e);
447 break;
448 case MouseEvent.MOUSE_ENTERED:
449 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
450 ((Manipulator)m_manipulators.get(i)).mouseEntered(e);
451 break;
452 case MouseEvent.MOUSE_EXITED:
453 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
454 ((Manipulator)m_manipulators.get(i)).mouseExited(e);
455 break;
456 }
457 }
458 /***
459 * Processes the mouse motion events.
460 *
461 * @param e mouse event
462 */
463 protected void processMouseMotionEvent(MouseEvent e) {
464 super.processMouseMotionEvent(e);
465 switch (e.getID()) {
466 case MouseEvent.MOUSE_MOVED:
467 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
468 ((Manipulator)m_manipulators.get(i)).mouseMoved(e);
469 break;
470 case MouseEvent.MOUSE_DRAGGED:
471 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
472 ((Manipulator)m_manipulators.get(i)).mouseDragged(e);
473 break;
474 }
475 }
476 /***
477 * Processes the key event.
478 *
479 * @param e the event
480 */
481 protected void processKeyEvent(KeyEvent e) {
482 super.processKeyEvent(e);
483 switch (e.getID()) {
484 case KeyEvent.KEY_TYPED:
485 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
486 ((Manipulator)m_manipulators.get(i)).keyTyped(e);
487 break;
488 case KeyEvent.KEY_PRESSED:
489 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
490 ((Manipulator)m_manipulators.get(i)).keyPressed(e);
491 break;
492 case KeyEvent.KEY_RELEASED:
493 for (int i=0;i<m_manipulators.size() && !e.isConsumed();i++)
494 ((Manipulator)m_manipulators.get(i)).keyReleased(e);
495 break;
496 }
497 }
498 /***
499 * Processes the focus event.
500 *
501 * @param e the event
502 */
503 protected void processFocusEvent(FocusEvent e) {
504 super.processFocusEvent(e);
505 switch (e.getID()) {
506 case FocusEvent.FOCUS_GAINED:
507 for (int i=0;i<m_manipulators.size();i++)
508 ((Manipulator)m_manipulators.get(i)).focusGained(e);
509 break;
510 case FocusEvent.FOCUS_LOST:
511 for (int i=0;i<m_manipulators.size();i++)
512 ((Manipulator)m_manipulators.get(i)).focusLost(e);
513 break;
514 }
515 }
516 /***
517 * Overridden to notify the manipulators that the scroll position has changed.
518 *
519 * @param rectangle the rectangle
520 */
521 public void scrollRectToVisible(Rectangle rectangle) {
522 super.scrollRectToVisible(rectangle);
523 for (int i=0;i<m_manipulators.size();i++)
524 ((Manipulator)m_manipulators.get(i)).notifyGraphPaneScrolled();
525 }
526
527 /***
528 * The handler of graph events.
529 */
530 protected class GraphHandler implements GraphListener {
531 public void graphLayoutUpdated(Graph graph) {
532 if (SwingUtilities.isEventDispatchThread()) {
533 updateNodeScreenPositions();
534 repaint();
535 }
536 else
537 SwingUtilities.invokeLater(new Runnable() {
538 public void run() {
539 updateNodeScreenPositions();
540 repaint();
541 }
542 });
543 }
544 public void graphUpdated(Graph graph) {
545 if (SwingUtilities.isEventDispatchThread())
546 repaint();
547 else
548 SwingUtilities.invokeLater(new Runnable() {
549 public void run() {
550 repaint();
551 }
552 });
553 }
554 public void graphContentsChanged(Graph graph) {
555 m_nodePositions.clear();
556 repaint();
557 }
558 public void elementsAdded(Graph graph,Collection nodes,Collection edges) {
559 repaint();
560 }
561 public void elementsRemoved(Graph graph,Collection nodes,Collection edges) {
562 if (nodes!=null) {
563 Iterator iterator=nodes.iterator();
564 while (iterator.hasNext()) {
565 Node node=(Node)iterator.next();
566 m_nodePositions.remove(node);
567 }
568 }
569 repaint();
570 }
571 }
572
573 /***
574 * The handler of lens events.
575 */
576 protected class LensHandler implements LensListener {
577 public void lensUpdated(Lens lens) {
578 updateNodeScreenPositions();
579 repaint();
580 }
581 }
582 }