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