1 /* 2 * Copyright (c) 2010, 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 javafx.geometry.Side; 29 import javafx.scene.control.MenuButton; 30 import com.sun.javafx.scene.control.inputmap.InputMap; 31 import javafx.scene.input.KeyEvent; 32 import javafx.scene.input.MouseButton; 33 import javafx.scene.input.MouseEvent; 34 35 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping; 36 import static javafx.scene.input.KeyCode.*; 37 38 /** 39 * The base behavior for a MenuButton. 40 */ 41 public abstract class MenuButtonBehaviorBase<C extends MenuButton> extends ButtonBehavior<C> { 42 43 private final InputMap<C> buttonInputMap; 44 45 /*************************************************************************** 46 * * 47 * Constructors * 48 * * 49 **************************************************************************/ 50 51 public MenuButtonBehaviorBase(final C menuButton) { 52 super(menuButton); 53 54 // pull down the parent input map, no need to add focus traversal 55 // mappings - added in ButtonBehavior. 56 buttonInputMap = super.getInputMap(); 57 58 // We want to remove the maping for MOUSE_RELEASED, as the event is 59 // handled by the skin instead, which calls the mouseReleased method below. 60 removeMapping(MouseEvent.MOUSE_RELEASED); 61 62 /** 63 * The base key bindings for a MenuButton. These basically just define the 64 * bindings to close an open menu. Subclasses will tell you what can be done 65 * to open it. 66 */ 67 addDefaultMapping(new KeyMapping(ESCAPE, e -> getNode().hide())); 68 addDefaultMapping(new KeyMapping(CANCEL, e -> getNode().hide())); 69 70 // we create a child input map, as we want to override some of the 71 // focus traversal behaviors (and child maps take precedence over parent maps) 72 InputMap<C> customFocusInputMap = new InputMap<>(menuButton); 73 addDefaultMapping(customFocusInputMap, new KeyMapping(UP, this::overrideTraversalInput)); 74 addDefaultMapping(customFocusInputMap, new KeyMapping(DOWN, this::overrideTraversalInput)); 75 addDefaultMapping(customFocusInputMap, new KeyMapping(LEFT, this::overrideTraversalInput)); 76 addDefaultMapping(customFocusInputMap, new KeyMapping(RIGHT, this::overrideTraversalInput)); 77 addDefaultChildMap(buttonInputMap, customFocusInputMap); 78 } 79 80 81 /*************************************************************************** 82 * * 83 * Key event handling * 84 * * 85 **************************************************************************/ 86 87 private void overrideTraversalInput(KeyEvent event) { 88 final MenuButton button = getNode(); 89 final Side popupSide = button.getPopupSide(); 90 if (!button.isShowing() && 91 (event.getCode() == UP && popupSide == Side.TOP) || 92 (event.getCode() == DOWN && (popupSide == Side.BOTTOM || popupSide == Side.TOP)) || 93 (event.getCode() == LEFT && (popupSide == Side.RIGHT || popupSide == Side.LEFT)) || 94 (event.getCode() == RIGHT && (popupSide == Side.RIGHT || popupSide == Side.LEFT))) { 95 // Show the menu when arrow key matches the popupSide 96 // direction -- but also allow RIGHT key for LEFT position and 97 // DOWN key for TOP position. To be symmetrical, we also allow for 98 // the LEFT key to work when in the RIGHT position. This is needed 99 // because the skin only paints right- and down-facing arrows in 100 // these cases. 101 button.show(); 102 } 103 } 104 105 protected void openAction() { 106 if (getNode().isShowing()) { 107 getNode().hide(); 108 } else { 109 getNode().show(); 110 } 111 } 112 113 /*************************************************************************** 114 * * 115 * Mouse event handling * 116 * * 117 **************************************************************************/ 118 119 /** 120 * When a mouse button is pressed, we either want to behave like a button or 121 * show the popup. This will be called by the skin. 122 * 123 * @param e the mouse press event 124 * @param behaveLikeButton if true, this should act just like a button 125 */ 126 public void mousePressed(MouseEvent e, boolean behaveLikeButton) { 127 final C control = getNode(); 128 129 /* 130 * Behaving like a button is easy - we just call super. But, we cannot 131 * call super if all we want to do is show the popup. The reason for 132 * this is that super also handles all the arm/disarm/fire logic, and 133 * this can inadvertently cause actions to fire when we don't want them 134 * to fire. So, we unfortunately need to duplicate the focus 135 * handling code here. 136 */ 137 if (behaveLikeButton) { 138 if (control.isShowing()) { 139 control.hide(); 140 } 141 super.mousePressed(e); 142 } else { 143 if (!control.isFocused() && control.isFocusTraversable()) { 144 control.requestFocus(); 145 } 146 if (control.isShowing()) { 147 control.hide(); 148 } else { 149 if (e.getButton() == MouseButton.PRIMARY) { 150 control.show(); 151 } 152 } 153 } 154 } 155 156 /** 157 * Handles mouse release events. This will be called by the skin. 158 * 159 * @param e the mouse press event 160 * @param behaveLikeButton if true, this should act just like a button 161 */ 162 public void mouseReleased(MouseEvent e, boolean behaveLikeButton) { 163 if (behaveLikeButton) { 164 super.mouseReleased(e); 165 } else { 166 if (getNode().isShowing() && !getNode().contains(e.getX(), e.getY())) { 167 getNode().hide(); 168 } 169 getNode().disarm(); 170 } 171 } 172 }