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