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