1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.anim;
15  
16  import demo.view.DemoDefaults;
17  import y.anim.AnimationEvent;
18  import y.anim.AnimationFactory;
19  import y.anim.AnimationListener;
20  import y.anim.AnimationObject;
21  import y.anim.AnimationPlayer;
22  import y.anim.CompositeAnimationObject;
23  import y.base.Edge;
24  import y.base.EdgeCursor;
25  import y.base.EdgeMap;
26  import y.base.GraphEvent;
27  import y.base.GraphListener;
28  import y.base.Node;
29  import y.base.NodeCursor;
30  import y.option.GuiFactory;
31  import y.option.ResourceBundleGuiFactory;
32  import y.util.DefaultMutableValue2D;
33  import y.util.Value2D;
34  import y.view.Arrow;
35  import y.view.EdgeRealizer;
36  import y.view.EditMode;
37  import y.view.Graph2D;
38  import y.view.Graph2DViewRepaintManager;
39  import y.view.LineType;
40  import y.view.NodeRealizer;
41  import y.view.PolyLineEdgeRealizer;
42  import y.view.PopupMode;
43  import y.view.ViewAnimationFactory;
44  
45  import javax.swing.AbstractAction;
46  import javax.swing.Action;
47  import javax.swing.ActionMap;
48  import javax.swing.InputMap;
49  import javax.swing.JButton;
50  import javax.swing.JComponent;
51  import javax.swing.JFrame;
52  import javax.swing.JOptionPane;
53  import javax.swing.JPopupMenu;
54  import javax.swing.JRootPane;
55  import javax.swing.KeyStroke;
56  import java.awt.Color;
57  import java.awt.EventQueue;
58  import java.awt.event.ActionEvent;
59  import java.awt.event.KeyEvent;
60  import java.awt.geom.GeneralPath;
61  import java.util.MissingResourceException;
62  
63  /**
64   * Shows various animation effects for graph elements and graph views:
65   * <ul>
66   *   <li>fade in and fade out for nodes and/or edges</li>
67   *   <li>resizing of nodes</li>
68   *   <li>edge traversals</li>
69   *   <li>animated loading and clearing of graph structures</li>
70   *   <li>animated zooming</li>
71   *   <li>animated camera movement</li>
72   * </ul>
73   * Makes use of class {@link demo.view.anim.AnimationEffectsDemoBase}.
74   *
75   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/animation.html">Section Animations for Graph Elements</a> in the yFiles for Java Developer's Guide
76   */
77  public final class AnimationEffectsDemo extends AnimationEffectsDemoBase {
78    private static final long LONG_DURATION = 2000;
79    private static final long SHORT_DURATION = 500;
80    private static final long DEFAULT_DURATION = 1000;
81    private static final long PREFERRED_DURATION_CAMERA = 10000;
82    private static final long PREFERRED_DURATION_GRAPH = 5000;
83    private static final long PREFERRED_DURATION_TRAVERSAL = 2000;
84  
85    private final AnimationPlayer player;
86    private final EndHandler endHandler;
87    private final ViewAnimationFactory unmanagedFactory;
88  
89    /**
90     * Creates a new AnimationEffectsDemo.
91     */
92    public AnimationEffectsDemo() {
93      this(createGuiFactory(), false);
94    }
95  
96    /**
97     * Creates a new AnimationEffectsDemo.
98     */
99    private AnimationEffectsDemo(
100           final GuiFactory i18n,
101           final boolean wantsRadioButtons
102   ) {
103     super(i18n, wantsRadioButtons);
104     this.endHandler = new EndHandler();
105     this.player = new AnimationPlayer();
106     this.player.setFps(240);
107     this.player.addAnimationListener(endHandler);
108     this.player.setBlocking(false);
109     this.unmanagedFactory = new ViewAnimationFactory(view);
110 
111     configureDefaultRealizers();
112 
113     EdgeRealizer edgeRealizer = view.getGraph2D().getDefaultEdgeRealizer();
114     edgeRealizer.setLineColor(Color.LIGHT_GRAY);
115     edgeRealizer.setTargetArrow(Arrow.NONE);
116     edgeRealizer.setLineType(LineType.DASHED_2);
117   }
118 
119   protected void configureDefaultRealizers() {
120     DemoDefaults.configureDefaultRealizers(view);
121   }
122 
123 
124   /**
125    * Delegates appropriate to user-specified options.
126    */
127   void animate() {
128     if (player.isPlaying()) {
129       return;
130     }
131 
132     switch (((Byte) oh.get("misc", "animation")).byteValue()) {
133       case NO_ANIM:
134         break;
135       case ANIMATED_LOAD:
136         animatedLoad();
137         break;
138       case ANIMATED_CLEAR:
139         animatedClear();
140         break;
141       case TRAVERSE_EDGE:
142         animateTraverseEdge();
143         break;
144       case ZOOM:
145         animateZoom();
146         break;
147       case MOVE_CAMERA:
148         animateMoveCamera();
149         break;
150       case MORPH:
151         animateMorph();
152         break;
153       case RESIZE:
154         animateResize();
155         break;
156       case BLINK:
157         animateBlink();
158         break;
159     }
160   }
161 
162   /**
163    * Demonstrates animated loading of whole graph structures.
164    */
165   private void animatedLoad() {
166     compoundAction = true;
167 
168     openGraph(i18n.getString(DEMO_NAME + ".RESOURCE.graph." +
169         oh.get("misc", "animateLoad_graph")));
170 
171     final Graph2D graph = view.getGraph2D();
172 
173     if (graph.nodeCount() > 0) {
174       final ViewAnimationFactory factory = createManagedAnimationFactory();
175       play(factory.fadeIn(graph,
176           (ViewAnimationFactory.NodeOrder) oh.get("misc", "animateLoad_nodeOrder"),
177           oh.getBool("misc", "animateLoad_obeyEdgeDirection"),
178           oh.getDouble("misc", "animateLoad_ratio"),
179           PREFERRED_DURATION_GRAPH),
180           factory.getRepaintManager());
181 
182     }
183 
184     compoundAction = false;
185   }
186 
187   /**
188    * Demonstrates animated removal of whole graph structures.
189    */
190   private void animatedClear() {
191     compoundAction = true;
192 
193     final Graph2D graph = view.getGraph2D();
194     if (graph.nodeCount() < 1) {
195       openGraph(i18n.getString(DEMO_NAME + ".RESOURCE.graph.big"));
196     }
197 
198     if (graph.nodeCount() > 0) {
199       final ViewAnimationFactory factory = createManagedAnimationFactory();
200       factory.setQuality(ViewAnimationFactory.HIGH_QUALITY);
201       endHandler.setClear(true);
202       play(factory.fadeOut(graph,
203           (ViewAnimationFactory.NodeOrder) oh.get("misc", "animateClear_nodeOrder"),
204           oh.getBool("misc", "animateClear_obeyEdgeDirection"),
205           oh.getDouble("misc", "animateClear_ratio"),
206           PREFERRED_DURATION_GRAPH),
207           factory.getRepaintManager());
208     }
209 
210     compoundAction = false;
211   }
212 
213   /**
214    * Demonstrates animated edge traversal.
215    */
216   private void animateTraverseEdge() {
217     final Graph2D graph = view.getGraph2D();
218     final ViewAnimationFactory factory = createManagedAnimationFactory();
219 
220     // use a concurrency object, since we want to traverse all selected edges
221     // simultaneous
222     final CompositeAnimationObject traversal = AnimationFactory.createConcurrency();
223 
224     EdgeCursor edgesToTraverse = graph.selectedEdges();
225     if (!edgesToTraverse.ok()) {
226       edgesToTraverse = graph.edges();
227     }
228     for (EdgeCursor ec = edgesToTraverse; ec.ok(); ec.next()) {
229       // the edge to be traversed
230       final EdgeRealizer er = graph.getRealizer(ec.edge());
231       er.setVisible(true);
232 
233       final EdgeRealizer bak = er.createCopy();
234 
235       // set up the visual features of the "yet-to-be-traversed"
236       // part of the edge
237       final EdgeRealizer unvisited1 = er.createCopy();
238       unvisited1.setLineColor((Color) oh.get("misc", "colorUnvisited"));
239 //      unvisited1.setSourceArrow(er.getSourceArrow());
240 //      unvisited1.setTargetArrow(er.getTargetArrow());
241 
242       final EdgeRealizer unvisited2 = er.createCopy();
243       unvisited2.setLineColor((Color) oh.get("misc", "colorVisited"));
244       unvisited2.setLineType(LineType.LINE_2);
245       unvisited2.setSourceArrow(Arrow.NONE);
246       unvisited2.setTargetArrow(Arrow.NONE);
247 
248       // set up the visual features of the "already-traversed"
249       // part of the edge
250       final EdgeRealizer visited1 = er.createCopy();
251       visited1.setLineColor(unvisited2.getLineColor());
252       visited1.setLineType(LineType.LINE_2);
253       visited1.setSourceArrow(Arrow.NONE);
254       visited1.setTargetArrow(Arrow.NONE);
255 
256       final EdgeRealizer visited2 = er.createCopy();
257       visited2.setLineColor(unvisited1.getLineColor());
258 //      visited2.setSourceArrow(er.getTargetArrow());
259 //      visited2.setTargetArrow(er.getSourceArrow());
260 
261       // combine two traversals to run one after the other:
262       // first traverse the edge from source to target,
263       // then back from target to source; fix arrows in between
264       final CompositeAnimationObject seq = AnimationFactory.createLazySequence();
265       seq.addAnimation(
266           factory.traverseEdge(er, visited1, unvisited1, true,
267               ViewAnimationFactory.RESET_EFFECT,
268               PREFERRED_DURATION_TRAVERSAL));
269 
270       // fix realizer state
271       seq.addAnimation(new AnimationObject() {
272         public void initAnimation() {
273           er.setLineType(visited1.getLineType());
274           er.setLineColor(visited1.getLineColor());
275         }
276 
277         public void calcFrame(double time) {
278           // do nothing
279         }
280 
281         public void disposeAnimation() {
282           // do nothing
283         }
284 
285         public long preferredDuration() {
286           return 0;
287         }
288       });
289       seq.addAnimation(AnimationFactory.createPause(500));
290       seq.addAnimation(
291           factory.traverseEdge(er, visited2, unvisited2, false,
292               ViewAnimationFactory.APPLY_EFFECT,
293               PREFERRED_DURATION_TRAVERSAL));
294 
295       // fix realizer state
296       seq.addAnimation(new AnimationObject() {
297         public void initAnimation() {
298           // do nothing
299         }
300 
301         public void calcFrame(double time) {
302           // do nothing
303         }
304 
305         public void disposeAnimation() {
306           er.setSelected(bak.isSelected());
307           er.setLineColor(bak.getLineColor());
308           er.setLineType(bak.getLineType());
309           er.setSourceArrow(bak.getSourceArrow());
310           er.setTargetArrow(bak.getTargetArrow());
311         }
312 
313         public long preferredDuration() {
314           return 0;
315         }
316       });
317       traversal.addAnimation(seq);
318     }
319 
320     if (!traversal.isEmpty()) {
321       play(traversal, factory.getRepaintManager());
322     } else {
323       JOptionPane.showMessageDialog(null, i18n.getString(DEMO_NAME + ".message.selectEdge"));
324     }
325   }
326 
327   /**
328    * Demonstrates animated zooming in and out.
329    */
330   private void animateZoom() {
331     final ViewAnimationFactory factory = createUnmanagedAnimationFactory();
332     final double newZoom = oh.getDouble("misc", "zoom_factor");
333     play(factory.zoom(newZoom, ViewAnimationFactory.APPLY_EFFECT, DEFAULT_DURATION), null);
334   }
335 
336   /**
337    * Demonstrates animated camera movements (by adjusting the view center).
338    */
339   private void animateMoveCamera() {
340     final double x1 = view.toWorldCoordX(0);
341     final double x2 = view.toWorldCoordX(view.getWidth());
342     final double y1 = view.toWorldCoordY(0);
343     final double y2 = view.toWorldCoordY(view.getHeight());
344 
345     // create a path to traverse with the camera
346     final GeneralPath path = new GeneralPath();
347     path.moveTo((float) ((x1 + x2) * 0.5), (float) ((y1 + y2) * 0.5));
348     path.lineTo((float) (x1 + 0.15 * (x2 - x1)), (float) (y1 + 0.15 * (y2 - y1)));
349     path.lineTo((float) (x2 - 0.15 * (x2 - x1)), (float) (y1 + 0.15 * (y2 - y1)));
350     path.lineTo((float) ((x1 + x2) * 0.5), (float) ((y1 + y2) * 0.5));
351     path.lineTo((float) (x2 - 0.15 * (x2 - x1)), (float) (y2 - 0.15 * (y2 - y1)));
352     path.lineTo((float) (x1 + 0.15 * (x2 - x1)), (float) (y2 - 0.15 * (y2 - y1)));
353     path.quadTo((float) ((x1 + x2) * 0.5), (float) ((y1 + y2) * 0.5),
354         (float) (x1 + 0.15 * (x2 - x1)), (float) (y1 + 0.25 * (y2 - y1)));
355     path.quadTo((float) ((x1 + x2) * 0.5), (float) (y2 - 0.25 * (y2 - y1)),
356         (float) ((x1 + x2) * 0.5), (float) ((y1 + y2) * 0.5));
357 
358     final ViewAnimationFactory factory = createUnmanagedAnimationFactory();
359     play(factory.moveCamera(path, PREFERRED_DURATION_CAMERA), null);
360   }
361 
362   /**
363    * Demonstrates animated changing of visual features of a NodeRealizer.
364    */
365   private void animateMorph() {
366     final Graph2D graph = view.getGraph2D();
367     {
368       // use a concurrency object to morph all selected nodes simultaneously
369       final CompositeAnimationObject morph = AnimationFactory.createConcurrency();
370       final ViewAnimationFactory factory = createManagedAnimationFactory();
371 
372       NodeCursor nodesToMorph = graph.selectedNodes();
373       if (!nodesToMorph.ok()) {
374         nodesToMorph = graph.nodes();
375       }
376       for (NodeCursor nc = nodesToMorph; nc.ok(); nc.next()) {
377         final NodeRealizer nr = graph.getRealizer(nc.node());
378 
379         // set up the new visiual features according to user-specified options
380         final NodeRealizer morphTarget = nr.createCopy();
381         morphTarget.moveBy(oh.getDouble("misc", "translateX"),
382             oh.getDouble("misc", "translateY"));
383         morphTarget.setSize(oh.getDouble("misc", "width"),
384             oh.getDouble("misc", "height"));
385         morphTarget.setLineColor((Color) oh.get("misc", "lineColor"));
386         morphTarget.setFillColor((Color) oh.get("misc", "fillColor"));
387         morphTarget.setFillColor2((Color) oh.get("misc", "fillColor2"));
388 
389         morph.addAnimation(
390             factory.morph(
391                 nr, morphTarget, ViewAnimationFactory.APPLY_EFFECT,
392                 DEFAULT_DURATION));
393       }
394       if (!morph.isEmpty()) {
395         play(morph, factory.getRepaintManager());
396       } else {
397         JOptionPane.showMessageDialog(null, i18n.getString(DEMO_NAME + ".message.selectNode"));
398       }
399     }
400   }
401 
402   /**
403    * Demonstrates animated resizing of nodes.
404    */
405   private void animateResize() {
406     final Graph2D graph = view.getGraph2D();
407     final ViewAnimationFactory factory = createManagedAnimationFactory();
408 
409     // use a concurrency object to resize all selected nodes simultaneously
410     final CompositeAnimationObject resize = AnimationFactory.createConcurrency();
411 
412     NodeCursor nodesToResize = graph.selectedNodes();
413     if (!nodesToResize.ok()) {
414       nodesToResize = graph.nodes();
415     }
416     for (NodeCursor nc = nodesToResize; nc.ok(); nc.next()) {
417       // set up the new size according to user-specified option
418       final Value2D size =
419           DefaultMutableValue2D.create(oh.getDouble("misc", "resize_width"),
420               oh.getDouble("misc", "resize_height"));
421 
422       resize.addAnimation(
423           factory.resize(
424               graph.getRealizer(nc.node()), size,
425               ViewAnimationFactory.APPLY_EFFECT, SHORT_DURATION));
426     }
427     if (!resize.isEmpty()) {
428       play(resize, factory.getRepaintManager());
429     } else {
430       JOptionPane.showMessageDialog(null, i18n.getString(DEMO_NAME + ".message.selectNode"));
431     }
432   }
433 
434   /**
435    * Demonstrates blinking of nodes.
436    */
437   private void animateBlink() {
438     final Graph2D graph = view.getGraph2D();
439     final ViewAnimationFactory factory = createManagedAnimationFactory();
440 
441     // use a concurrency object to make all selected nodes blink simultaneously
442     final CompositeAnimationObject blink = AnimationFactory.createConcurrency();
443 
444     NodeCursor nodesToBlink = graph.selectedNodes();
445     if (!nodesToBlink.ok()) {
446       nodesToBlink = graph.nodes();
447     }
448     for (NodeCursor nc = nodesToBlink; nc.ok(); nc.next()) {
449       // set-up blink count according to user-specified option
450       final int repetitions = oh.getInt("misc", "repetitions");
451       if (repetitions > 1) {
452         blink.addAnimation(
453             AnimationFactory.createRepetition(
454                 factory.blink(graph.getRealizer(nc.node()),
455                     SHORT_DURATION),
456                 repetitions, false));
457       } else {
458         blink.addAnimation(
459             factory.blink(graph.getRealizer(nc.node()), SHORT_DURATION));
460       }
461     }
462 
463     if (!blink.isEmpty()) {
464       play(blink, factory.getRepaintManager());
465     } else {
466       JOptionPane.showMessageDialog(null, i18n.getString(DEMO_NAME + ".message.selectNode"));
467     }
468   }
469 
470   /**
471    * Demonstrates animated creation of nodes.
472    * @param nr   the <code>NodeRealizer</code> representing the node
473    */
474   private void animateCreate(final NodeRealizer nr) {
475     nr.setVisible(false);
476 
477     final ViewAnimationFactory factory = createManagedAnimationFactory();
478 
479     // select animation according to user-specified option
480     switch (((Byte) oh.get("elements", "createNode")).byteValue()) {
481       case NO_ANIM:
482         nr.setVisible(true);
483         break;
484       case BLUR_IN:
485         play(factory.blurIn(nr, DEFAULT_DURATION),
486             factory.getRepaintManager());
487         break;
488       case FADE_IN:
489         play(factory.fadeIn(nr, DEFAULT_DURATION),
490             factory.getRepaintManager());
491         break;
492       case IMPLODE:
493         play(factory.implode(nr, DEFAULT_DURATION),
494             factory.getRepaintManager());
495         break;
496       case WHIRL_IN:
497         play(factory.whirlIn(nr, DEFAULT_DURATION),
498             factory.getRepaintManager());
499         break;
500     }
501   }
502 
503   /**
504    * Demonstrates animated creation of edges.
505    * @param er   the <code>EdgeRealizer</code> representing the edge
506    */
507   private void animateCreate(final EdgeRealizer er) {
508     er.setVisible(false);
509 
510     final ViewAnimationFactory factory = createManagedAnimationFactory();
511 
512     // select animation according to user-specified option
513     switch (((Byte) oh.get("elements", "createEdge")).byteValue()) {
514       case NO_ANIM:
515         er.setVisible(true);
516         break;
517       case BLUR_IN:
518         play(factory.blurIn(er, DEFAULT_DURATION),
519             factory.getRepaintManager());
520         break;
521       case FADE_IN:
522         play(factory.fadeIn(er, DEFAULT_DURATION),
523             factory.getRepaintManager());
524         break;
525       case IMPLODE:
526         play(factory.implode(er, DEFAULT_DURATION),
527             factory.getRepaintManager());
528         break;
529       case EXTRACT:
530         play(factory.extract(er, DEFAULT_DURATION),
531             factory.getRepaintManager());
532         break;
533     }
534   }
535 
536   /**
537    * Demonstrates animated deletion of nodes.
538    * @param nr   the <code>NodeRealizer</code> representing the node
539    */
540   private void animateDelete(final NodeRealizer nr) {
541     final Graph2D graph = view.getGraph2D();
542     final ViewAnimationFactory factory = createManagedAnimationFactory();
543 
544     final CompositeAnimationObject deletes =
545         AnimationFactory.createLazySequence();
546     final CompositeAnimationObject deleteEdges =
547         AnimationFactory.createConcurrency();
548 
549     // first delete all edges of the specified node in an animated fashion
550     for (EdgeCursor ec = nr.getNode().edges(); ec.ok(); ec.next()) {
551       final EdgeRealizer er = graph.getRealizer(ec.edge());
552 
553       // select animation according to user-specified option
554       switch (((Byte) oh.get("elements", "deleteEdge")).byteValue()) {
555         case NO_ANIM:
556           deleteEdges.addAnimation(new RemoveEdge(er, factory.getRepaintManager()));
557           break;
558         case BLUR_OUT:
559           deleteEdges.addAnimation(
560               factory.blurOut(er, ViewAnimationFactory.APPLY_EFFECT,
561                   SHORT_DURATION));
562           break;
563         case FADE_OUT:
564           deleteEdges.addAnimation(
565               factory.fadeOut(er, ViewAnimationFactory.APPLY_EFFECT,
566                   SHORT_DURATION));
567           break;
568         case EXPLODE:
569           deleteEdges.addAnimation(
570               factory.explode(er, ViewAnimationFactory.APPLY_EFFECT,
571                   SHORT_DURATION));
572           break;
573         case RETRACT:
574           deleteEdges.addAnimation(
575               factory.retract(er, ViewAnimationFactory.APPLY_EFFECT,
576                   SHORT_DURATION));
577           break;
578       }
579     }
580     if (deleteEdges.preferredDuration() > 0) {
581       deletes.addAnimation(deleteEdges);
582     }
583 
584     // select animation according to user-specified option
585     switch (((Byte) oh.get("elements", "deleteNode")).byteValue()) {
586       case NO_ANIM:
587         deletes.addAnimation(new RemoveNode(nr, factory.getRepaintManager()));
588         break;
589       case BLUR_OUT:
590         deletes.addAnimation(
591             factory.blurOut(nr, ViewAnimationFactory.APPLY_EFFECT,
592                 DEFAULT_DURATION));
593         break;
594       case FADE_OUT:
595         deletes.addAnimation(
596             factory.fadeOut(nr, ViewAnimationFactory.APPLY_EFFECT,
597                 DEFAULT_DURATION));
598         break;
599       case EXPLODE:
600         deletes.addAnimation(
601             factory.explode(nr, ViewAnimationFactory.APPLY_EFFECT,
602                 DEFAULT_DURATION));
603         break;
604       case WHIRL_OUT:
605         deletes.addAnimation(
606             factory.whirlOut(nr, ViewAnimationFactory.APPLY_EFFECT,
607                 DEFAULT_DURATION));
608         break;
609     }
610     play(deletes, factory.getRepaintManager());
611   }
612 
613   /**
614    * Demonstrates animated deletion of edges.
615    * @param er   the <code>EdgeRealizer</code> representing the edge
616    */
617   private void animateDelete(final EdgeRealizer er) {
618     final ViewAnimationFactory factory = createManagedAnimationFactory();
619 
620     // select animation according to user-specified option
621     switch (((Byte) oh.get("elements", "deleteEdge")).byteValue()) {
622       case NO_ANIM:
623         play(new RemoveEdge(er, factory.getRepaintManager()),
624             factory.getRepaintManager());
625         break;
626       case BLUR_OUT:
627         play(factory.blurOut(er, ViewAnimationFactory.APPLY_EFFECT,
628             DEFAULT_DURATION),
629             factory.getRepaintManager());
630         break;
631       case FADE_OUT:
632         play(factory.fadeOut(er, ViewAnimationFactory.APPLY_EFFECT,
633             SHORT_DURATION),
634             factory.getRepaintManager());
635         break;
636       case EXPLODE:
637         play(factory.explode(er, ViewAnimationFactory.APPLY_EFFECT,
638             DEFAULT_DURATION),
639             factory.getRepaintManager());
640         break;
641       case RETRACT:
642         play(factory.retract(er, ViewAnimationFactory.APPLY_EFFECT,
643             SHORT_DURATION),
644             factory.getRepaintManager());
645         break;
646     }
647   }
648 
649   /**
650    * Demonstrates animated deletion of all selected graph elements.
651    */
652   private void animateDeleteSelection() {
653     final ViewAnimationFactory factory = createManagedAnimationFactory();
654 
655     final Graph2D graph = view.getGraph2D();
656 
657     final byte animatedEdgeDelete =
658         ((Byte) oh.get("elements", "deleteEdge")).byteValue();
659 
660     final byte animatedNodeDelete =
661         ((Byte) oh.get("elements", "deleteNode")).byteValue();
662 
663     // use a concurrency object to make all selected edges disappear
664     // simultaneously
665     final CompositeAnimationObject deleteEdges =
666         AnimationFactory.createConcurrency();
667 
668     final EdgeMap markAsScheduled = graph.createEdgeMap();
669 
670     // first schedule all selected edges for removal
671     for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
672       final EdgeRealizer er = graph.getRealizer(ec.edge());
673 
674       // select animation according to user-specified option
675       switch (animatedEdgeDelete) {
676         case NO_ANIM:
677           deleteEdges.addAnimation(new RemoveEdge(er, factory.getRepaintManager()));
678           break;
679         case BLUR_OUT:
680           deleteEdges.addAnimation(
681               factory.blurOut(er, ViewAnimationFactory.APPLY_EFFECT,
682                   SHORT_DURATION));
683           break;
684         case FADE_OUT:
685           deleteEdges.addAnimation(
686               factory.fadeOut(er, ViewAnimationFactory.APPLY_EFFECT,
687                   SHORT_DURATION));
688           break;
689         case EXPLODE:
690           deleteEdges.addAnimation(
691               factory.explode(er, ViewAnimationFactory.APPLY_EFFECT,
692                   SHORT_DURATION));
693           break;
694         case RETRACT:
695           deleteEdges.addAnimation(
696               factory.retract(er, ViewAnimationFactory.APPLY_EFFECT,
697                   SHORT_DURATION));
698           break;
699       }
700       markAsScheduled.setBool(ec.edge(), true);
701     }
702 
703     // then schedule all edges to or from selected nodes for removal
704     for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
705       for (EdgeCursor ec = nc.node().edges(); ec.ok(); ec.next()) {
706         if (!markAsScheduled.getBool(ec.edge())) {
707           final EdgeRealizer er = graph.getRealizer(ec.edge());
708 
709           // select animation according to user-specified option
710           switch (animatedEdgeDelete) {
711             case NO_ANIM:
712               deleteEdges.addAnimation(new RemoveEdge(er, factory.getRepaintManager()));
713               break;
714             case BLUR_OUT:
715               deleteEdges.addAnimation(
716                   factory.blurOut(er, ViewAnimationFactory.APPLY_EFFECT,
717                       SHORT_DURATION));
718               break;
719             case FADE_OUT:
720               deleteEdges.addAnimation(
721                   factory.fadeOut(er, ViewAnimationFactory.APPLY_EFFECT,
722                       SHORT_DURATION));
723               break;
724             case EXPLODE:
725               deleteEdges.addAnimation(
726                   factory.explode(er, ViewAnimationFactory.APPLY_EFFECT,
727                       SHORT_DURATION));
728               break;
729             case RETRACT:
730               deleteEdges.addAnimation(
731                   factory.retract(er, ViewAnimationFactory.APPLY_EFFECT,
732                       SHORT_DURATION));
733               break;
734           }
735           markAsScheduled.setBool(ec.edge(), true);
736         }
737       }
738     }
739 
740     graph.disposeEdgeMap(markAsScheduled);
741 
742     // use a concurrency object to make all selected edges disappear
743     // simultaneously
744     final CompositeAnimationObject deleteNodes =
745         AnimationFactory.createConcurrency();
746 
747     for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
748       final NodeRealizer nr = graph.getRealizer(nc.node());
749 
750       // select animation according to user-specified option
751       switch (animatedNodeDelete) {
752         case NO_ANIM:
753           deleteNodes.addAnimation(new RemoveNode(nr, factory.getRepaintManager()));
754           break;
755         case BLUR_OUT:
756           deleteNodes.addAnimation(
757               factory.blurOut(nr, ViewAnimationFactory.APPLY_EFFECT,
758                   DEFAULT_DURATION));
759           break;
760         case FADE_OUT:
761           deleteNodes.addAnimation(
762               factory.fadeOut(nr, ViewAnimationFactory.APPLY_EFFECT,
763                   DEFAULT_DURATION));
764           break;
765         case EXPLODE:
766           deleteNodes.addAnimation(
767               factory.explode(nr, ViewAnimationFactory.APPLY_EFFECT,
768                   LONG_DURATION));
769           break;
770         case WHIRL_OUT:
771           deleteNodes.addAnimation(
772               factory.whirlOut(nr, ViewAnimationFactory.APPLY_EFFECT,
773                   DEFAULT_DURATION));
774           break;
775       }
776     }
777 
778     // play animations
779 
780     // use a sequence object to make all selected edges disappear
781     // before the selected nodes disappear
782     final CompositeAnimationObject deletes = AnimationFactory.createLazySequence();
783 
784     if (!deleteEdges.isEmpty()) {
785       deletes.addAnimation(deleteEdges);
786     }
787     if (!deleteNodes.isEmpty()) {
788       deletes.addAnimation(deleteNodes);
789     }
790     if (!deletes.isEmpty()) {
791       play(deletes, factory.getRepaintManager());
792     }
793   }
794 
795   /**
796    * Plays the specified animation an registers the specified repaint manager
797    * as an <code>AnimationListener</code>.
798    */
799   private void play(final AnimationObject ao, final Graph2DViewRepaintManager arm) {
800     player.addAnimationListener(new AutoRemoveListener(arm != null
801         ? (AnimationListener) arm
802         : view));
803     player.setSpeed(oh.getDouble("global", "speed"));
804     player.animate(ao);
805   }
806 
807 
808   /**
809    * Factory method to create an <code>ViewAnimationFactory</code>.
810    */
811   private ViewAnimationFactory createUnmanagedAnimationFactory() {
812     return unmanagedFactory;
813   }
814 
815   /**
816    * Factory method to create an <code>ViewAnimationFactory</code> that uses
817    * a <code>Graph2DViewRepaintManager</code>.
818    */
819   private ViewAnimationFactory createManagedAnimationFactory() {
820     final ViewAnimationFactory factory =
821         new ViewAnimationFactory(new Graph2DViewRepaintManager(view));
822     factory.setQuality(ViewAnimationFactory.HIGH_QUALITY);
823     return factory;
824   }
825 
826   /**
827    * Factory method to create an <code>Action</code> that triggers animated
828    * edge creation between selected nodes.
829    */
830   private Action createCreateEdgeAction() {
831     final Action create = new AbstractAction() {
832       public void actionPerformed(final ActionEvent e) {
833         final Graph2D graph = view.getGraph2D();
834 
835         Node lastNode = null;
836         for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
837           if (lastNode != null) {
838             final EdgeRealizer er = new PolyLineEdgeRealizer();
839             er.setVisible(false);
840             graph.createEdge(lastNode, nc.node(), er);
841             animateCreate(er);
842           }
843           lastNode = nc.node();
844         }
845 
846         if (lastNode == null) {
847           JOptionPane.showMessageDialog(null, i18n.getString(DEMO_NAME + ".message.selectNodes"));
848         }
849       }
850     };
851     localizeAction(create, DEMO_NAME + ".action.CreateEdge");
852 
853     return create;
854   }
855 
856   /**
857    * Factory method to create an <code>Action</code> that triggers animated
858    * deletion of selected graph elements.
859    */
860   private Action createDeleteSelectionAction() {
861     final Action delete = new AbstractAction() {
862       public void actionPerformed(final ActionEvent e) {
863         animateDeleteSelection();
864       }
865     };
866     localizeAction(delete, DEMO_NAME + ".action.DeleteSelection");
867 
868     return delete;
869   }
870 
871   /**
872    * Factory method to create an <code>Action</code> that triggers animated
873    * deletion of selected nodes.
874    */
875   private Action createDeleteNodeAction() {
876     final Action delete = new AbstractAction() {
877       public void actionPerformed(final ActionEvent e) {
878         final Graph2D graph = view.getGraph2D();
879         final NodeCursor nc = graph.selectedNodes();
880         if (nc.ok()) {
881           animateDelete(graph.getRealizer(nc.node()));
882 //          view.updateView();
883         }
884       }
885     };
886     localizeAction(delete, DEMO_NAME + ".action.DeleteNode");
887 
888     return delete;
889   }
890 
891   /**
892    * Factory method to create an <code>Action</code> that triggers animated
893    * deletion of selected edges.
894    */
895   private Action createDeleteEdgeAction() {
896     final Action delete = new AbstractAction() {
897       public void actionPerformed(final ActionEvent e) {
898         final Graph2D graph = view.getGraph2D();
899         final EdgeCursor ec = graph.selectedEdges();
900         if (ec.ok()) {
901           animateDelete(graph.getRealizer(ec.edge()));
902         }
903       }
904     };
905     localizeAction(delete, DEMO_NAME + ".action.DeleteEdge");
906 
907     return delete;
908   }
909 
910 
911   /**
912    * Registers a <code>GraphListener</code> on the demo's
913    * <code>Graph2DView</code> that triggers animated node creation.
914    */
915   private void registerGraphListener() {
916     final Graph2D graph = view.getGraph2D();
917     graph.addGraphListener(new GraphListener() {
918       public void onGraphEvent(final GraphEvent e) {
919         if (GraphEvent.NODE_CREATION == e.getType() && !compoundAction) {
920           animateCreate(graph.getRealizer((Node) e.getData()));
921         } else if (GraphEvent.EDGE_CREATION == e.getType() && !compoundAction) {
922           EdgeRealizer edgeRealizer = graph.getRealizer((Edge) e.getData());
923           edgeRealizer.setLineColor((Color) oh.get("misc", "colorUnvisited"));
924           edgeRealizer.setTargetArrow(Arrow.STANDARD);
925           edgeRealizer.setLineType(LineType.LINE_1);
926           animateCreate(edgeRealizer);
927         }
928       }
929     });
930   }
931 
932   /**
933    * Registers an <code>EditMode</code> that provides context popup menus
934    * to trigger animated node and edge deletion and edge creation.
935    */
936   private void registerEditMode() {
937     EditMode editMode = new EditMode();
938     editMode.allowMovingWithPopup(true);
939     
940     PopupMode popupMode = new PopupMode() {
941       private final Action createEdge = createCreateEdgeAction();
942       private final Action deleteNode = createDeleteNodeAction();
943       private final Action deleteEdge = createDeleteEdgeAction();
944 
945       public JPopupMenu getSelectionPopup(double x, double y) {
946         final JPopupMenu menu = new JPopupMenu();
947         menu.add(createEdge);
948         return menu;
949       }
950 
951       public JPopupMenu getNodePopup(final Node v) {
952         final JPopupMenu menu = new JPopupMenu();
953         menu.add(deleteNode);
954         return menu;
955       }
956 
957       public JPopupMenu getEdgePopup(final Edge e) {
958         final JPopupMenu menu = new JPopupMenu();
959         menu.add(deleteEdge);
960         return menu;
961       }
962     };
963     editMode.setPopupMode(popupMode);
964     view.addViewMode(editMode);
965   }
966 
967   /**
968    * Binds key events to actions.
969    */
970   private void registerActions() {
971     final ActionMap amap = new ActionMap();
972     amap.put("DELETE_SELECTION", createDeleteSelectionAction());
973     final InputMap imap = new InputMap();
974     imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "DELETE_SELECTION");
975     view.getCanvasComponent().setActionMap(amap);
976     view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
977   }
978 
979 
980   public void addContentTo(final JRootPane rootPane) {
981     openGraph(i18n.getString(DEMO_NAME + ".RESOURCE.graph.small"));
982     registerGraphListener();
983     registerEditMode();
984     registerActions();
985     rootPane.setContentPane(createContentPane());
986   }
987 
988   public static void main(String[] args) {
989     EventQueue.invokeLater(new Runnable() {
990       public void run() {
991         DemoDefaults.initLnF();
992         final GuiFactory gf = createGuiFactory();
993         final AnimationEffectsDemo demo = new AnimationEffectsDemo(gf, true);
994         final JFrame frame = new JFrame(gf.getString(DEMO_NAME + ".title"));
995         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
996         demo.addContentTo(frame.getRootPane());
997         frame.pack();
998         frame.setLocationRelativeTo(null);
999         frame.setVisible(true);
1000      }
1001    });
1002  }
1003
1004  private static GuiFactory createGuiFactory() {
1005    try {
1006      final ResourceBundleGuiFactory i18n = new ResourceBundleGuiFactory();
1007      i18n.addBundle(AnimationEffectsDemo.class.getName());
1008      return i18n;
1009    }
1010    catch (final MissingResourceException mre) {
1011      System.err.println("Could not find resources! " + mre);
1012      return new GuiFactory() {
1013        public JButton createButton(final String action) {
1014          return new JButton(action);
1015        }
1016
1017        public String getString(final String key) {
1018          return key;
1019        }
1020
1021        public Action createHelpAction(final String helpKey) {
1022          return null;
1023        }
1024      };
1025    }
1026  }
1027
1028  /**
1029   * <code>AnimationListener</code> implementation that automatically
1030   * de-registers itself on <code>AnimationEvent.END</code>.
1031   */
1032  private final class AutoRemoveListener implements AnimationListener {
1033    private final AnimationListener delegate;
1034
1035    AutoRemoveListener(final AnimationListener delegate) {
1036      this.delegate = delegate;
1037    }
1038
1039    public void animationPerformed(final AnimationEvent e) {
1040      delegate.animationPerformed(e);
1041      if (AnimationEvent.END == e.getHint()) {
1042        player.removeAnimationListener(this);
1043      }
1044    }
1045  }
1046
1047  /**
1048   * <code>AnimationListener</code> implementation that triggers
1049   * a view update on <code>AnimationEvent.END</code>.
1050   */
1051  private final class EndHandler implements AnimationListener {
1052    private boolean clear;
1053
1054    EndHandler() {
1055      this.clear = false;
1056    }
1057
1058    void setClear(final boolean clear) {
1059      this.clear = clear;
1060    }
1061
1062    public void animationPerformed(final AnimationEvent e) {
1063      if (AnimationEvent.END == e.getHint()) {
1064        if (clear) {
1065          view.getGraph2D().clear();
1066          view.fitContent();
1067          clear = false;
1068        }
1069        view.updateView();
1070      }
1071    }
1072  }
1073
1074  /**
1075   * <em>Animation</em> that removes a node without visual feed back.
1076   * This <code>AnimationObject</code> is used to simplify the
1077   * <em>delete selection</em> action when using animated edge deletion
1078   * but no animated node deletion.
1079   */
1080  private static final class RemoveNode implements AnimationObject {
1081    private NodeRealizer realizer;
1082    private Graph2DViewRepaintManager manager;
1083
1084    public RemoveNode(final NodeRealizer realizer,
1085                      final Graph2DViewRepaintManager manager) {
1086      this.realizer = realizer;
1087      this.manager = manager;
1088    }
1089
1090    public void initAnimation() {
1091      if (manager != null) {
1092        manager.add(realizer);
1093      }
1094    }
1095
1096    public void calcFrame(final double time) {
1097    }
1098
1099    public void disposeAnimation() {
1100      final Node node = realizer.getNode();
1101      node.getGraph().removeNode(node);
1102
1103      if (manager != null) {
1104        manager.remove(realizer);
1105        manager = null;
1106      }
1107
1108      realizer = null;
1109    }
1110
1111    public long preferredDuration() {
1112      return 10;
1113    }
1114  }
1115
1116  /**
1117   * <em>Animation</em> that removes an edge without visual feed back.
1118   * This <code>AnimationObject</code> is used to simplify the
1119   * <em>delete selection</em> action when using animated node deletion
1120   * but no animated edge deletion.
1121   */
1122  private static final class RemoveEdge implements AnimationObject {
1123    private EdgeRealizer realizer;
1124    private Graph2DViewRepaintManager manager;
1125
1126    public RemoveEdge(final EdgeRealizer realizer,
1127                      final Graph2DViewRepaintManager manager) {
1128      this.realizer = realizer;
1129      this.manager = manager;
1130    }
1131
1132    public void initAnimation() {
1133      if (manager != null) {
1134        manager.add(realizer);
1135      }
1136    }
1137
1138    public void calcFrame(final double time) {
1139    }
1140
1141    public void disposeAnimation() {
1142      final Edge edge = realizer.getEdge();
1143      edge.getGraph().removeEdge(edge);
1144
1145      if (manager != null) {
1146        manager.remove(realizer);
1147        manager = null;
1148      }
1149
1150      realizer = null;
1151    }
1152
1153    public long preferredDuration() {
1154      return 10;
1155    }
1156  }
1157}
1158