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 javafx.event.EventType; 29 import javafx.geometry.NodeOrientation; 30 import javafx.geometry.Orientation; 31 import javafx.scene.control.Control; 32 import javafx.scene.control.Skin; 33 import javafx.scene.control.Slider; 34 import javafx.scene.input.KeyCode; 35 import javafx.scene.input.KeyEvent; 36 import javafx.scene.input.MouseEvent; 37 import java.util.ArrayList; 38 import java.util.List; 39 import com.sun.javafx.util.Utils; 40 import static javafx.scene.input.KeyCode.DOWN; 41 import static javafx.scene.input.KeyCode.END; 42 import static javafx.scene.input.KeyCode.F4; 43 import static javafx.scene.input.KeyCode.HOME; 44 import static javafx.scene.input.KeyCode.KP_DOWN; 45 import static javafx.scene.input.KeyCode.KP_LEFT; 46 import static javafx.scene.input.KeyCode.KP_RIGHT; 47 import static javafx.scene.input.KeyCode.KP_UP; 48 import static javafx.scene.input.KeyCode.LEFT; 49 import static javafx.scene.input.KeyCode.RIGHT; 50 import static javafx.scene.input.KeyCode.UP; 51 import static javafx.scene.input.KeyEvent.KEY_RELEASED; 52 53 public class SliderBehavior extends BehaviorBase<Slider> { 54 /************************************************************************** 55 * Setup KeyBindings * 56 * * 57 * We manually specify the focus traversal keys because Slider has * 58 * different usage for up/down arrow keys. * 59 *************************************************************************/ 60 protected static final List<KeyBinding> SLIDER_BINDINGS = new ArrayList<KeyBinding>(); 61 static { 62 SLIDER_BINDINGS.add(new KeyBinding(F4, "TraverseDebug").alt().ctrl().shift()); 63 64 SLIDER_BINDINGS.add(new SliderKeyBinding(LEFT, "DecrementValue")); 65 SLIDER_BINDINGS.add(new SliderKeyBinding(KP_LEFT, "DecrementValue")); 66 SLIDER_BINDINGS.add(new SliderKeyBinding(UP, "IncrementValue").vertical()); 67 SLIDER_BINDINGS.add(new SliderKeyBinding(KP_UP, "IncrementValue").vertical()); 68 SLIDER_BINDINGS.add(new SliderKeyBinding(RIGHT, "IncrementValue")); 69 SLIDER_BINDINGS.add(new SliderKeyBinding(KP_RIGHT, "IncrementValue")); 70 SLIDER_BINDINGS.add(new SliderKeyBinding(DOWN, "DecrementValue").vertical()); 71 SLIDER_BINDINGS.add(new SliderKeyBinding(KP_DOWN, "DecrementValue").vertical()); 72 73 SLIDER_BINDINGS.add(new KeyBinding(HOME, KEY_RELEASED, "Home")); 74 SLIDER_BINDINGS.add(new KeyBinding(END, KEY_RELEASED, "End")); 75 } 76 77 protected /*final*/ String matchActionForEvent(KeyEvent e) { 78 String action = super.matchActionForEvent(e); 79 if (action != null) { 80 if (e.getCode() == LEFT || e.getCode() == KP_LEFT) { 81 if (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 82 action = getControl().getOrientation() == Orientation.HORIZONTAL ? "IncrementValue" : "DecrementValue"; 83 } 84 } else if (e.getCode() == RIGHT || e.getCode() == KP_RIGHT) { 85 if (getControl().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { 86 action = getControl().getOrientation() == Orientation.HORIZONTAL ? "DecrementValue" : "IncrementValue"; 87 } 88 } 89 } 90 return action; 91 } 92 93 @Override 94 protected void callAction(String name) { 95 if ("Home".equals(name)) home(); 96 else if ("End".equals(name)) end(); 97 else if ("IncrementValue".equals(name)) incrementValue(); 98 else if ("DecrementValue".equals(name)) decrementValue(); 99 else super.callAction(name); 100 } 101 102 private TwoLevelFocusBehavior tlFocus; 103 104 public SliderBehavior(Slider slider) { 105 super(slider, SLIDER_BINDINGS); 106 // Only add this if we're on an embedded platform that supports 5-button navigation 107 if (com.sun.javafx.scene.control.skin.Utils.isTwoLevelFocus()) { 108 tlFocus = new TwoLevelFocusBehavior(slider); // needs to be last. 109 } 110 } 111 112 @Override public void dispose() { 113 if (tlFocus != null) tlFocus.dispose(); 114 super.dispose(); 115 } 116 117 /************************************************************************** 118 * State and Functions * 119 *************************************************************************/ 120 121 /** 122 * Invoked by the Slider {@link Skin} implementation whenever a mouse press 123 * occurs on the "track" of the slider. This will cause the thumb to be 124 * moved by some amount. 125 * 126 * @param position The mouse position on track with 0.0 being beginning of 127 * track and 1.0 being the end 128 */ 129 public void trackPress(MouseEvent e, double position) { 130 // determine the percentage of the way between min and max 131 // represented by this mouse event 132 final Slider slider = getControl(); 133 // If not already focused, request focus 134 if (!slider.isFocused()) slider.requestFocus(); 135 if (slider.getOrientation().equals(Orientation.HORIZONTAL)) { 136 slider.adjustValue(position * (slider.getMax() - slider.getMin()) + slider.getMin()); 137 } else { 138 slider.adjustValue((1-position) * (slider.getMax() - slider.getMin()) + slider.getMin()); 139 } 140 } 141 142 /** 143 * @param position The mouse position on track with 0.0 being beginning of 144 * track and 1.0 being the end 145 */ 146 public void thumbPressed(MouseEvent e, double position) { 147 // If not already focused, request focus 148 final Slider slider = getControl(); 149 if (!slider.isFocused()) slider.requestFocus(); 150 slider.setValueChanging(true); 151 } 152 153 /** 154 * @param position The mouse position on track with 0.0 being beginning of 155 * track and 1.0 being the end 156 */ 157 public void thumbDragged(MouseEvent e, double position) { 158 final Slider slider = getControl(); 159 slider.setValue(Utils.clamp(slider.getMin(), (position * (slider.getMax() - slider.getMin())) + slider.getMin(), slider.getMax())); 160 } 161 162 private double snapValueToTicks(double val) { 163 final Slider slider = getControl(); 164 double v = val; 165 double tickSpacing = 0; 166 // compute the nearest tick to this value 167 if (slider.getMinorTickCount() != 0) { 168 tickSpacing = slider.getMajorTickUnit() / (Math.max(slider.getMinorTickCount(),0)+1); 169 } else { 170 tickSpacing = slider.getMajorTickUnit(); 171 } 172 int prevTick = (int)((v - slider.getMin())/ tickSpacing); 173 double prevTickValue = (prevTick) * tickSpacing + slider.getMin(); 174 double nextTickValue = (prevTick + 1) * tickSpacing + slider.getMin(); 175 v = Utils.nearest(prevTickValue, v, nextTickValue); 176 return Utils.clamp(slider.getMin(), v, slider.getMax()); 177 } 178 179 180 /** 181 * When thumb is released valueChanging should be set to false. 182 */ 183 public void thumbReleased(MouseEvent e) { 184 final Slider slider = getControl(); 185 slider.setValueChanging(false); 186 // RT-15207 When snapToTicks is true, slider value calculated in drag 187 // is then snapped to the nearest tick on mouse release. 188 if (slider.isSnapToTicks()) { 189 slider.setValue(snapValueToTicks(slider.getValue())); 190 } 191 } 192 193 void home() { 194 final Slider slider = getControl(); 195 slider.adjustValue(slider.getMin()); 196 } 197 198 void decrementValue() { 199 final Slider slider = getControl(); 200 // RT-8634 If snapToTicks is true and block increment is less than 201 // tick spacing, tick spacing is used as the decrement value. 202 if (slider.isSnapToTicks()) { 203 slider.adjustValue(slider.getValue() - computeIncrement()); 204 } else { 205 slider.decrement(); 206 } 207 208 } 209 210 void end() { 211 final Slider slider = getControl(); 212 slider.adjustValue(slider.getMax()); 213 } 214 215 void incrementValue() { 216 final Slider slider = getControl(); 217 // RT-8634 If snapToTicks is true and block increment is less than 218 // tick spacing, tick spacing is used as the increment value. 219 if (slider.isSnapToTicks()) { 220 slider.adjustValue(slider.getValue()+ computeIncrement()); 221 } else { 222 slider.increment(); 223 } 224 } 225 226 // Used only if snapToTicks is true. 227 double computeIncrement() { 228 final Slider slider = getControl(); 229 double tickSpacing = 0; 230 if (slider.getMinorTickCount() != 0) { 231 tickSpacing = slider.getMajorTickUnit() / (Math.max(slider.getMinorTickCount(),0)+1); 232 } else { 233 tickSpacing = slider.getMajorTickUnit(); 234 } 235 236 if (slider.getBlockIncrement() > 0 && slider.getBlockIncrement() < tickSpacing) { 237 return tickSpacing; 238 } 239 240 return slider.getBlockIncrement(); 241 } 242 243 public static class SliderKeyBinding extends OrientedKeyBinding { 244 public SliderKeyBinding(KeyCode code, String action) { 245 super(code, action); 246 } 247 248 public SliderKeyBinding(KeyCode code, EventType<KeyEvent> type, String action) { 249 super(code, type, action); 250 } 251 252 public @Override boolean getVertical(Control control) { 253 return ((Slider)control).getOrientation() == Orientation.VERTICAL; 254 } 255 } 256 257 }