--- old/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java 2015-05-11 15:53:46.563036615 -0700 +++ new/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java 2015-05-11 15:53:46.467036615 -0700 @@ -27,7 +27,10 @@ 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; @@ -35,7 +38,9 @@ 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; @@ -52,6 +57,12 @@ 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 comboBox, final List bindings) { @@ -112,10 +123,15 @@ 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); } @@ -130,6 +146,10 @@ } 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); } @@ -170,8 +190,32 @@ } } - - + 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; + } + + if (textField != null && textField.getTextFormatter() != null) { + textField.cancelEdit(); + } else { + forwardToParent(event); + } + } + /************************************************************************** * * * Mouse Events * --- old/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java 2015-05-11 15:53:47.139036620 -0700 +++ new/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/DatePickerBehavior.java 2015-05-11 15:53:47.047036619 -0700 @@ -57,43 +57,9 @@ * * **************************************************************************/ - /** - * Opens the Date Picker Popup - */ - protected static final String OPEN_ACTION = "Open"; - - /** - * Closes the Date Picker Popup - */ - protected static final String CLOSE_ACTION = "Close"; - - protected static final List DATE_PICKER_BINDINGS = new ArrayList(); static { - DATE_PICKER_BINDINGS.add(new KeyBinding(F4, KEY_RELEASED, "togglePopup")); - DATE_PICKER_BINDINGS.add(new KeyBinding(UP, "togglePopup").alt()); - DATE_PICKER_BINDINGS.add(new KeyBinding(DOWN, "togglePopup").alt()); - } - - @Override protected void callAction(String name) { - switch (name) { - case OPEN_ACTION: - show(); break; - - case CLOSE_ACTION: - hide(); break; - - case "togglePopup": - if (getControl().isShowing()) { - hide(); - } else { - show(); - } - break; - - default: - super.callAction(name); - } + DATE_PICKER_BINDINGS.addAll(COMBO_BOX_BASE_BINDINGS); } @Override public void onAutoHide() { --- old/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxPopupControl.java 2015-05-11 15:53:47.719036625 -0700 +++ new/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxPopupControl.java 2015-05-11 15:53:47.627036624 -0700 @@ -89,11 +89,23 @@ // of the conditions below. if (ke.getTarget().equals(textField)) return; - // Fix for the regression noted in a comment in RT-29885. - // This forwards the event down into the TextField when - // the key event is actually received by the ComboBox. - textField.fireEvent(ke.copyFor(textField, textField)); - ke.consume(); + switch (ke.getCode()) { + case ESCAPE: + case F10: + // Allow to bubble up. + break; + + case ENTER: + handleKeyEvent(ke, true); + break; + + default: + // Fix for the regression noted in a comment in RT-29885. + // This forwards the event down into the TextField when + // the key event is actually received by the ComboBox. + textField.fireEvent(ke.copyFor(textField, textField)); + ke.consume(); + } } }); @@ -317,11 +329,6 @@ * * **************************************************************************/ - private EventHandler textFieldKeyEventHandler = event -> { - if (getEditor() != null && textField != null) { - handleKeyEvent(event, true); - } - }; private EventHandler textFieldMouseEventHandler = event -> { ComboBoxBase comboBoxBase = getSkinnable(); if (!event.getTarget().equals(comboBoxBase)) { @@ -357,28 +364,13 @@ protected TextField getEditableInputNode() { if (textField == null && getEditor() != null) { textField = getEditor(); - textField.focusTraversableProperty().bindBidirectional(comboBoxBase.focusTraversableProperty()); + textField.setFocusTraversable(false); textField.promptTextProperty().bind(comboBoxBase.promptTextProperty()); textField.tooltipProperty().bind(comboBoxBase.tooltipProperty()); // Fix for RT-21406: ComboBox do not show initial text value initialTextFieldValue = textField.getText(); // End of fix (see updateDisplayNode below for the related code) - - textField.focusedProperty().addListener((ov, t, hasFocus) -> { - if (getEditor() != null) { - // Fix for RT-29885 - comboBoxBase.getProperties().put("FOCUSED", hasFocus); - // --- end of RT-29885 - - // RT-21454 starts here - if (!hasFocus) { - setTextFromTextFieldIntoComboBoxValue(); - } - pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, hasFocus); - // --- end of RT-21454 - } - }); } return textField; @@ -441,23 +433,25 @@ if (ke.getCode() == KeyCode.ENTER) { setTextFromTextFieldIntoComboBoxValue(); - if (doConsume) ke.consume(); + if (doConsume && comboBoxBase.getOnAction() != null) { + ke.consume(); + } else { + forwardToParent(ke); + } } else if (ke.getCode() == KeyCode.F4) { if (ke.getEventType() == KeyEvent.KEY_RELEASED) { if (comboBoxBase.isShowing()) comboBoxBase.hide(); else comboBoxBase.show(); } ke.consume(); // we always do a consume here (otherwise unit tests fail) - } else if (ke.getCode() == KeyCode.F10 || ke.getCode() == KeyCode.ESCAPE) { - // RT-23275: The TextField fires F10 and ESCAPE key events - // up to the parent, which are then fired back at the - // TextField, and this ends up in an infinite loop until - // the stack overflows. So, here we consume these two - // events and stop them from going any further. - if (doConsume) ke.consume(); } } + private void forwardToParent(KeyEvent event) { + if (comboBoxBase.getParent() != null) { + comboBoxBase.getParent().fireEvent(event); + } + } protected void updateEditable() { TextField newTextField = getEditor(); @@ -465,7 +459,6 @@ if (getEditor() == null) { // remove event filters if (textField != null) { - textField.removeEventFilter(KeyEvent.ANY, textFieldKeyEventHandler); textField.removeEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler); textField.removeEventFilter(DragEvent.ANY, textFieldDragEventHandler); @@ -473,7 +466,6 @@ } } else if (newTextField != null) { // add event filters - newTextField.addEventFilter(KeyEvent.ANY, textFieldKeyEventHandler); // Fix for RT-31093 - drag events from the textfield were not surfacing // properly for the ComboBox. @@ -523,6 +515,12 @@ public static final class FakeFocusTextField extends TextField { + @Override public void requestFocus() { + if (getParent() != null) { + getParent().requestFocus(); + } + } + public void setFakeFocus(boolean b) { setFocused(b); } --- old/modules/controls/src/test/java/javafx/scene/control/ComboBoxTest.java 2015-05-11 15:53:48.339036630 -0700 +++ new/modules/controls/src/test/java/javafx/scene/control/ComboBoxTest.java 2015-05-11 15:53:48.247036630 -0700 @@ -1208,9 +1208,9 @@ StageLoader sl = new StageLoader(cb); - cb.getEditor().requestFocus(); + cb.requestFocus(); cb.getEditor().setText("Test"); - KeyEventFirer keyboard = new KeyEventFirer(cb.getEditor()); + KeyEventFirer keyboard = new KeyEventFirer(cb); keyboard.doKeyPress(KeyCode.ENTER); assertEquals(1, test_rt35586_count); @@ -1654,8 +1654,12 @@ cb1Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT); assertTrue("Expect cb2 to be focused, but actual focus owner is: " + scene.getFocusOwner(), cb2.isFocused()); - assertEquals("Expect cb2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(), - cb2.getEditor(), scene.getFocusOwner()); + // Updated with fix for RT-34602: The TextField now never gets + // focus (it's just faking it). + // assertEquals("Expect cb2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(), + // cb2.getEditor(), scene.getFocusOwner()); + assertEquals("Expect cb2 to be focused, but actual focus owner is: " + scene.getFocusOwner(), + cb2, scene.getFocusOwner()); // This is where the second half of the bug appears, as we are stuck in // the FakeFocusTextField of cb2, we never make it to cb1 --- old/modules/controls/src/test/java/javafx/scene/control/DatePickerTest.java 2015-05-11 15:53:48.935036635 -0700 +++ new/modules/controls/src/test/java/javafx/scene/control/DatePickerTest.java 2015-05-11 15:53:48.839036634 -0700 @@ -432,9 +432,9 @@ StageLoader sl = new StageLoader(dp); - dp.getEditor().requestFocus(); + dp.requestFocus(); dp.getEditor().setText("1/2/2015"); - KeyEventFirer keyboard = new KeyEventFirer(dp.getEditor()); + KeyEventFirer keyboard = new KeyEventFirer(dp); keyboard.doKeyPress(KeyCode.ENTER); assertEquals(1, test_rt35586_count); @@ -592,8 +592,12 @@ dp1Keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT); assertTrue("Expect dp2 to be focused, but actual focus owner is: " + scene.getFocusOwner(), dp2.isFocused()); - assertEquals("Expect dp2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(), - dp2.getEditor(), scene.getFocusOwner()); + // Updated with fix for RT-34602: The TextField now never gets + // focus (it's just faking it). + // assertEquals("Expect dp2 TextField to be focused, but actual focus owner is: " + scene.getFocusOwner(), + // dp2.getEditor(), scene.getFocusOwner()); + assertEquals("Expect dp2 to be focused, but actual focus owner is: " + scene.getFocusOwner(), + dp2, scene.getFocusOwner()); // This is where the second half of the bug appears, as we are stuck in // the FakeFocusTextField of dp2, we never make it to dp1