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