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 }