modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java
Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
*** 23,96 ****
* questions.
*/
package com.sun.javafx.scene.control.behavior;
import javafx.event.EventTarget;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ComboBoxBase;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextField;
! import javafx.scene.input.KeyEvent;
! import javafx.scene.input.MouseButton;
! import javafx.scene.input.MouseEvent;
! import java.util.ArrayList;
! import java.util.List;
! import static javafx.scene.input.KeyCode.DOWN;
! import static javafx.scene.input.KeyCode.ENTER;
! import static javafx.scene.input.KeyCode.ESCAPE;
! import static javafx.scene.input.KeyCode.F4;
! import static javafx.scene.input.KeyCode.F10;
! import static javafx.scene.input.KeyCode.SPACE;
! import static javafx.scene.input.KeyCode.UP;
! import static javafx.scene.input.KeyEvent.KEY_PRESSED;
! import static javafx.scene.input.KeyEvent.KEY_RELEASED;
public class ComboBoxBaseBehavior<T> extends BehaviorBase<ComboBoxBase<T>> {
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
private TwoLevelFocusComboBehavior tlFocus;
/**
- * Used to keep track of the most recent key event. This is used when
- * the event needs to be forwarded to the parent for bubbling up.
- */
- private KeyEvent lastEvent;
-
- /**
*
*/
! public ComboBoxBaseBehavior(final ComboBoxBase<T> comboBox, final List<KeyBinding> bindings) {
! super(comboBox, bindings);
// Only add this if we're on an embedded platform that supports 5-button navigation
! if (com.sun.javafx.scene.control.skin.Utils.isTwoLevelFocus()) {
tlFocus = new TwoLevelFocusComboBehavior(comboBox); // needs to be last.
}
}
@Override public void dispose() {
if (tlFocus != null) tlFocus.dispose();
super.dispose();
}
/***************************************************************************
* *
* Focus change handling *
* *
**************************************************************************/
! @Override protected void focusChanged() {
// If we did have the key down, but are now not focused, then we must
// disarm the box.
! final ComboBoxBase<T> box = getControl();
if (keyDown && !box.isFocused()) {
keyDown = false;
box.disarm();
}
}
--- 23,132 ----
* questions.
*/
package com.sun.javafx.scene.control.behavior;
+ import com.sun.javafx.scene.control.inputmap.InputMap;
+ import javafx.beans.Observable;
+ import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ComboBoxBase;
import javafx.scene.control.DatePicker;
+ import javafx.scene.control.PopupControl;
import javafx.scene.control.TextField;
! import com.sun.javafx.scene.control.skin.Utils;
! import javafx.scene.input.*;
! import com.sun.javafx.scene.control.inputmap.KeyBinding;
!
! import static javafx.scene.input.KeyCode.*;
! import static javafx.scene.input.KeyEvent.*;
! import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
! import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping;
public class ComboBoxBaseBehavior<T> extends BehaviorBase<ComboBoxBase<T>> {
+ private final InputMap<ComboBoxBase<T>> inputMap;
+
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
private TwoLevelFocusComboBehavior tlFocus;
/**
*
*/
! public ComboBoxBaseBehavior(final ComboBoxBase<T> comboBox) {
! super(comboBox);
!
! // create a map for comboBox-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)
! inputMap = createInputMap();
!
! final EventHandler<KeyEvent> togglePopup = e -> {
! // If popup is shown, KeyEvent causes popup to close
! showPopupOnMouseRelease = true;
!
! if (getNode().isShowing()) hide();
! else show();
! };
!
! // comboBox-specific mappings for key and mouse input
! addDefaultMapping(inputMap,
! new KeyMapping(F4, KEY_RELEASED, togglePopup),
! new KeyMapping(new KeyBinding(UP).alt(), togglePopup),
! new KeyMapping(new KeyBinding(DOWN).alt(), togglePopup),
!
! new KeyMapping(SPACE, KEY_PRESSED, this::keyPressed),
! new KeyMapping(SPACE, KEY_RELEASED, this::keyReleased),
!
! new KeyMapping(ENTER, KEY_PRESSED, this::keyPressed),
! new KeyMapping(ENTER, KEY_RELEASED, this::keyReleased),
!
! // The following keys are forwarded to the parent container
! new KeyMapping(ESCAPE, KEY_PRESSED, this::cancelEdit),
! new KeyMapping(F10, KEY_PRESSED, this::forwardToParent),
!
! 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)
! );
!
! // ComboBoxBase also cares about focus
! comboBox.focusedProperty().addListener(this::focusChanged);
// Only add this if we're on an embedded platform that supports 5-button navigation
! if (Utils.isTwoLevelFocus()) {
tlFocus = new TwoLevelFocusComboBehavior(comboBox); // needs to be last.
}
}
@Override public void dispose() {
if (tlFocus != null) tlFocus.dispose();
+ getNode().focusedProperty().removeListener(this::focusChanged);
super.dispose();
}
+ @Override public InputMap<ComboBoxBase<T>> getInputMap() {
+ return inputMap;
+ }
+
/***************************************************************************
* *
* Focus change handling *
* *
**************************************************************************/
! protected void focusChanged(Observable o) {
// If we did have the key down, but are now not focused, then we must
// disarm the box.
! final ComboBoxBase<T> box = getNode();
if (keyDown && !box.isFocused()) {
keyDown = false;
box.disarm();
}
}
*** 107,209 ****
* 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 static final String PRESS_ACTION = "Press";
- private static final String RELEASE_ACTION = "Release";
-
- protected static final List<KeyBinding> COMBO_BOX_BASE_BINDINGS = new ArrayList<KeyBinding>();
- static {
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(F4, KEY_RELEASED, "togglePopup"));
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(UP, "togglePopup").alt());
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(DOWN, "togglePopup").alt());
-
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(SPACE, KEY_PRESSED, PRESS_ACTION));
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(SPACE, KEY_RELEASED, RELEASE_ACTION));
-
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, PRESS_ACTION));
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(ENTER, KEY_RELEASED, RELEASE_ACTION));
-
- // The following keys are forwarded to the parent container
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(ESCAPE, "Cancel"));
- COMBO_BOX_BASE_BINDINGS.add(new KeyBinding(F10, "ToParent"));
- }
-
- @Override protected void callActionForEvent(KeyEvent e) {
- // If popup is shown, KeyEvent causes popup to close
- lastEvent = e;
- showPopupOnMouseRelease = true;
- super.callActionForEvent(e);
- }
-
- @Override protected void callAction(String name) {
- if (PRESS_ACTION.equals(name)) {
- keyPressed();
- } else if (RELEASE_ACTION.equals(name)) {
- keyReleased();
- } else if ("showPopup".equals(name)) {
- show();
- } else if ("togglePopup".equals(name)) {
- if (getControl().isShowing()) hide();
- else show();
- } else if ("Cancel".equals(name)) {
- cancelEdit(lastEvent);
- } else if ("ToParent".equals(name)) {
- forwardToParent(lastEvent);
- } else {
- super.callAction(name);
- }
- }
-
/**
* 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.
*/
! private void keyPressed() {
! if (com.sun.javafx.scene.control.skin.Utils.isTwoLevelFocus()) {
show();
if (tlFocus != null) {
tlFocus.setExternalFocus(false);
}
}
else {
! if (! getControl().isPressed() && ! getControl().isArmed()) {
keyDown = true;
! getControl().arm();
}
}
}
/**
* Invoked when a valid keystroke release occurs which causes the button
* to fire if it was armed by a keyPress.
*/
! private void keyReleased() {
! if (!com.sun.javafx.scene.control.skin.Utils.isTwoLevelFocus()) {
if (keyDown) {
keyDown = false;
! if (getControl().isArmed()) {
! getControl().disarm();
}
}
}
}
! protected void forwardToParent(KeyEvent event) {
! if (getControl().getParent() != null) {
! getControl().getParent().fireEvent(event);
}
}
! protected void cancelEdit(KeyEvent event) {
/**
* This can be cleaned up if the editor property is moved up
* to ComboBoxBase.
*/
! ComboBoxBase comboBoxBase = getControl();
TextField textField = null;
if (comboBoxBase instanceof DatePicker) {
textField = ((DatePicker)comboBoxBase).getEditor();
} else if (comboBoxBase instanceof ComboBox) {
textField = comboBoxBase.isEditable() ? ((ComboBox)comboBoxBase).getEditor() : null;
--- 143,205 ----
* 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;
/**
* 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.
*/
! private void keyPressed(KeyEvent e) {
! // If popup is shown, KeyEvent causes popup to close
! showPopupOnMouseRelease = true;
!
! if (Utils.isTwoLevelFocus()) {
show();
if (tlFocus != null) {
tlFocus.setExternalFocus(false);
}
}
else {
! 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.
*/
! private void keyReleased(KeyEvent e) {
! // If popup is shown, KeyEvent causes popup to close
! showPopupOnMouseRelease = true;
!
! if (!Utils.isTwoLevelFocus()) {
if (keyDown) {
keyDown = false;
! if (getNode().isArmed()) {
! getNode().disarm();
}
}
}
}
! private void forwardToParent(KeyEvent event) {
! if (getNode().getParent() != null) {
! getNode().getParent().fireEvent(event);
}
}
! private void cancelEdit(KeyEvent event) {
/**
* This can be cleaned up if the editor property is moved up
* to ComboBoxBase.
*/
! ComboBoxBase comboBoxBase = getNode();
TextField textField = null;
if (comboBoxBase instanceof DatePicker) {
textField = ((DatePicker)comboBoxBase).getEditor();
} else if (comboBoxBase instanceof ComboBox) {
textField = comboBoxBase.isEditable() ? ((ComboBox)comboBoxBase).getEditor() : null;
*** 214,237 ****
} else {
forwardToParent(event);
}
}
/**************************************************************************
* *
* Mouse Events *
* *
*************************************************************************/
! @Override public void mousePressed(MouseEvent e) {
! super.mousePressed(e);
arm(e);
}
! @Override public void mouseReleased(MouseEvent e) {
! super.mouseReleased(e);
!
disarm();
// The showPopupOnMouseRelease boolean was added to resolve
// RT-18151: namely, clicking on the comboBox button shouldn't hide,
// and then immediately show the popup, which was occurring because we
--- 210,231 ----
} else {
forwardToParent(event);
}
}
+
/**************************************************************************
* *
* Mouse Events *
* *
*************************************************************************/
! public void mousePressed(MouseEvent e) {
arm(e);
}
! public void mouseReleased(MouseEvent e) {
disarm();
// The showPopupOnMouseRelease boolean was added to resolve
// RT-18151: namely, clicking on the comboBox button shouldn't hide,
// and then immediately show the popup, which was occurring because we
*** 243,320 ****
showPopupOnMouseRelease = true;
hide();
}
}
! @Override public void mouseEntered(MouseEvent e) {
! super.mouseEntered(e);
!
! if (!getControl().isEditable()) {
mouseInsideButton = true;
} else {
// This is strongly tied to ComboBoxBaseSkin
final EventTarget target = e.getTarget();
mouseInsideButton = (target instanceof Node && "arrow-button".equals(((Node) target).getId()));
}
arm();
}
! @Override public void mouseExited(MouseEvent e) {
! super.mouseExited(e);
mouseInsideButton = false;
disarm();
}
! private void getFocus() {
! if (! getControl().isFocused() && getControl().isFocusTraversable()) {
! getControl().requestFocus();
! }
! }
private void arm(MouseEvent e) {
boolean valid = (e.getButton() == MouseButton.PRIMARY &&
! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() ||
e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown()));
! if (! getControl().isArmed() && valid) {
! getControl().arm();
}
}
public void show() {
! if (! getControl().isShowing()) {
! getControl().requestFocus();
! getControl().show();
}
}
public void hide() {
! if (getControl().isShowing()) {
! getControl().hide();
}
}
private boolean showPopupOnMouseRelease = true;
private boolean mouseInsideButton = false;
! public void onAutoHide() {
// RT-18151: if the ComboBox button was clicked, and it was this that forced the
// popup to disappear, we don't want the popup to immediately reappear.
// If the mouse was not within the comboBox button at the time of the auto-hide occurring,
// then showPopupOnMouseRelease returns to its default of true; otherwise, it toggles.
// Note that this logic depends on popup.setAutoHide(true) in ComboBoxPopupControl
hide();
showPopupOnMouseRelease = mouseInsideButton ? !showPopupOnMouseRelease : true;
}
public void arm() {
! if (getControl().isPressed()) {
! getControl().arm();
}
}
public void disarm() {
! if (! keyDown && getControl().isArmed()) {
! getControl().disarm();
}
}
}
--- 237,311 ----
showPopupOnMouseRelease = true;
hide();
}
}
! public void mouseEntered(MouseEvent e) {
! if (!getNode().isEditable()) {
mouseInsideButton = true;
} else {
// This is strongly tied to ComboBoxBaseSkin
final EventTarget target = e.getTarget();
mouseInsideButton = (target instanceof Node && "arrow-button".equals(((Node) target).getId()));
}
arm();
}
! public void mouseExited(MouseEvent e) {
mouseInsideButton = false;
disarm();
}
! // private void getFocus() {
! // if (! getNode().isFocused() && getNode().isFocusTraversable()) {
! // getNode().requestFocus();
! // }
! // }
private void arm(MouseEvent e) {
boolean valid = (e.getButton() == MouseButton.PRIMARY &&
! (e.isMiddleButtonDown() || e.isSecondaryButtonDown() ||
e.isShiftDown() || e.isControlDown() || e.isAltDown() || e.isMetaDown()));
! if (! getNode().isArmed() && valid) {
! getNode().arm();
}
}
public void show() {
! if (! getNode().isShowing()) {
! getNode().requestFocus();
! getNode().show();
}
}
public void hide() {
! if (getNode().isShowing()) {
! getNode().hide();
}
}
private boolean showPopupOnMouseRelease = true;
private boolean mouseInsideButton = false;
! public void onAutoHide(PopupControl popup) {
// RT-18151: if the ComboBox button was clicked, and it was this that forced the
// popup to disappear, we don't want the popup to immediately reappear.
// If the mouse was not within the comboBox button at the time of the auto-hide occurring,
// then showPopupOnMouseRelease returns to its default of true; otherwise, it toggles.
// Note that this logic depends on popup.setAutoHide(true) in ComboBoxPopupControl
hide();
showPopupOnMouseRelease = mouseInsideButton ? !showPopupOnMouseRelease : true;
}
public void arm() {
! if (getNode().isPressed()) {
! getNode().arm();
}
}
public void disarm() {
! if (! keyDown && getNode().isArmed()) {
! getNode().disarm();
}
}
}