1 /*
2 * Copyright (c) 2010, 2014, 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.application.ConditionalFeature;
29 import javafx.application.Platform;
30 import javafx.beans.InvalidationListener;
31 import javafx.beans.Observable;
32 import javafx.event.EventHandler;
33 import javafx.scene.Node;
34 import javafx.scene.control.Control;
35 import javafx.scene.input.ContextMenuEvent;
36 import javafx.scene.input.KeyEvent;
37 import javafx.scene.input.MouseEvent;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 import com.sun.javafx.scene.traversal.Direction;
42 import static javafx.scene.input.KeyCode.DOWN;
43 import static javafx.scene.input.KeyCode.LEFT;
44 import static javafx.scene.input.KeyCode.RIGHT;
45 import static javafx.scene.input.KeyCode.TAB;
46 import static javafx.scene.input.KeyCode.UP;
47
48 /**
49 * A convenient base class from which all our built-in behaviors extend. The
50 * main functionality in BehaviorBase revolves around infrastructure for
51 * resolving key events into function calls. The differences between platforms
52 * can be subtle, and we attempt to build sufficient infrastructure into
53 * BehaviorBase to minimize the amount of code and the complexity of code
54 * necessary to support multiple platforms sufficiently well.
55 *
56 * <p>Although BehaviorBase is typically used as a base class, it is not abstract and
57 * several skins instantiate an instance of BehaviorBase directly.</p>
58 *
59 * <p>BehaviorBase also implements the hooks for focus traversal. This
60 * implementation is sufficient for most subclasses of BehaviorBase. The
61 * following action names are registered in the keyMap for handling focus
62 * traversal. Subclasses which need to invoke focus traversal using non-standard
63 * key strokes should map key strokes to these action names:</p>
64 * <ul>
65 * <li>TraverseUp</li>
66 * <li>TraverseDown</li>
67 * <li>TraverseLeft</li>
68 * <li>TraverseRight</li>
69 * <li>TraverseNext</li>
70 * <li>TraversePrevious</li>
71 * </ul>
72 *
73 * <p>Note that by convention, action names are camel case with the first letter
74 * uppercase, matching class naming conventions.</p>
75 */
76 public class BehaviorBase<C extends Control> {
77 /**
78 * A static final reference to whether the platform we are on supports touch.
79 */
80 protected final static boolean IS_TOUCH_SUPPORTED = Platform.isSupported(ConditionalFeature.INPUT_TOUCH);
81
82 /**
83 * The default key bindings for focus traversal. For many behavior
84 * implementations, you may be able to use this directly. The built in names
85 * for these traversal actions are:
86 * <ul>
87 * <li>TraverseUp</li>
88 * <li>TraverseDown</li>
89 * <li>TraverseLeft</li>
90 * <li>TraverseRight</li>
91 * <li>TraverseNext</li>
92 * <li>TraversePrevious</li>
93 * </ul>
94 */
95 protected static final List<KeyBinding> TRAVERSAL_BINDINGS = new ArrayList<>();
96 static final String TRAVERSE_UP = "TraverseUp";
97 static final String TRAVERSE_DOWN = "TraverseDown";
98 static final String TRAVERSE_LEFT = "TraverseLeft";
99 static final String TRAVERSE_RIGHT = "TraverseRight";
100 static final String TRAVERSE_NEXT = "TraverseNext";
101 static final String TRAVERSE_PREVIOUS = "TraversePrevious";
102
103 static {
104 TRAVERSAL_BINDINGS.add(new KeyBinding(UP, TRAVERSE_UP));
105 TRAVERSAL_BINDINGS.add(new KeyBinding(DOWN, TRAVERSE_DOWN));
106 TRAVERSAL_BINDINGS.add(new KeyBinding(LEFT, TRAVERSE_LEFT));
107 TRAVERSAL_BINDINGS.add(new KeyBinding(RIGHT, TRAVERSE_RIGHT));
108 TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_NEXT));
109 TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_PREVIOUS).shift());
110
111 TRAVERSAL_BINDINGS.add(new KeyBinding(UP, TRAVERSE_UP).shift().alt().ctrl());
112 TRAVERSAL_BINDINGS.add(new KeyBinding(DOWN, TRAVERSE_DOWN).shift().alt().ctrl());
113 TRAVERSAL_BINDINGS.add(new KeyBinding(LEFT, TRAVERSE_LEFT).shift().alt().ctrl());
114 TRAVERSAL_BINDINGS.add(new KeyBinding(RIGHT, TRAVERSE_RIGHT).shift().alt().ctrl());
115 TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_NEXT).shift().alt().ctrl());
116 TRAVERSAL_BINDINGS.add(new KeyBinding(TAB, TRAVERSE_PREVIOUS).alt().ctrl());
117 }
118
119 /**
120 * The Control with which this Behavior is used. This must be specified in
121 * the constructor and must not be null.
122 */
123 private final C control;
124
125 /**
126 * The key bindings for this Behavior.
127 */
128 private final List<KeyBinding> keyBindings;
129
130 /**
131 * Listens to any key events on the Control and responds to them
132 */
133 private final EventHandler<KeyEvent> keyEventListener = e -> {
134 if (!e.isConsumed()) {
135 callActionForEvent(e);
136 }
137 };
138
139 /**
140 * Listens to any focus events on the Control and calls protected methods as a result
141 */
142 private final InvalidationListener focusListener = property -> {
143 focusChanged();
144 };
145
146 /**
147 * Create a new BehaviorBase for the given control. The Control must not
148 * be null.
149 *
150 * @param control The control. Must not be null.
151 * @param keyBindings The key bindings that should be used with this behavior.
152 * Null is treated as an empty list.
153 */
154 public BehaviorBase(final C control, final List<KeyBinding> keyBindings) {
155 // Don't need to explicitly check for null because Collections.unmodifiableList
156 // will die on null, as will the adding of listeners
157 this.control = control;
158 this.keyBindings = keyBindings == null ? Collections.emptyList()
159 : Collections.unmodifiableList(new ArrayList<>(keyBindings));
160 control.addEventHandler(KeyEvent.ANY, keyEventListener);
161 control.focusedProperty().addListener(focusListener);
162 }
163
164 /**
165 * Called by a Skin when the Skin is disposed. This method
166 * allows a Behavior to implement any logic necessary to clean up itself after
167 * the Behavior is no longer needed. Calling dispose twice has no effect. This
168 * method is intended to be overridden by subclasses, although all subclasses
169 * must call super.dispose() or a potential memory leak will result.
170 */
171 public void dispose() {
172 control.removeEventHandler(KeyEvent.ANY, keyEventListener);
173 control.focusedProperty().removeListener(focusListener);
174 }
175
176 /***************************************************************************
177 * Implementation of the Behavior "interface" *
178 * *
179 * One of the specialized duties of the behavior is to react to key *
180 * events. The behavior breaks the handling of a key event down into a few *
181 * distinct stages. First, the BehaviorBase will analyze the key event and *
182 * find the String name of a matching action to invoke, if any. If an *
183 * action exists for this event, the name is then fed to *
184 * callActionForEvent(name), which will then invoke an actual method on *
185 * the behavior that is the implementation of that action. *
186 * *
187 * The reason for returning the intermediate action name as a String is *
188 * twofold. First, the matching is done by analyzing a set of key bindings *
189 * which are *statically declared* on each behavior class. The fact that *
190 * they are static means that we cannot refer to an actual instance method *
191 * to invoke (such as with lambda's). It is also important that these are *
192 * static to reduce the memory footprint of a control (since having *
193 * per-instance key bindings would add a lot to memory footprint). We *
194 * could have used something other than String as the intermediate token, *
195 * however String is useful if we ever want to expose to developers a way *
196 * to alter the action map from a property file or XML file etc. *
197 * *
198 **************************************************************************/
199
200 /**
201 * Gets the control associated with this behavior. Even after the BehaviorBase is
202 * disposed, this reference will be non-null.
203 *
204 * @return The control for this Behavior.
205 */
206 public final C getControl() { return control; }
207
208 /**
209 * Invokes the appropriate action for this key event. This is the main entry point where
210 * key events are passed when they occur. This method is responsible for invoking
211 * matchActionForEvent, callAction, and consuming the event if it was handled by this control.
212 *
213 * @param e The key event. Must not be null.
214 */
215 protected void callActionForEvent(KeyEvent e) {
216 String action = matchActionForEvent(e);
217 if (action != null) {
218 callAction(action);
219 e.consume();
220 }
221 }
222
223 /**
224 * Given a key event, this method will find the matching action name, or null if there
225 * is not one.
226 *
227 * @param e The key event. Must not be null.
228 * @return The name of the action to invoke, or null if there is not one.
229 */
230 protected String matchActionForEvent(final KeyEvent e) {
231 if (e == null) throw new NullPointerException("KeyEvent must not be null");
232 KeyBinding match = null;
233 int specificity = 0;
234 int maxBindings = keyBindings.size();
235 for (int i = 0; i < maxBindings; i++) {
236 KeyBinding binding = keyBindings.get(i);
237 int s = binding.getSpecificity(control, e);
238 if (s > specificity) {
239 specificity = s;
240 match = binding;
241 }
242 }
243 String action = null;
244 if (match != null) {
245 action = match.getAction();
246 }
247 return action;
248 }
249
250 /**
251 * Called to invoke the action associated with the given name.
252 *
253 * <p>When a KeyEvent is handled, it is first passed through
254 * callActionForEvent which resolves which "action" should be executed
255 * based on the key event. This action is indicated by name. This name is
256 * then passed to this function which is responsible for invoking the right
257 * function based on the name.</p>
258 */
259 protected void callAction(String name) {
260 switch (name) {
261 case TRAVERSE_UP: traverseUp(); break;
262 case TRAVERSE_DOWN: traverseDown(); break;
263 case TRAVERSE_LEFT: traverseLeft(); break;
264 case TRAVERSE_RIGHT: traverseRight(); break;
265 case TRAVERSE_NEXT: traverseNext(); break;
266 case TRAVERSE_PREVIOUS: traversePrevious(); break;
267 }
268 }
269
270 /***************************************************************************
271 * Focus Traversal methods *
272 **************************************************************************/
273
274 /**
275 * Called by any of the BehaviorBase traverse methods to actually effect a
276 * traversal of the focus. The default behavior of this method is to simply
277 * call impl_traverse on the given node, passing the given direction. A
278 * subclass may override this method.
279 *
280 * @param node The node to call impl_traverse on
281 * @param dir The direction to traverse
282 */
283 protected void traverse(final Node node, final Direction dir) {
284 node.impl_traverse(dir);
285 }
286
287 /**
288 * Calls the focus traversal engine and indicates that traversal should
289 * go the next focusTraversable Node above the current one.
290 */
291 public final void traverseUp() {
292 traverse(control, com.sun.javafx.scene.traversal.Direction.UP);
293 }
294
295 /**
296 * Calls the focus traversal engine and indicates that traversal should
297 * go the next focusTraversable Node below the current one.
298 */
299 public final void traverseDown() {
300 traverse(control, com.sun.javafx.scene.traversal.Direction.DOWN);
301 }
302
303 /**
304 * Calls the focus traversal engine and indicates that traversal should
305 * go the next focusTraversable Node left of the current one.
306 */
307 public final void traverseLeft() {
308 traverse(control, com.sun.javafx.scene.traversal.Direction.LEFT);
309 }
310
311 /**
312 * Calls the focus traversal engine and indicates that traversal should
313 * go the next focusTraversable Node right of the current one.
314 */
315 public final void traverseRight() {
316 traverse(control, com.sun.javafx.scene.traversal.Direction.RIGHT);
317 }
318
319 /**
320 * Calls the focus traversal engine and indicates that traversal should
321 * go the next focusTraversable Node in the focus traversal cycle.
322 */
323 public final void traverseNext() {
324 traverse(control, com.sun.javafx.scene.traversal.Direction.NEXT);
325 }
326
327 /**
328 * Calls the focus traversal engine and indicates that traversal should
329 * go the previous focusTraversable Node in the focus traversal cycle.
330 */
331 public final void traversePrevious() {
332 traverse(control, com.sun.javafx.scene.traversal.Direction.PREVIOUS);
333 }
334
335 /***************************************************************************
336 * Event handler methods. *
337 * *
338 * I'm not sure why only mouse events are here. What about drag and *
339 * drop events for instance? What about touch events? What about the *
340 * other mouse events? It does seem like these need to be here, because *
341 * for example mouse interaction logic might differ from platform to *
342 * platform, and the Behavior is supposed to implement all the user *
343 * interaction logic (not just key handling). So it seems like *
344 * BehaviorBase should have methods for handling all forms of input events,*
345 * and not just these four mouse events. *
346 **************************************************************************/
347
348 /**
349 * Called whenever the focus on the control has changed. This method is
350 * intended to be overridden by subclasses that are interested in focus
351 * change events.
352 */
353 protected void focusChanged() { }
354
355 /**
356 * Invoked by a Skin when the body of the control has been pressed by
357 * the mouse. Subclasses should be sure to call super unless they intend
358 * to disable any built-in support.
359 *
360 * @param e the mouse event
361 */
362 public void mousePressed(MouseEvent e) { }
363
364 /**
365 * Invoked by a Skin when the body of the control has been dragged by
366 * the mouse. Subclasses should be sure to call super unless they intend
367 * to disable any built-in support (for example, for tooltips).
368 *
369 * @param e the mouse event
370 */
371 public void mouseDragged(MouseEvent e) { }
372
373 /**
374 * Invoked by a Skin when the body of the control has been released by
375 * the mouse. Subclasses should be sure to call super unless they intend
376 * to disable any built-in support (for example, for tooltips).
377 *
378 * @param e the mouse event
379 */
380 public void mouseReleased(MouseEvent e) { }
381
382 /**
383 * Invoked by a Skin when the body of the control has been entered by
384 * the mouse. Subclasses should be sure to call super unless they intend
385 * to disable any built-in support.
386 *
387 * @param e the mouse event
388 */
389 public void mouseEntered(MouseEvent e) { }
390
391 /**
392 * Invoked by a Skin when the body of the control has been exited by
393 * the mouse. Subclasses should be sure to call super unless they intend
394 * to disable any built-in support.
395 *
396 * @param e the mouse event
397 */
398 public void mouseExited(MouseEvent e) { }
399
400 /**
401 * Invoked by a Skin when the control has had its context menu requested,
402 * most commonly by right-clicking on the control. Subclasses should be sure
403 * to call super unless they intend to disable any built-in support.
404 *
405 * @param e the context menu event
406 */
407 public void contextMenuRequested(ContextMenuEvent e) { }
408 }
|
1 /*
2 * Copyright (c) 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 package com.sun.javafx.scene.control.behavior;
26
27 import javafx.scene.Node;
28 import com.sun.javafx.scene.control.inputmap.InputMap;
29 import com.sun.javafx.scene.control.inputmap.InputMap.Mapping;
30
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.function.Consumer;
34
35 public abstract class BehaviorBase<N extends Node> {
36
37 private final N node;
38 private final List<Mapping<?>> installedDefaultMappings;
39 private final List<Runnable> childInputMapDisposalHandlers;
40
41
42 public BehaviorBase(N node) {
43 this.node = node;
44 this.installedDefaultMappings = new ArrayList<>();
45 this.childInputMapDisposalHandlers = new ArrayList<>();
46 }
47
48 public abstract InputMap<N> getInputMap();
49
50 public final N getNode() {
51 return node;
52 }
53
54 public void dispose() {
55 // when we dispose a behavior, we do NOT want to dispose the InputMap,
56 // as that can remove input mappings that were not installed by the
57 // behavior. Instead, we want to only remove mappings that the behavior
58 // itself installed. This can be done by removing all input mappings that
59 // were installed via the 'addDefaultMapping' method.
60
61 // remove default mappings only
62 for (Mapping<?> mapping : installedDefaultMappings) {
63 getInputMap().getMappings().remove(mapping);
64 }
65
66 // Remove all default child mappings
67 for (Runnable r : childInputMapDisposalHandlers) {
68 r.run();
69 }
70
71 // InputMap<N> inputMap = getInputMap();
72 // if (inputMap != null) {
73 // inputMap.dispose();
74 // }
75 }
76
77 protected void addDefaultMapping(List<Mapping<?>> newMapping) {
78 addDefaultMapping(getInputMap(), newMapping.toArray(new Mapping[newMapping.size()]));
79 }
80
81 protected void addDefaultMapping(Mapping<?>... newMapping) {
82 addDefaultMapping(getInputMap(), newMapping);
83 }
84
85 protected void addDefaultMapping(InputMap<N> inputMap, Mapping<?>... newMapping) {
86 // make a copy of the existing mappings, so we only check against those
87 List<Mapping<?>> existingMappings = new ArrayList<>(inputMap.getMappings());
88
89 for (Mapping<?> mapping : newMapping) {
90 // check if a mapping already exists, and if so, do not add this mapping
91 // TODO this is insufficient as we need to check entire InputMap hierarchy
92 // for (Mapping<?> existingMapping : existingMappings) {
93 // if (existingMapping != null && existingMapping.equals(mapping)) {
94 // return;
95 // }
96 // }
97 if (existingMappings.contains(mapping)) continue;
98
99 inputMap.getMappings().add(mapping);
100 installedDefaultMappings.add(mapping);
101 }
102 }
103
104 protected <T extends Node> void addDefaultChildMap(InputMap<T> parentInputMap, InputMap<T> newChildInputMap) {
105 parentInputMap.getChildInputMaps().add(newChildInputMap);
106
107 childInputMapDisposalHandlers.add(() -> parentInputMap.getChildInputMaps().remove(newChildInputMap));
108 }
109
110 protected InputMap<N> createInputMap() {
111 // TODO re-enable when InputMap moves back to Node / Control
112 // return node.getInputMap() != null ?
113 // (InputMap<N>)node.getInputMap() :
114 // new InputMap<>(node);
115 return new InputMap<>(node);
116 }
117
118 protected void removeMapping(Object key) {
119 InputMap<?> inputMap = getInputMap();
120 inputMap.lookupMapping(key).ifPresent(mapping -> {
121 inputMap.getMappings().remove(mapping);
122 installedDefaultMappings.remove(mapping);
123 });
124 }
125
126 void rtl(Node node, Runnable rtlMethod, Runnable nonRtlMethod) {
127 switch(node.getEffectiveNodeOrientation()) {
128 case RIGHT_TO_LEFT: rtlMethod.run(); break;
129 default: nonRtlMethod.run(); break;
130 }
131 }
132
133 <T> void rtl(Node node, T object, Consumer<T> rtlMethod, Consumer<T> nonRtlMethod) {
134 switch(node.getEffectiveNodeOrientation()) {
135 case RIGHT_TO_LEFT: rtlMethod.accept(object); break;
136 default: nonRtlMethod.accept(object); break;
137 }
138 }
139
140 boolean isRTL(Node n) {
141 switch(n.getEffectiveNodeOrientation()) {
142 case RIGHT_TO_LEFT: return true;
143 default: return false;
144 }
145 }
146 }
|