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 }