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