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 }
|