modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/SliderBehavior.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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 }


   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 }