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.orgchart;
29  
30  import y.anim.AnimationObject;
31  import y.anim.AnimationPlayer;
32  import y.util.DefaultMutableValue2D;
33  import y.view.Graph2DView;
34  import y.view.ViewAnimationFactory;
35  
36  import javax.swing.Timer;
37  import java.awt.Rectangle;
38  import java.awt.event.ActionEvent;
39  import java.awt.event.ActionListener;
40  import java.awt.event.KeyAdapter;
41  import java.awt.event.KeyEvent;
42  import java.awt.event.KeyListener;
43  import java.awt.geom.Point2D;
44  
45  /**
46   * Factory class that provides {@link java.awt.event.KeyListener} implementations suitable for
47   * navigating within Graph2DView.
48   */
49  public class KeyboardNavigation {
50  
51    private Graph2DView view;
52    
53    /**
54     * Creates a KeyboardNavigation for the given view.
55     * @param view given view
56     */
57    public KeyboardNavigation(final Graph2DView view) {
58      this.view = view;
59    }
60    
61    /**
62     * Creates and returns a KeyListener that zooms into the view
63     * while keyCode1 or keyCode2 is being pressed.
64     */
65    public KeyListener createZoomInKeyListener(final int keyCode1, final int keyCode2 ) 
66    {
67      return new ZoomTrigger(true, keyCode1, keyCode2);
68    }
69  
70  
71    /**
72     * Creates and returns a KeyListener that zooms out of the view
73     * while keyCode1 or keyCode2 is being pressed.
74     */
75    public KeyListener createZoomOutKeyListener(final int keyCode1, final int keyCode2 ) 
76    {
77      return new ZoomTrigger(false, keyCode1, keyCode2);
78    }
79  
80    /**
81     * Creates and returns a KeyListener that moves the view port up
82     * while keyCode is being pressed.
83     */
84    public KeyListener createMoveViewportUpKeyListener(final int keyCode) 
85    {
86      return new MoveViewportTrigger(MoveViewportTrigger.UP, keyCode);        
87    }
88  
89    /**
90     * Creates and returns a KeyListener that moves the view port down
91     * while keyCode is being pressed.
92     */
93    public KeyListener createMoveViewportDownKeyListener(final int keyCode) 
94    {
95      return new MoveViewportTrigger(MoveViewportTrigger.DOWN, keyCode);
96    }
97    
98  
99    /**
100    * Creates and returns a KeyListener that moves the view port to the left
101    * while keyCode is being pressed.
102    */
103   public KeyListener createMoveViewportLeftKeyListener(final int keyCode) 
104   {
105     return new MoveViewportTrigger(MoveViewportTrigger.LEFT, keyCode);
106   }
107 
108   /**
109    * Creates and returns a KeyListener that moves the view port to the right
110    * while keyCode is being pressed.
111    */
112   public KeyListener createMoveViewportRightKeyListener(final int keyCode) 
113   {
114     return new MoveViewportTrigger(MoveViewportTrigger.RIGHT, keyCode);
115   }
116   
117   /**
118    * KeyListener base used for all implementation provided by this class.
119    * It uses the yFiles animation framework to perform smooth navigation
120    * effects.
121    */
122   private abstract class AnimationTrigger extends KeyAdapter {
123     private AnimationPlayer player;
124     private DisarmableAnimationWrapper wrapper;
125 
126     private final int keyCode1;
127     private final int keyCode2;
128     private final Timer timer;
129 
130     AnimationTrigger( final int keyCode1, final int keyCode2  ) {
131       this.keyCode1 = keyCode1;
132       this.keyCode2 = keyCode2;
133       this.timer = new Timer(0, new ActionListener() {
134         public void actionPerformed( final ActionEvent e ) {
135           if (player != null && player.isPlaying()) {
136             wrapper.disarm();
137             player.stop();
138           }
139         }
140       });
141       this.timer.setInitialDelay(50);
142       this.timer.setRepeats(false);
143     }
144 
145     public void keyPressed( final KeyEvent e ) {
146       if (player == null) {
147         player = new AnimationPlayer(false);
148         player.addAnimationListener(view);
149       }
150 
151       final int keyCode = e.getKeyCode();
152       if ((keyCode1 == keyCode || keyCode2 == keyCode) &&
153           (0 == e.getModifiersEx())) {
154         timer.stop();
155         if (!player.isPlaying()) {
156           wrapper = new DisarmableAnimationWrapper(createAnimation());
157           if (wrapper.preferredDuration() > 0) {
158             player.animate(wrapper);
159           }
160         }
161       }
162     }
163 
164     public void keyReleased( final KeyEvent e ) {
165       final int keyCode = e.getKeyCode();
166       if ((keyCode1 == keyCode || keyCode2 == keyCode) &&
167           (0 == e.getModifiersEx())) {
168         if (!timer.isRunning()) {
169           timer.restart();
170         }
171       }
172     }
173 
174     abstract AnimationObject createAnimation();
175   }
176 
177   /**
178    * KeyListener implementation that performs zooming
179    */
180   class ZoomTrigger extends AnimationTrigger {
181     private ViewAnimationFactory factory;
182     private final boolean zoomIn;
183 
184     ZoomTrigger( final boolean zoomIn, final int keyCode1, final int keyCode2 ) {
185       super(keyCode1, keyCode2);
186       this.zoomIn = zoomIn;
187     }
188 
189     AnimationObject createAnimation() {
190       final double newZoom = calculateZoom();
191       if ((zoomIn && newZoom > view.getZoom()) || (!zoomIn && newZoom < view.getZoom())) {
192         if (factory == null) {
193           factory = new ViewAnimationFactory(view);
194         }
195         return factory.zoom(newZoom, ViewAnimationFactory.APPLY_EFFECT, 1000);
196       } else {
197         return null;
198       }
199     }
200 
201     private double calculateZoom() {
202       if (zoomIn) {
203         return 4;
204       } else {
205         final Point2D oldP = view.getViewPoint2D();
206         final double oldZoom = view.getZoom();
207         view.fitContent();
208         final double fitContentZoom = view.getZoom();
209         view.setZoom(oldZoom);
210         view.setViewPoint2D(oldP.getX(), oldP.getY());
211         return fitContentZoom;
212       }
213     }
214   }
215 
216 
217   /**
218    * KeyListener implementation that moves the view port of the view
219    */
220   private class MoveViewportTrigger extends AnimationTrigger {
221     static final int LEFT = 0;
222     static final int RIGHT = 1;
223     static final int UP = 2;
224     static final int DOWN = 3;
225 
226     private int direction;
227     private ViewAnimationFactory factory;
228 
229     MoveViewportTrigger( final int direction, final int keyCode ) {
230       super(keyCode, -1);
231       this.direction = direction;
232     }
233 
234     AnimationObject createAnimation() {
235       final Rectangle bx = view.getGraph2D().getBoundingBox();
236       final double dx = Math.max(bx.getWidth(), 10000);
237       final double dy = Math.max(bx.getHeight(), 10000);
238 
239       final Point2D oldCenter = view.getCenter();
240       final DefaultMutableValue2D newCenter =
241               DefaultMutableValue2D.create(oldCenter.getX(), oldCenter.getY());
242 
243       double dist = 0;
244       switch (direction) {
245         case LEFT:
246           newCenter.setX(bx.getX() - dx);
247           dist = oldCenter.getX() - newCenter.getX();
248           break;
249         case RIGHT:
250           newCenter.setX(bx.getX() + bx.getWidth() + dx);
251           dist = newCenter.getX() - oldCenter.getX();
252           break;
253         case UP:
254           newCenter.setY(bx.getY() - dy);
255           dist = oldCenter.getY() - newCenter.getY();
256           break;
257         case DOWN:
258           newCenter.setY(bx.getY() + bx.getHeight() + dy);
259           dist = newCenter.getY() - oldCenter.getY();
260           break;
261       }
262 
263       if (dist > 1e-4) {
264         if (factory == null) {
265           factory = new ViewAnimationFactory(view);
266         }
267         return factory.moveCamera(newCenter, (long) Math.rint(dist * view.getZoom()));
268       } else {
269         return null;
270       }
271     }
272   }
273   
274   /**
275    * Animation object wrapper that can be used to disarm an AnimationObject during execution.
276    */
277   private static final class DisarmableAnimationWrapper implements AnimationObject {
278     private AnimationObject animation;
279 
280     private boolean armed;
281 
282     DisarmableAnimationWrapper( final AnimationObject animation ) {
283       this.animation = animation;
284       this.armed = true;
285     }
286 
287     public void initAnimation() {
288       if (armed && animation != null) {
289         animation.initAnimation();
290       }
291     }
292 
293     public void calcFrame( final double time ) {
294       if (armed && animation != null) {
295         animation.calcFrame(time);
296       }
297     }
298 
299     public void disposeAnimation() {
300       if (armed && animation != null) {
301         animation.disposeAnimation();
302       }
303     }
304 
305     public long preferredDuration() {
306       return animation != null ? animation.preferredDuration() : 0;
307     }
308 
309     void disarm() {
310       armed = false;
311     }
312   }
313 
314 }
315