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.skin; 27 28 import javafx.event.Event; 29 import javafx.event.EventHandler; 30 import javafx.scene.AccessibleAttribute; 31 import javafx.scene.Node; 32 import javafx.scene.control.ContextMenu; 33 import javafx.scene.control.Menu; 34 import javafx.scene.control.Skin; 35 import javafx.scene.input.KeyCode; 36 import javafx.scene.input.KeyEvent; 37 import javafx.scene.layout.Region; 38 import javafx.stage.WindowEvent; 39 import com.sun.javafx.scene.control.behavior.TwoLevelFocusPopupBehavior; 40 41 /** 42 * Default Skin implementation for PopupMenu. Several controls use PopupMenu in 43 * order to display items in a drop down. It deals mostly with show hide logic for 44 * Popup based controls, and specifically in this case for PopupMenu. This is 45 * explained in the superclass {@link PopupControlSkin}. 46 * <p> 47 * Region/css based skin implementation for PopupMenu is the {@link PopupMenuContent} 48 * class. 49 */ 50 public class ContextMenuSkin implements Skin<ContextMenu> { 51 52 /* need to hold a reference to popupMenu here because getSkinnable() deliberately 53 * returns null in PopupControlSkin. */ 54 private ContextMenu popupMenu; 55 56 private final Region root; 57 private TwoLevelFocusPopupBehavior tlFocus; 58 59 // Fix for RT-18247 60 private final EventHandler<KeyEvent> keyListener = new EventHandler<KeyEvent>() { 61 @Override public void handle(KeyEvent event) { 62 if (event.getEventType() != KeyEvent.KEY_PRESSED) return; 63 64 // We only care if the root container still has focus 65 if (! root.isFocused()) return; 66 67 final KeyCode code = event.getCode(); 68 switch (code) { 69 case ENTER: 70 case SPACE: popupMenu.hide(); return; 71 default: return; 72 } 73 } 74 }; 75 76 /***/ 77 public ContextMenuSkin(final ContextMenu popupMenu) { 78 this.popupMenu = popupMenu; 79 80 // When a contextMenu is shown, requestFocus on its content to enable 81 // keyboard navigation. 82 popupMenu.addEventHandler(Menu.ON_SHOWN, new EventHandler<Event>() { 83 @Override public void handle(Event event) { 84 Node cmContent = popupMenu.getSkin().getNode(); 85 if (cmContent != null) { 86 cmContent.requestFocus(); 87 if (cmContent instanceof ContextMenuContent) { 88 Node accMenu = ((ContextMenuContent)cmContent).getItemsContainer(); 89 accMenu.notifyAccessibleAttributeChanged(AccessibleAttribute.VISIBLE); 90 } 91 } 92 93 root.addEventHandler(KeyEvent.KEY_PRESSED, keyListener); 94 } 95 }); 96 popupMenu.addEventHandler(Menu.ON_HIDDEN, new EventHandler<Event>() { 97 @Override public void handle(Event event) { 98 Node cmContent = popupMenu.getSkin().getNode(); 99 if (cmContent != null) cmContent.requestFocus(); 100 101 root.removeEventHandler(KeyEvent.KEY_PRESSED, keyListener); 102 } 103 }); 104 105 // For accessibility Menu.ON_HIDING does not work because isShowing is true 106 // during the event, Menu.ON_HIDDEN does not work because the Window (in glass) 107 // has already being disposed. The fix is to use WINDOW_HIDING (WINDOW_HIDDEN). 108 popupMenu.addEventFilter(WindowEvent.WINDOW_HIDING, new EventHandler<Event>() { 109 @Override public void handle(Event event) { 110 Node cmContent = popupMenu.getSkin().getNode(); 111 if (cmContent instanceof ContextMenuContent) { 112 Node accMenu = ((ContextMenuContent)cmContent).getItemsContainer(); 113 accMenu.notifyAccessibleAttributeChanged(AccessibleAttribute.VISIBLE); 114 } 115 } 116 }); 117 118 if (BehaviorSkinBase.IS_TOUCH_SUPPORTED && 119 popupMenu.getStyleClass().contains("text-input-context-menu")) { 120 root = new EmbeddedTextContextMenuContent(popupMenu); 121 } else { 122 root = new ContextMenuContent(popupMenu); 123 } 124 root.idProperty().bind(popupMenu.idProperty()); 125 root.styleProperty().bind(popupMenu.styleProperty()); 126 root.getStyleClass().addAll(popupMenu.getStyleClass()); // TODO needs to handle updates 127 128 // Only add this if we're on an embedded platform that supports 5-button navigation 129 if (Utils.isTwoLevelFocus()) { 130 tlFocus = new TwoLevelFocusPopupBehavior(popupMenu); // needs to be last. 131 } 132 } 133 134 @Override public ContextMenu getSkinnable() { 135 return popupMenu; 136 } 137 138 @Override public Node getNode() { 139 return root; 140 } 141 142 @Override public void dispose() { 143 root.idProperty().unbind(); 144 root.styleProperty().unbind(); 145 if (tlFocus != null) tlFocus.dispose(); 146 } 147 }