1 /*
   2  * Copyright (c) 2010, 2015, 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 
  26 package com.sun.javafx.scene.control.behavior;
  27 
  28 import com.sun.javafx.util.Utils;
  29 import javafx.animation.KeyFrame;
  30 import javafx.animation.Timeline;
  31 import javafx.event.ActionEvent;
  32 import javafx.event.EventHandler;
  33 import javafx.geometry.Orientation;
  34 import javafx.scene.control.ScrollBar;
  35 import javafx.scene.control.Skin;
  36 import com.sun.javafx.scene.control.inputmap.InputMap;
  37 import javafx.util.Duration;
  38 
  39 import static javafx.scene.input.KeyCode.*;
  40 import static javafx.scene.input.KeyEvent.KEY_RELEASED;
  41 
  42 /**
  43  * A Behavior implementation for ScrollBars.
  44  *
  45  */
  46 
  47 public class ScrollBarBehavior extends BehaviorBase<ScrollBar> {
  48 
  49     private final InputMap<ScrollBar> inputMap;
  50 
  51     /***************************************************************************
  52      *                                                                         *
  53      * Constructors                                                            *
  54      *                                                                         *
  55      **************************************************************************/
  56 
  57     public ScrollBarBehavior(ScrollBar scrollBar) {
  58         super(scrollBar);
  59 
  60         // create a map for scrollbar-specific mappings (this reuses the default
  61         // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
  62         inputMap = createInputMap();
  63 
  64         // scrollbar-specific mappings for key and mouse input
  65         addDefaultMapping(inputMap,
  66             new InputMap.KeyMapping(HOME, KEY_RELEASED, e -> home()),
  67             new InputMap.KeyMapping(END, KEY_RELEASED, e -> end())
  68         );
  69 
  70         // create two child input maps for horizontal and vertical scrollbars
  71         InputMap<ScrollBar> horizontalInputMap = new InputMap<>(scrollBar);
  72         horizontalInputMap.setInterceptor(e -> scrollBar.getOrientation() != Orientation.HORIZONTAL);
  73         horizontalInputMap.getMappings().addAll(
  74             new InputMap.KeyMapping(LEFT, e -> rtl(scrollBar, this::incrementValue, this::decrementValue)),
  75             new InputMap.KeyMapping(KP_LEFT, e -> rtl(scrollBar, this::incrementValue, this::decrementValue)),
  76             new InputMap.KeyMapping(RIGHT, e -> rtl(scrollBar, this::decrementValue, this::incrementValue)),
  77             new InputMap.KeyMapping(KP_RIGHT, e -> rtl(scrollBar, this::decrementValue, this::incrementValue))
  78         );
  79         addDefaultChildMap(inputMap, horizontalInputMap);
  80 
  81         InputMap<ScrollBar> verticalInputMap = new InputMap<>(scrollBar);
  82         verticalInputMap.setInterceptor(e -> scrollBar.getOrientation() != Orientation.VERTICAL);
  83         verticalInputMap.getMappings().addAll(
  84                 new InputMap.KeyMapping(UP, e -> decrementValue()),
  85                 new InputMap.KeyMapping(KP_UP, e -> decrementValue()),
  86                 new InputMap.KeyMapping(DOWN, e -> incrementValue()),
  87                 new InputMap.KeyMapping(KP_DOWN, e -> incrementValue())
  88         );
  89         addDefaultChildMap(inputMap, verticalInputMap);
  90     }
  91 
  92     /***************************************************************************
  93      *                                                                         *
  94      * Functions                                                               *
  95      *                                                                         *
  96      **************************************************************************/
  97 
  98 
  99     @Override public InputMap<ScrollBar> getInputMap() {
 100         return inputMap;
 101     }
 102     private void home() {
 103         getNode().setValue(getNode().getMin());
 104     }
 105 
 106     private void decrementValue() {
 107         getNode().adjustValue(0);
 108     }
 109 
 110     private void end() {
 111         getNode().setValue(getNode().getMax());
 112     }
 113 
 114     private void incrementValue() {
 115         getNode().adjustValue(1);
 116     }
 117 
 118 
 119     /***************************************************************************
 120      *                                                                         *
 121      * Mouse event handling                                                    *
 122      *                                                                         *
 123      **************************************************************************/
 124 
 125     /**
 126      * This timeline is used to adjust the value of the bar when the
 127      * track has been pressed but not released.
 128      */
 129     Timeline timeline;
 130 
 131     /**
 132      * Invoked by the ScrollBar {@link Skin} implementation whenever a mouse
 133      * press occurs on the "track" of the bar. This will cause the thumb to
 134      * be moved by some amount.
 135      *
 136      * @param position The mouse position on track with 0.0 being beginning of track and 1.0 being the end
 137      */
 138     public void trackPress(double position) {
 139 
 140         /* We can get a press if someone presses an end button.  In that
 141          * case, we don't want to start a timeline because the end button
 142          * will have already done so.  We can detect that because the timeline
 143          * will not be null.
 144          */
 145         if (timeline != null) return;
 146 
 147         // determine the percentage of the way between min and max
 148         // represented by this mouse event
 149         final ScrollBar bar = getNode();
 150         if (!bar.isFocused() && bar.isFocusTraversable()) bar.requestFocus();
 151         final double pos = position;
 152         final boolean incrementing = (pos > ((bar.getValue() - bar.getMin())/(bar.getMax() - bar.getMin())));
 153         timeline = new Timeline();
 154         timeline.setCycleCount(Timeline.INDEFINITE);
 155 
 156         final EventHandler<ActionEvent> step =
 157                 event -> {
 158                     boolean i = (pos > ((bar.getValue() - bar.getMin())/(bar.getMax() - bar.getMin())));
 159                     if (incrementing == i) {
 160                         // we started incrementing and still are, or we
 161                         // started decrementing and still are
 162                         bar.adjustValue(pos);
 163                     }
 164                     else {
 165                         stopTimeline();
 166                     }
 167                 };
 168 
 169         final KeyFrame kf = new KeyFrame(Duration.millis(200), step);
 170         timeline.getKeyFrames().add(kf);
 171         // do the first step immediately
 172         timeline.play();
 173         step.handle(null);
 174     }
 175 
 176     /**
 177      */
 178     public void trackRelease() {
 179         stopTimeline();
 180     }
 181 
 182     /**
 183      * Invoked by the ScrollBar {@link Skin} implementation whenever a mouse
 184      * press occurs on the decrement button of the bar.
 185      */
 186     public void decButtonPressed() {
 187         final ScrollBar bar = getNode();
 188         if (!bar.isFocused() && bar.isFocusTraversable()) bar.requestFocus();
 189         stopTimeline();
 190         timeline = new Timeline();
 191         timeline.setCycleCount(Timeline.INDEFINITE);
 192 
 193         final EventHandler<ActionEvent> dec =
 194                 event -> {
 195                     if (bar.getValue() > bar.getMin()) {
 196                         bar.decrement();
 197                     }
 198                     else {
 199                         stopTimeline();
 200                     }
 201                 };
 202 
 203         final KeyFrame kf = new KeyFrame(Duration.millis(200), dec);
 204         timeline.getKeyFrames().add(kf);
 205         // do the first step immediately
 206         timeline.play();
 207         dec.handle(null);
 208     }
 209 
 210     /**
 211      */
 212     public void decButtonReleased() {
 213         stopTimeline();
 214     }
 215 
 216     /**
 217      * Invoked by the ScrollBar {@link Skin} implementation whenever a mouse
 218      * press occurs on the increment button of the bar.
 219      */
 220     public void incButtonPressed() {
 221         final ScrollBar bar = getNode();
 222         if (!bar.isFocused() && bar.isFocusTraversable()) bar.requestFocus();
 223         stopTimeline();
 224         timeline = new Timeline();
 225         timeline.setCycleCount(Timeline.INDEFINITE);
 226 
 227         final EventHandler<ActionEvent> inc =
 228                 event -> {
 229                     if (bar.getValue() < bar.getMax()) {
 230                         bar.increment();
 231                     }
 232                     else {
 233                         stopTimeline();
 234                     }
 235                 };
 236 
 237         final KeyFrame kf = new KeyFrame(Duration.millis(200), inc);
 238         timeline.getKeyFrames().add(kf);
 239         // do the first step immediately
 240         timeline.play();
 241         inc.handle(null);
 242     }
 243 
 244     /**
 245      */
 246     public void incButtonReleased() {
 247         stopTimeline();
 248     }
 249 
 250     /**
 251      * @param position The mouse position on track with 0.0 being begining of track and 1.0 being the end
 252      */
 253     //public function thumbPressed(e:MouseEvent, position:Number):Void {
 254     //}
 255 
 256     /**
 257      * @param position The mouse position on track with 0.0 being begining of track and 1.0 being the end
 258      */
 259     public void thumbDragged(double position) {
 260         final ScrollBar scrollbar = getNode();
 261 
 262         // Stop the timeline for continuous increments as drags take precedence
 263         stopTimeline();
 264 
 265         if (!scrollbar.isFocused() && scrollbar.isFocusTraversable()) scrollbar.requestFocus();
 266         double newValue = (position * (scrollbar.getMax() - scrollbar.getMin())) + scrollbar.getMin();
 267         if (!Double.isNaN(newValue)) {
 268             scrollbar.setValue(Utils.clamp(scrollbar.getMin(), newValue, scrollbar.getMax()));
 269         }
 270     }
 271 
 272     private void stopTimeline() {
 273         if (timeline != null) {
 274             timeline.stop();
 275             timeline = null;
 276         }
 277     }
 278 }