1 /* 2 * Copyright (c) 2012, 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 com.sun.javafx.scene.NodeHelper; 29 import javafx.scene.Node; 30 31 import javafx.scene.Scene; 32 import javafx.scene.input.KeyEvent; 33 34 import javafx.beans.value.ChangeListener; 35 import javafx.event.Event; 36 import javafx.event.EventDispatcher; 37 import javafx.event.EventHandler; 38 import javafx.scene.input.MouseEvent; 39 40 41 public class TwoLevelFocusComboBehavior extends TwoLevelFocusBehavior { 42 43 public TwoLevelFocusComboBehavior(Node node) { 44 45 tlNode = node; 46 47 // listen to all keyevents, maybe 48 tlNode.addEventHandler(KeyEvent.ANY, keyEventListener); 49 tlNode.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEventListener); 50 tlNode.focusedProperty().addListener(focusListener); 51 52 // block ScrollEvent from being passed down to scrollbar's skin 53 origEventDispatcher = tlNode.getEventDispatcher(); 54 tlNode.setEventDispatcher(tlfEventDispatcher); 55 } 56 57 /** 58 * Invoked by the behavior when it is disposed, so that any listeners installed by 59 * the TwoLevelFocusBehavior can also be uninstalled 60 */ 61 public void dispose() { 62 tlNode.removeEventHandler(KeyEvent.ANY, keyEventListener); 63 tlNode.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseEventListener); 64 tlNode.focusedProperty().removeListener(focusListener); 65 tlNode.setEventDispatcher(origEventDispatcher); 66 } 67 68 /* 69 ** don't allow the Node handle a key event if it is in externalFocus mode. 70 ** the only keyboard actions allowed are the navigation keys...... 71 */ 72 final EventDispatcher preemptiveEventDispatcher = (event, tail) -> { 73 74 // block the event from being passed down to children 75 if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { 76 if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { 77 if (isExternalFocus()) { 78 // 79 // don't let the behaviour leak any navigation keys when 80 // we're not in blocking mode.... 81 // 82 Object obj = event.getTarget(); 83 84 switch (((KeyEvent)event).getCode()) { 85 case TAB : 86 if (((KeyEvent)event).isShiftDown()) { 87 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.PREVIOUS); 88 } 89 else { 90 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.NEXT); 91 } 92 event.consume(); 93 break; 94 case UP : 95 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.UP); 96 event.consume(); 97 break; 98 case DOWN : 99 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.DOWN); 100 event.consume(); 101 break; 102 case LEFT : 103 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.LEFT); 104 event.consume(); 105 break; 106 case RIGHT : 107 NodeHelper.traverse((Node) obj, com.sun.javafx.scene.traversal.Direction.RIGHT); 108 event.consume(); 109 break; 110 case ENTER : 111 setExternalFocus(false); 112 origEventDispatcher.dispatchEvent(event, tail); 113 break; 114 default : 115 // this'll kill mnemonics.... unless! 116 Scene s = tlNode.getScene(); 117 Event.fireEvent(s, event); 118 event.consume(); 119 break; 120 } 121 } 122 } 123 } 124 return event; 125 }; 126 127 final EventDispatcher tlfEventDispatcher = (event, tail) -> { 128 if ((event instanceof KeyEvent)) { 129 if (isExternalFocus()) { 130 tail = tail.prepend(preemptiveEventDispatcher); 131 return tail.dispatchEvent(event); 132 } 133 } 134 return origEventDispatcher.dispatchEvent(event, tail); 135 }; 136 137 private Event postDispatchTidyup(Event event) { 138 139 // block the event from being passed down to children 140 if (event instanceof KeyEvent && event.getEventType() == KeyEvent.KEY_PRESSED) { 141 if (!isExternalFocus()) { 142 // 143 // don't let the behaviour leak any navigation keys when 144 // we're not in blocking mode.... 145 // 146 if (!((KeyEvent)event).isMetaDown() && !((KeyEvent)event).isControlDown() && !((KeyEvent)event).isAltDown()) { 147 switch (((KeyEvent)event).getCode()) { 148 case TAB : 149 case UP : 150 case DOWN : 151 case LEFT : 152 case RIGHT : 153 event.consume(); 154 break; 155 156 case ENTER : 157 setExternalFocus(true); 158 event.consume(); 159 break; 160 default : 161 break; 162 } 163 } 164 } 165 } 166 return event; 167 } 168 169 170 private final EventHandler<KeyEvent> keyEventListener = e -> { 171 postDispatchTidyup(e); 172 }; 173 174 175 /* 176 ** When a node gets focus, put it in external-focus mode. 177 */ 178 final ChangeListener<Boolean> focusListener = (observable, oldVal, newVal) -> { 179 setExternalFocus(true); 180 }; 181 182 private final EventHandler<MouseEvent> mouseEventListener = e -> { 183 setExternalFocus(false); 184 }; 185 }