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.hierarchy;
15  
16  import org.w3c.dom.Document;
17  import y.anim.AnimationObject;
18  import y.anim.AnimationPlayer;
19  import y.base.Node;
20  import y.base.NodeCursor;
21  import y.io.GraphMLIOHandler;
22  import y.view.GenericNodeRealizer;
23  import y.view.Graph2D;
24  import y.view.Graph2DTraversal;
25  import y.view.Graph2DViewRepaintManager;
26  import y.view.HitInfo;
27  import y.view.NodeRealizer;
28  import y.view.ProxyShapeNodeRealizer;
29  import y.view.ViewMode;
30  import y.view.hierarchy.DefaultHierarchyGraphFactory;
31  import y.view.hierarchy.GroupNodePainter;
32  
33  import java.awt.EventQueue;
34  import java.beans.PropertyChangeEvent;
35  import java.beans.PropertyChangeListener;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.util.Locale;
39  
40  /**
41   * Demonstrates how to use a custom {@link y.view.ViewMode} in order to fade in the group state icon when the mouse is
42   * placed over a group/folder node and fade it out when the mouse leaves a group/folder node.
43   * <p/>
44   * This <code>ViewMode</code> determines whether the mouse moves over a group/folder node and then starts an animation
45   * that changes a style property of the group node realizer which contains the opacity value for the icon.
46   */
47  public class FadingGroupStateIconDemo extends GroupingDemo {
48  
49    /**
50     * Overwritten to register a custom view mode that shows/hides the group state icon.
51     */
52    protected void registerViewModes() {
53      super.registerViewModes();
54      view.addViewMode(new FadingIconViewMode());
55    }
56  
57    /**
58     * Handles the deserialization of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} by setting
59     * the opacity to zero for all nodes when loading the graphML file.
60     * @return the <code>GraphMLIOHandler</code>
61     */
62    protected GraphMLIOHandler createGraphMLIOHandler() {
63      return new GraphMLIOHandler(){
64        public void read(Graph2D graph, InputStream in) throws IOException {
65          super.read(graph, in);
66          setTransparentGroupState(graph);
67        }
68  
69        public void read(Graph2D graph, Document documentElement) throws IOException {
70          super.read(graph, documentElement);
71          setTransparentGroupState(graph);
72        }
73  
74        private void setTransparentGroupState(Graph2D graph) {
75          for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
76            setOpacity(graph.getRealizer(nc.node()), 0);
77          }
78        }
79      };
80    }
81  
82    /**
83     * Configures the default group node realizers with an opacity value of 0.
84     */
85    protected void configureDefaultGroupNodeRealizers() {
86      // If the opacity value would be set for the default group node realizer,
87      // all added group nodes would share the same style property instance.
88      // As the default group node realizer here does not have the style property, a
89      // new style property is set for every added node.
90      getHierarchyManager().setGraphFactory(new DefaultHierarchyGraphFactory() {
91        public NodeRealizer createNodeRealizer(Object hint) {
92          final NodeRealizer nr = super.createNodeRealizer(hint);
93          setOpacity(nr, 0);
94          return nr;
95        }
96      });
97      super.configureDefaultGroupNodeRealizers();
98    }
99  
100   /**
101    * Sets the opacity of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} and creates a new style property when
102    * needed. If the realizer is a <code>ProxyShapeNodeRealizer</code> the opacity is set for all its delegates.
103    * @param realizer the realizer the property is set for
104    * @param opacity the opacity
105    */
106   private static void setOpacity(NodeRealizer realizer, float opacity) {
107     if (realizer instanceof ProxyShapeNodeRealizer) {
108       // If the realizer is a proxy the style property is set recursively for all delegates.
109       final ProxyShapeNodeRealizer proxyRealizer = (ProxyShapeNodeRealizer) realizer;
110       for (int i = 0; i < proxyRealizer.realizerCount(); i++) {
111         setOpacity(proxyRealizer.getRealizer(i), opacity);
112       }
113     } else {
114       // Set or create the FadingIconValue
115       GroupNodePainter.GroupStateStyle style = (GroupNodePainter.GroupStateStyle) ((GenericNodeRealizer) realizer).getStyleProperty(
116           GroupNodePainter.GROUP_STATE_STYLE_ID);
117       if (style == null) {
118         style = new GroupNodePainter.GroupStateStyle();
119         ((GenericNodeRealizer) realizer).setStyleProperty(GroupNodePainter.GROUP_STATE_STYLE_ID, style);
120       }
121       style.setOpacity(opacity);
122     }
123   }
124 
125   /**
126    * Launches the <code>CustomGroupViewModeDemo</code>.
127    * @param args not used
128    */
129   public static void main(String[] args) {
130     EventQueue.invokeLater(new Runnable() {
131       public void run() {
132         Locale.setDefault(Locale.ENGLISH);
133         initLnF();
134         (new FadingGroupStateIconDemo()).start();
135       }
136     });
137   }
138 
139   /**
140    * A custom {@link y.view.ViewMode} which fades in the group state icon when the mouse moves over a group/folder node
141    * and fades out the group state icon when the mouse when the mouse leaves the group/folder node.
142    */
143   public class FadingIconViewMode extends ViewMode {
144 
145     /** Player to animate the fading. */
146     private AnimationPlayer player;
147 
148     /** Last seen node to be able to unset its property when leaving. */
149     private Node lastSeenNode;
150 
151     /** Repaint manager that executes repaints during animations. */
152     private Graph2DViewRepaintManager repaintManager;
153 
154     /** Last fade in animation to be able to abort the fading */
155     private FadingIconAnimation lastFadeInAnimation;
156 
157     /**
158      * Creates a <code>FadingIconViewMode</code>.
159      */
160     public FadingIconViewMode() {
161       player = new AnimationPlayer(false);
162 
163       // As view is not set at creation time listening to the appropriate property change events
164       // allows us to create a repaint manager as soon as the necessary view instance is available.
165       addPropertyChangeListener(new PropertyChangeListener() {
166         public void propertyChange(PropertyChangeEvent e) {
167           if (ACTIVE_VIEW_PROPERTY.equals(e.getPropertyName())) {
168             if (repaintManager != null) {
169               player.removeAnimationListener(repaintManager);
170             }
171             repaintManager = new Graph2DViewRepaintManager(view);
172             player.addAnimationListener(repaintManager);
173           }
174         }
175       });
176     }
177 
178     /**
179      * Determines if the mouse moves over a group or folder node and starts the animation to fade
180      * in/out the group state icon.
181      */
182     public void mouseMoved(double x, double y) {
183       final Graph2D graph = view.getGraph2D();
184       if (graph.getHierarchyManager() != null) {
185         final HitInfo hitInfo =
186             view.getHitInfoFactory().createHitInfo(x, y, Graph2DTraversal.NODES, true);
187 
188         // determine whether the mouse moves over a group/folder node
189         Node n = null;
190         if (hitInfo.hasHitNodes()) {
191           n = hitInfo.getHitNode();
192           if (graph.getHierarchyManager().isNormalNode(n)) {
193             if (graph.getHierarchyManager().getParentNode(n) == lastSeenNode) {
194               n = lastSeenNode;
195             } else {
196               n = null;
197             }
198           }
199         }
200 
201         //fade out
202         if (lastSeenNode != n && lastSeenNode != null && lastSeenNode.getGraph() != null) {
203           if (lastFadeInAnimation != null) {
204             lastFadeInAnimation.setActive(false);
205           }
206 
207           final NodeRealizer lastSeenNodeRealizer = ((Graph2D) lastSeenNode.getGraph()).getRealizer(lastSeenNode);
208           player.animate(new FadingIconAnimation(lastSeenNodeRealizer, false));
209 
210           lastSeenNode = null;
211         }
212 
213         //fade in
214         if (lastSeenNode != n && n != null) {
215           final NodeRealizer hitNodeRealizer = ((Graph2D) n.getGraph()).getRealizer(n);
216 
217           final FadingIconAnimation animation = new FadingIconAnimation(hitNodeRealizer, true);
218           player.animate(animation);
219 
220           lastFadeInAnimation = animation;
221           lastSeenNode = n;
222         }
223       }
224     }
225 
226     /**
227      * An {@link AnimationObject} that animates fading of the group state icon. It sets the transparency value of
228      * the icon using a style property.
229      */
230     private final class FadingIconAnimation implements AnimationObject {
231 
232       /** Maximum duration of the fading animation */
233       private static final int FADE_DURATION = 500;
234 
235       /** Start value of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} value. */
236       private float startValue;
237 
238       /** Realizer of the group/folder node whose state icon fades. */
239       private NodeRealizer realizer;
240 
241       /** Determines if it is a fade in or a fade out animation */
242       private boolean fadeIn;
243 
244       /** Determines if the animation is still active */
245       private boolean active;
246 
247       /**
248        * Creates a <code>FadingIconAnimation</code> for a given realizer.
249        * @param realizer the realizer
250        * @param fadeIn   <code>true</code> if it is a fade in animation, <code>false</code> otherwise.
251        */
252       public FadingIconAnimation(NodeRealizer realizer, boolean fadeIn) {
253         this.realizer = realizer;
254         this.fadeIn = fadeIn;
255         this.active = true;
256       }
257 
258       /**
259        * Initializes fading either with transparency zero (fade in) or with the last transparency value (fade out).
260        */
261       public void initAnimation() {
262         if (repaintManager != null) {
263           repaintManager.add(realizer);
264         }
265 
266         if (fadeIn) {
267           // Set a new fading icon property with start value 0 = fully transparent
268           setOpacity(realizer, 0);
269         } else {
270           // Start at the transparency value where the last fade in animation ended
271           startValue = getOpacity(realizer);
272         }
273       }
274 
275       /**
276        * Calculates the transparency value for the group state icon.
277        * @param time a point in [0.0, 1.0]
278        */
279       public void calcFrame(double time) {
280         if (active) {
281           if (fadeIn) {
282             setOpacity(realizer, (float) time);
283           } else {
284             setOpacity(realizer, startValue * (1 - (float) time));
285           }
286         }
287       }
288 
289       public void disposeAnimation() {
290         if (repaintManager != null) {
291           repaintManager.remove(realizer);
292         }
293       }
294 
295       /**
296        * Returns the preferred duration of the animation which is shorter if the fade in animation was aborted early.
297        * @return the preferred duration of the animation
298        */
299       public long preferredDuration() {
300         if (fadeIn) {
301           return FADE_DURATION;
302         } else {
303           if (startValue > 0) {
304             // if the fade in animation was aborted, shorten the fade out animation
305             return (long) (FADE_DURATION * startValue);
306           } else {
307             return 0;
308           }
309         }
310       }
311 
312       /**
313        * Specifies whether the animation is active or not. If the animation is not active, {@link #calcFrame(double)}
314        * will do nothing.
315        * @param active the state of this animation
316        */
317       public void setActive(boolean active) {
318         this.active = active;
319       }
320 
321       /**
322        * Retrieves the value of the {@link GroupNodePainter#GROUP_STATE_STYLE_ID} for the given realizer.
323        * @param realizer the realizer
324        * @return the current value of the style property
325        */
326       private float getOpacity(NodeRealizer realizer) {
327         NodeRealizer r = getDelegateRealizer(realizer);
328         return ((GroupNodePainter.GroupStateStyle) ((GenericNodeRealizer) r).getStyleProperty(
329             GroupNodePainter.GROUP_STATE_STYLE_ID)).getOpacity();
330       }
331 
332       /**
333        * Gets the current delegate realizer of the <code>ProxyShapeNodeRealizer</code> of the node or the node's realizer
334        * if no proxy is used.
335        * @param realizer the <code>NodeRealizer</code> that could be a <code>ProxyShapeNodeRealizer</code>
336        * @return the delegate realizer or the given realizer itself
337        */
338       private GenericNodeRealizer getDelegateRealizer(NodeRealizer realizer) {
339         if (realizer instanceof ProxyShapeNodeRealizer) {
340           return (GenericNodeRealizer) ((ProxyShapeNodeRealizer) realizer).getRealizerDelegate();
341         } else {
342           return (GenericNodeRealizer) realizer;
343         }
344       }
345     }
346   }
347 }