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 }