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 }