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