1 /*
2 * Copyright (c) 2010, 2013, 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.scene.control.ButtonBase;
29 import javafx.scene.input.MouseButton;
30 import javafx.scene.input.MouseEvent;
31 import java.util.ArrayList;
32 import java.util.List;
33 import static javafx.scene.input.KeyCode.SPACE;
34 import static javafx.scene.input.KeyCode.TAB;
35 import static javafx.scene.input.KeyEvent.KEY_PRESSED;
36 import static javafx.scene.input.KeyEvent.KEY_RELEASED;
37
38
39 /**
40 * All of the "button" types (CheckBox, RadioButton, ToggleButton, and Button)
41 * and also maybe some other types like hyperlinks operate on the "armed"
42 * selection strategy, just like JButton. This behavior class encapsulates that
43 * logic in a way that can be reused and extended by each of the individual
44 * class behaviors.
45 *
46 */
47 public class ButtonBehavior<C extends ButtonBase> extends BehaviorBase<C> {
48
49 /***************************************************************************
50 * *
51 * Constructors *
52 * *
53 **************************************************************************/
54
55 public ButtonBehavior(final C button) {
56 super(button, BUTTON_BINDINGS);
57 }
58
59 public ButtonBehavior(final C button, final List<KeyBinding> bindings) {
60 super(button, bindings);
61 }
62
63 /***************************************************************************
64 * *
65 * Focus change handling *
66 * *
67 **************************************************************************/
68
69 @Override protected void focusChanged() {
70 // If we did have the key down, but are now not focused, then we must
71 // disarm the button.
72 final ButtonBase button = getControl();
73 if (keyDown && !button.isFocused()) {
74 keyDown = false;
75 button.disarm();
76 }
77 }
78
79 /***************************************************************************
80 * *
81 * Key event handling *
82 * *
83 **************************************************************************/
84
85 /**
86 * Indicates that a keyboard key has been pressed which represents the
87 * event (this could be space bar for example). As long as keyDown is true,
88 * we are also armed, and will ignore mouse events related to arming.
89 * Note this is made package private solely for the sake of testing.
90 */
91 private boolean keyDown;
92
93 private static final String PRESS_ACTION = "Press";
94 private static final String RELEASE_ACTION = "Release";
95
96 protected static final List<KeyBinding> BUTTON_BINDINGS = new ArrayList<KeyBinding>();
97 static {
98 BUTTON_BINDINGS.add(new KeyBinding(TAB, "TraverseNext"));
99 BUTTON_BINDINGS.add(new KeyBinding(TAB, "TraversePrevious").shift());
100
101 BUTTON_BINDINGS.add(new KeyBinding(SPACE, KEY_PRESSED, PRESS_ACTION));
102 BUTTON_BINDINGS.add(new KeyBinding(SPACE, KEY_RELEASED, RELEASE_ACTION));
103 }
104
105 @Override protected void callAction(String name) {
106 if (!getControl().isDisabled()) {
107 if (PRESS_ACTION.equals(name)) {
108 keyPressed();
109 } else if (RELEASE_ACTION.equals(name)) {
110 keyReleased();
111 } else {
112 super.callAction(name);
113 }
114 }
115 }
116
117 /**
118 * This function is invoked when an appropriate keystroke occurs which
119 * causes this button to be armed if it is not already armed by a mouse
120 * press.
121 */
122 private void keyPressed() {
123 final ButtonBase button = getControl();
124 if (! button.isPressed() && ! button.isArmed()) {
125 keyDown = true;
126 button.arm();
127 }
128 }
129
130 /**
131 * Invoked when a valid keystroke release occurs which causes the button
132 * to fire if it was armed by a keyPress.
133 */
134 private void keyReleased() {
135 final ButtonBase button = getControl();
136 if (keyDown) {
137 keyDown = false;
138 if (button.isArmed()) {
139 button.disarm();
140 button.fire();
141 }
142 }
143 }
144
145 /***************************************************************************
146 * *
147 * Mouse event handling *
148 * *
149 **************************************************************************/
150
151 /**
152 * Invoked when a mouse press has occurred over the button. In addition to
153 * potentially arming the Button, this will transfer focus to the button
154 */
155 @Override public void mousePressed(MouseEvent e) {
156 final ButtonBase button = getControl();
157 super.mousePressed(e);
158 // if the button is not already focused, then request the focus
159 if (! button.isFocused() && button.isFocusTraversable()) {
160 button.requestFocus();
161 }
162
163 // arm the button if it is a valid mouse event
164 // Note there appears to be a bug where if I press and hold and release
165 // then there is a clickCount of 0 on the release, whereas a quick click
166 // has a release clickCount of 1. So here I'll check clickCount <= 1,
167 // though it should really be == 1 I think.
168 boolean valid = (e.getButton() == MouseButton.PRIMARY &&
169 ! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() ||
170 e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown()));
171
172 if (! button.isArmed() && valid) {
173 button.arm();
174 }
175 }
176
177 /**
178 * Invoked when a mouse release has occurred. We determine whether this
179 * was done in a manner that would fire the button's action. This happens
180 * only if the button was armed by a corresponding mouse press.
181 */
182 @Override public void mouseReleased(MouseEvent e) {
183 // if armed by a mouse press instead of key press, then fire!
184 final ButtonBase button = getControl();
185 if (! keyDown && button.isArmed()) {
186 button.fire();
187 button.disarm();
188 }
189 }
190
191 /**
192 * Invoked when the mouse enters the Button. If the Button had been armed
193 * by a mouse press and the mouse is still pressed, then this will cause
194 * the button to be rearmed.
195 */
196 @Override public void mouseEntered(MouseEvent e) {
197 // rearm if necessary
198 final ButtonBase button = getControl();
199 super.mouseEntered(e);
200 if (! keyDown && button.isPressed()) {
201 button.arm();
202 }
203 }
204
205 /**
206 * Invoked when the mouse exits the Button. If the Button is armed due to
207 * a mouse press, then this function will disarm the button upon the mouse
208 * exiting it.
209 */
210 @Override public void mouseExited(MouseEvent e) {
211 // Disarm if necessary
212 final ButtonBase button = getControl();
213 super.mouseExited(e);
214 if (! keyDown && button.isArmed()) {
215 button.disarm();
216 }
217 }
218 }
|
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 package com.sun.javafx.scene.control.behavior;
26
27 import javafx.beans.Observable;
28 import javafx.scene.control.ButtonBase;
29 import com.sun.javafx.scene.control.inputmap.InputMap;
30 import javafx.scene.input.KeyEvent;
31 import javafx.scene.input.MouseButton;
32 import javafx.scene.input.MouseEvent;
33
34 import static com.sun.javafx.scene.control.inputmap.InputMap.*;
35 import static javafx.scene.input.KeyCode.SPACE;
36
37 /**
38 * All of the "button" types (CheckBox, RadioButton, ToggleButton, and Button)
39 * and also maybe some other types like hyperlinks operate on the "armed"
40 * selection strategy, just like JButton. This behavior class encapsulates that
41 * logic in a way that can be reused and extended by each of the individual
42 * class behaviors.
43 *
44 */
45 public class ButtonBehavior<C extends ButtonBase> extends BehaviorBase<C> {
46 private final InputMap<C> buttonInputMap;
47
48 /**
49 * Indicates that a keyboard key has been pressed which represents the
50 * event (this could be space bar for example). As long as keyDown is true,
51 * we are also armed, and will ignore mouse events related to arming.
52 * Note this is made package private solely for the sake of testing.
53 */
54 private boolean keyDown;
55
56
57
58 /***************************************************************************
59 * *
60 * Constructors *
61 * *
62 **************************************************************************/
63
64 public ButtonBehavior(C control) {
65 super(control);
66
67 // create a map for button-specific mappings (this reuses the default
68 // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
69 buttonInputMap = createInputMap();
70
71 // add focus traversal mappings
72 addDefaultMapping(buttonInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
73
74 // then button-specific mappings for key and mouse input
75 addDefaultMapping(buttonInputMap,
76 new KeyMapping(SPACE, KeyEvent.KEY_PRESSED, this::keyPressed),
77 new KeyMapping(SPACE, KeyEvent.KEY_RELEASED, this::keyReleased),
78 new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
79 new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
80 new MouseMapping(MouseEvent.MOUSE_ENTERED, this::mouseEntered),
81 new MouseMapping(MouseEvent.MOUSE_EXITED, this::mouseExited)
82 );
83
84 // Button also cares about focus
85 control.focusedProperty().addListener(this::focusChanged);
86 }
87
88
89
90 /***************************************************************************
91 * *
92 * Implementation of BehaviorBase API *
93 * *
94 **************************************************************************/
95
96 @Override public InputMap<C> getInputMap() {
97 return buttonInputMap;
98 }
99
100 @Override public void dispose() {
101 super.dispose();
102
103 // TODO
104 getNode().focusedProperty().removeListener(this::focusChanged);
105 }
106
107
108
109 /***************************************************************************
110 * *
111 * Focus change handling *
112 * *
113 **************************************************************************/
114
115 private void focusChanged(Observable o) {
116 // If we did have the key down, but are now not focused, then we must
117 // disarm the button.
118 final ButtonBase button = getNode();
119 if (keyDown && !button.isFocused()) {
120 keyDown = false;
121 button.disarm();
122 }
123 }
124
125
126
127 /***************************************************************************
128 * *
129 * Key event handling *
130 * *
131 **************************************************************************/
132
133 /**
134 * This function is invoked when an appropriate keystroke occurs which
135 * causes this button to be armed if it is not already armed by a mouse
136 * press.
137 */
138 protected void keyPressed(KeyEvent e) {
139 if (! getNode().isPressed() && ! getNode().isArmed()) {
140 keyDown = true;
141 getNode().arm();
142 }
143 }
144
145 /**
146 * Invoked when a valid keystroke release occurs which causes the button
147 * to fire if it was armed by a keyPress.
148 */
149 protected void keyReleased(KeyEvent e) {
150 if (keyDown) {
151 keyDown = false;
152 if (getNode().isArmed()) {
153 getNode().disarm();
154 getNode().fire();
155 }
156 }
157 }
158
159
160
161 /***************************************************************************
162 * *
163 * Mouse event handling *
164 * *
165 **************************************************************************/
166
167 /**
168 * Invoked when a mouse press has occurred over the button. In addition to
169 * potentially arming the Button, this will transfer focus to the button
170 */
171 protected void mousePressed(MouseEvent e) {
172 // if the button is not already focused, then request the focus
173 if (! getNode().isFocused() && getNode().isFocusTraversable()) {
174 getNode().requestFocus();
175 }
176
177 // arm the button if it is a valid mouse event
178 // Note there appears to be a bug where if I press and hold and release
179 // then there is a clickCount of 0 on the release, whereas a quick click
180 // has a release clickCount of 1. So here I'll check clickCount <= 1,
181 // though it should really be == 1 I think.
182 boolean valid = (e.getButton() == MouseButton.PRIMARY &&
183 ! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() ||
184 e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown()));
185
186 if (! getNode().isArmed() && valid) {
187 getNode().arm();
188 }
189 }
190
191 /**
192 * Invoked when a mouse release has occurred. We determine whether this
193 * was done in a manner that would fire the button's action. This happens
194 * only if the button was armed by a corresponding mouse press.
195 */
196 protected void mouseReleased(MouseEvent e) {
197 // if armed by a mouse press instead of key press, then fire!
198 if (! keyDown && getNode().isArmed()) {
199 getNode().fire();
200 getNode().disarm();
201 }
202 }
203
204 /**
205 * Invoked when the mouse enters the Button. If the Button had been armed
206 * by a mouse press and the mouse is still pressed, then this will cause
207 * the button to be rearmed.
208 */
209 protected void mouseEntered(MouseEvent e) {
210 // rearm if necessary
211 if (! keyDown && getNode().isPressed()) {
212 getNode().arm();
213 }
214 }
215
216 /**
217 * Invoked when the mouse exits the Button. If the Button is armed due to
218 * a mouse press, then this function will disarm the button upon the mouse
219 * exiting it.
220 */
221 protected void mouseExited(MouseEvent e) {
222 // Disarm if necessary
223 if (! keyDown && getNode().isArmed()) {
224 getNode().disarm();
225 }
226 }
227 }
|