forked from openjdk/jfx
/
ButtonBehavior.java
235 lines (198 loc) · 9.54 KB
/
ButtonBehavior.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.scene.control.behavior;
import com.sun.javafx.PlatformUtil;
import com.sun.javafx.scene.control.inputmap.KeyBinding;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.scene.control.ButtonBase;
import com.sun.javafx.scene.control.inputmap.InputMap;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import static com.sun.javafx.scene.control.inputmap.InputMap.*;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.SPACE;
/**
* All of the "button" types (CheckBox, RadioButton, ToggleButton, and Button)
* and also maybe some other types like hyperlinks operate on the "armed"
* selection strategy, just like JButton. This behavior class encapsulates that
* logic in a way that can be reused and extended by each of the individual
* class behaviors.
*
*/
public class ButtonBehavior<C extends ButtonBase> extends BehaviorBase<C> {
private final InputMap<C> buttonInputMap;
/**
* Indicates that a keyboard key has been pressed which represents the
* event (this could be space bar for example). As long as keyDown is true,
* we are also armed, and will ignore mouse events related to arming.
* Note this is made package private solely for the sake of testing.
*/
private boolean keyDown;
private InvalidationListener focusListener = this::focusChanged;
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
public ButtonBehavior(C control) {
super(control);
// create a map for button-specific mappings (this reuses the default
// InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
buttonInputMap = createInputMap();
// add focus traversal mappings
addDefaultMapping(buttonInputMap, FocusTraversalInputMap.getFocusTraversalMappings());
// then button-specific mappings for key and mouse input
addDefaultMapping(buttonInputMap,
new KeyMapping(SPACE, KeyEvent.KEY_PRESSED, this::keyPressed),
new KeyMapping(SPACE, KeyEvent.KEY_RELEASED, this::keyReleased),
new MouseMapping(MouseEvent.MOUSE_PRESSED, this::mousePressed),
new MouseMapping(MouseEvent.MOUSE_RELEASED, this::mouseReleased),
new MouseMapping(MouseEvent.MOUSE_ENTERED, this::mouseEntered),
new MouseMapping(MouseEvent.MOUSE_EXITED, this::mouseExited),
// on non-Mac OS platforms, we support pressing the ENTER key to activate the button
new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_PRESSED), this::keyPressed, event -> PlatformUtil.isMac()),
new KeyMapping(new KeyBinding(ENTER, KeyEvent.KEY_RELEASED), this::keyReleased, event -> PlatformUtil.isMac())
);
// Button also cares about focus
control.focusedProperty().addListener(focusListener);
}
/***************************************************************************
* *
* Implementation of BehaviorBase API *
* *
**************************************************************************/
@Override public InputMap<C> getInputMap() {
return buttonInputMap;
}
@Override public void dispose() {
// TODO specify contract of dispose and post-condition for getNode()
getNode().focusedProperty().removeListener(focusListener);
super.dispose();
}
/***************************************************************************
* *
* Focus change handling *
* *
**************************************************************************/
private void focusChanged(Observable o) {
// If we did have the key down, but are now not focused, then we must
// disarm the button.
final ButtonBase button = getNode();
if (keyDown && !button.isFocused()) {
keyDown = false;
button.disarm();
}
}
/***************************************************************************
* *
* Key event handling *
* *
**************************************************************************/
/**
* This function is invoked when an appropriate keystroke occurs which
* causes this button to be armed if it is not already armed by a mouse
* press.
*/
protected void keyPressed(KeyEvent e) {
if (! getNode().isPressed() && ! getNode().isArmed()) {
keyDown = true;
getNode().arm();
}
}
/**
* Invoked when a valid keystroke release occurs which causes the button
* to fire if it was armed by a keyPress.
*/
protected void keyReleased(KeyEvent e) {
if (keyDown) {
keyDown = false;
if (getNode().isArmed()) {
getNode().disarm();
getNode().fire();
}
}
}
/***************************************************************************
* *
* Mouse event handling *
* *
**************************************************************************/
/**
* Invoked when a mouse press has occurred over the button. In addition to
* potentially arming the Button, this will transfer focus to the button
*/
protected void mousePressed(MouseEvent e) {
if (getNode().isFocusTraversable()) {
getNode().requestFocus();
}
// arm the button if it is a valid mouse event
// Note there appears to be a bug where if I press and hold and release
// then there is a clickCount of 0 on the release, whereas a quick click
// has a release clickCount of 1. So here I'll check clickCount <= 1,
// though it should really be == 1 I think.
boolean valid = (e.getButton() == MouseButton.PRIMARY &&
! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() ||
e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown()));
if (! getNode().isArmed() && valid) {
getNode().arm();
}
}
/**
* Invoked when a mouse release has occurred. We determine whether this
* was done in a manner that would fire the button's action. This happens
* only if the button was armed by a corresponding mouse press.
*/
protected void mouseReleased(MouseEvent e) {
// if armed by a mouse press instead of key press, then fire!
if (! keyDown && getNode().isArmed()) {
getNode().fire();
getNode().disarm();
}
}
/**
* Invoked when the mouse enters the Button. If the Button had been armed
* by a mouse press and the mouse is still pressed, then this will cause
* the button to be rearmed.
*/
protected void mouseEntered(MouseEvent e) {
// rearm if necessary
if (! keyDown && getNode().isPressed()) {
getNode().arm();
}
}
/**
* Invoked when the mouse exits the Button. If the Button is armed due to
* a mouse press, then this function will disarm the button upon the mouse
* exiting it.
*/
protected void mouseExited(MouseEvent e) {
// Disarm if necessary
if (! keyDown && getNode().isArmed()) {
getNode().disarm();
}
}
}