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 }
|
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 }
|