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