1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.javafx.scene.control.behavior;
  26 
  27 import javafx.animation.KeyFrame;
  28 import javafx.animation.Timeline;
  29 import javafx.event.ActionEvent;
  30 import javafx.event.EventHandler;
  31 import javafx.scene.control.Spinner;
  32 import javafx.scene.control.SpinnerValueFactory;
  33 import javafx.util.Duration;
  34 
  35 import java.util.ArrayList;
  36 import java.util.Collections;
  37 import java.util.List;
  38 
  39 import static javafx.scene.input.KeyCode.*;
  40 
  41 public class SpinnerBehavior<T> extends BehaviorBase<Spinner<T>> {
  42 
  43     // this specifies how long the mouse has to be pressed on a button
  44     // before the value steps. As the mouse is held down longer, we begin
  45     // to cut down the duration of subsequent steps (and also increase the
  46     // step size)
  47     private static final double INITIAL_DURATION_MS = 750;
  48 
  49     private final int STEP_AMOUNT = 1;
  50 
  51     private boolean isIncrementing = false;
  52 
  53     private Timeline timeline;
  54 
  55     final EventHandler<ActionEvent> spinningKeyFrameEventHandler = event -> {
  56         final SpinnerValueFactory<T> valueFactory = getControl().getValueFactory();
  57         if (valueFactory == null) {
  58             return;
  59         }
  60 
  61         if (isIncrementing) {
  62             increment(STEP_AMOUNT);
  63         } else {
  64             decrement(STEP_AMOUNT);
  65         }
  66     };
  67 
  68 
  69 
  70     /***************************************************************************
  71      *                                                                         *
  72      * Constructors                                                            *
  73      *                                                                         *
  74      **************************************************************************/
  75 
  76     public SpinnerBehavior(Spinner<T> spinner) {
  77         super(spinner, SPINNER_BINDINGS);
  78     }
  79 
  80 
  81 
  82     /***************************************************************************
  83      *                                                                         *
  84      * Key event handling                                                      *
  85      *                                                                         *
  86      **************************************************************************/
  87 
  88     /**
  89      * Indicates that a keyboard key has been pressed which represents the
  90      * event (this could be space bar for example). As long as keyDown is true,
  91      * we are also armed, and will ignore mouse events related to arming.
  92      * Note this is made package private solely for the sake of testing.
  93      */
  94     private boolean keyDown;
  95 
  96     protected static final List<KeyBinding> SPINNER_BINDINGS = new ArrayList<KeyBinding>();
  97     static {
  98         SPINNER_BINDINGS.add(new KeyBinding(UP, "increment-up"));
  99         SPINNER_BINDINGS.add(new KeyBinding(RIGHT, "increment-right"));
 100         SPINNER_BINDINGS.add(new KeyBinding(LEFT, "decrement-left"));
 101         SPINNER_BINDINGS.add(new KeyBinding(DOWN, "decrement-down"));
 102     }
 103 
 104     @Override protected void callAction(String name) {
 105         boolean vertical = arrowsAreVertical();
 106 
 107         switch (name) {
 108             case "increment-up": {
 109                 if (vertical) increment(1); else traverseUp(); break;
 110             }
 111             case "increment-right": {
 112                 if (! vertical) increment(1); else traverseRight(); break;
 113             }
 114             case "decrement-down": {
 115                 if (vertical) decrement(1); else traverseDown(); break;
 116             }
 117             case "decrement-left": {
 118                 if (! vertical) decrement(1); else traverseLeft(); break;
 119             }
 120             default: super.callAction(name); break;
 121         }
 122     }
 123 
 124 
 125     /***************************************************************************
 126      *                                                                         *
 127      * API                                                                     *
 128      *                                                                         *
 129      **************************************************************************/
 130 
 131     public void increment(int steps) {
 132         getControl().increment(steps);
 133     }
 134 
 135     public void decrement(int steps) {
 136         getControl().decrement(steps);
 137     }
 138 
 139     public void startSpinning(boolean increment) {
 140         isIncrementing = increment;
 141 
 142         if (timeline != null) {
 143             timeline.stop();
 144         }
 145         timeline = new Timeline();
 146         timeline.setCycleCount(Timeline.INDEFINITE);
 147         final KeyFrame kf = new KeyFrame(Duration.millis(INITIAL_DURATION_MS), spinningKeyFrameEventHandler);
 148         timeline.getKeyFrames().setAll(kf);
 149         timeline.playFromStart();
 150         timeline.play();
 151         spinningKeyFrameEventHandler.handle(null);
 152     }
 153 
 154     public void stopSpinning() {
 155         if (timeline != null) {
 156             timeline.stop();
 157         }
 158     }
 159 
 160 
 161 
 162     /***************************************************************************
 163      *                                                                         *
 164      * Implementation                                                          *
 165      *                                                                         *
 166      **************************************************************************/
 167 
 168     private boolean arrowsAreVertical() {
 169         final List<String> styleClass = getControl().getStyleClass();
 170 
 171         return ! (styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL)  ||
 172                   styleClass.contains(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL) ||
 173                   styleClass.contains(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL));
 174     }
 175 }