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.behavior;
  27 
  28 import static javafx.scene.input.KeyCode.UP;
  29 import static javafx.scene.input.KeyCode.DOWN;
  30 import static javafx.scene.input.KeyCode.LEFT;
  31 import static javafx.scene.input.KeyCode.RIGHT;
  32 import static javafx.scene.input.KeyCode.CANCEL;
  33 import static javafx.scene.input.KeyCode.ESCAPE;
  34 import static javafx.scene.input.KeyEvent.KEY_PRESSED;
  35 
  36 import java.util.ArrayList;
  37 import java.util.List;
  38 
  39 import javafx.geometry.NodeOrientation;
  40 import javafx.geometry.Side;
  41 import javafx.scene.control.MenuButton;
  42 import javafx.scene.input.MouseButton;
  43 import javafx.scene.input.MouseEvent;
  44 
  45 /**
  46  * The base behavior for a MenuButton.
  47  */
  48 public abstract class MenuButtonBehaviorBase<C extends MenuButton> extends ButtonBehavior<C> {
  49 
  50     /***************************************************************************
  51      *                                                                         *
  52      * Constructors                                                            *
  53      *                                                                         *
  54      **************************************************************************/
  55 
  56     public MenuButtonBehaviorBase(final C menuButton, List<KeyBinding> bindings) {
  57         super(menuButton, bindings);
  58     }
  59 
  60     /***************************************************************************
  61      *                                                                         *
  62      * Key event handling                                                      *
  63      *                                                                         *
  64      **************************************************************************/
  65 
  66     /**
  67      * Opens the popup menu.
  68      */
  69     protected static final String OPEN_ACTION = "Open";
  70 
  71     /**
  72      * Closes the popup menu.
  73      */
  74     protected static final String CLOSE_ACTION = "Close";
  75 
  76     /**
  77      * The base key bindings for a MenuButton. These basically just define the
  78      * bindings to close an open menu. Subclasses will tell you what can be done
  79      * to open it.
  80      */
  81     protected static final List<KeyBinding> BASE_MENU_BUTTON_BINDINGS = new ArrayList<KeyBinding>();
  82     static {
  83         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(UP, "TraverseUp"));
  84         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(DOWN, "TraverseDown"));
  85         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(LEFT, "TraverseLeft"));
  86         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(RIGHT, "TraverseRight"));
  87         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(ESCAPE, KEY_PRESSED, CLOSE_ACTION));
  88         BASE_MENU_BUTTON_BINDINGS.add(new KeyBinding(CANCEL, KEY_PRESSED, CLOSE_ACTION));
  89     }
  90 
  91     /**
  92      * Invokes the given named action.
  93      *
  94      * @param name the name of the action to invoke
  95      */
  96     @Override protected void callAction(String name) {
  97         MenuButton button = getControl();
  98         Side popupSide = button.getPopupSide();
  99 
 100         if (CLOSE_ACTION.equals(name)) {
 101             button.hide();
 102         } else if (OPEN_ACTION.equals(name)) {
 103             if (button.isShowing()) {
 104                 button.hide();
 105             } else {
 106                 button.show();
 107             }
 108         } else if (!button.isShowing() &&
 109                    ("TraverseUp".equals(name)    && popupSide == Side.TOP)    ||
 110                    ("TraverseDown".equals(name)  && (popupSide == Side.BOTTOM || popupSide == Side.TOP))  ||
 111                    ("TraverseLeft".equals(name)  && (popupSide == Side.RIGHT  || popupSide == Side.LEFT)) ||
 112                    ("TraverseRight".equals(name) && (popupSide == Side.RIGHT  || popupSide == Side.LEFT))) {
 113             // Show the menu when arrow key matches the popupSide
 114             // direction -- but also allow RIGHT key for LEFT position and
 115             // DOWN key for TOP position. To be symmetrical, we also allow for
 116             // the LEFT key to work when in the RIGHT position. This is needed
 117             // because the skin only paints right- and down-facing arrows in
 118             // these cases.
 119             button.show();
 120         } else {
 121             super.callAction(name);
 122         }
 123     }
 124 
 125     /***************************************************************************
 126      *                                                                         *
 127      * Mouse event handling                                                    *
 128      *                                                                         *
 129      **************************************************************************/
 130 
 131     /**
 132      * When a mouse button is pressed, we either want to behave like a button or
 133      * show the popup.  This will be called by the skin.
 134      *
 135      * @param e the mouse press event
 136      * @param behaveLikeButton if true, this should act just like a button
 137      */
 138     public void mousePressed(MouseEvent e, boolean behaveLikeButton) {
 139         final C control = getControl();
 140 
 141         /*
 142          * Behaving like a button is easy - we just call super. But, we cannot
 143          * call super if all we want to do is show the popup. The reason for
 144          * this is that super also handles all the arm/disarm/fire logic, and
 145          * this can inadvertently cause actions to fire when we don't want them
 146          * to fire. So, we unfortunately need to duplicate the focus
 147          * handling code here.
 148          */
 149         if (behaveLikeButton) {
 150             if (control.isShowing()) {
 151                 control.hide();
 152             }
 153             super.mousePressed(e);
 154         } else {
 155             if (!control.isFocused() && control.isFocusTraversable()) {
 156                 control.requestFocus();
 157             }
 158             if (control.isShowing()) {
 159                 control.hide();
 160             } else {
 161                 if (e.getButton() == MouseButton.PRIMARY) {
 162                     control.show();    
 163                 }
 164             }
 165         }
 166     }
 167 
 168     @Override public void mouseReleased(MouseEvent e) {
 169         // Overriding to not call fire() on mouseReleased.
 170         // The event is handled by the skin instead, which calls
 171         // the method below.
 172     }
 173 
 174     /**
 175      * Handles mouse release events.  This will be called by the skin.
 176      *
 177      * @param e the mouse press event
 178      * @param behaveLikeButton if true, this should act just like a button
 179      */
 180     public void mouseReleased(MouseEvent e, boolean behaveLikeButton) {
 181         if (behaveLikeButton) {
 182             super.mouseReleased(e);
 183         } else {
 184             if (getControl().isShowing() && !getControl().contains(e.getX(), e.getY())) {
 185                 getControl().hide();
 186             }
 187             getControl().disarm();
 188         }
 189     }
 190 }