modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxPopupControl.java
Print this page
rev 8726 : [mq]: rt40280
*** 24,53 ****
*/
package com.sun.javafx.scene.control.skin;
import javafx.beans.value.ObservableValue;
import javafx.css.Styleable;
import javafx.geometry.*;
import javafx.scene.control.*;
import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
import javafx.beans.InvalidationListener;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.stage.WindowEvent;
public abstract class ComboBoxPopupControl<T> extends ComboBoxBaseSkin<T> {
protected PopupControl popup;
public static final String COMBO_BOX_STYLE_CLASS = "combo-box-popup";
private boolean popupNeedsReconfiguring = true;
! public ComboBoxPopupControl(ComboBoxBase<T> comboBox, final ComboBoxBaseBehavior<T> behavior) {
! super(comboBox, behavior);
}
/**
* This method should return the Node that will be displayed when the user
* clicks on the ComboBox 'button' area.
--- 24,131 ----
*/
package com.sun.javafx.scene.control.skin;
import javafx.beans.value.ObservableValue;
+ import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.geometry.*;
import javafx.scene.control.*;
import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
+ import com.sun.javafx.scene.input.ExtendedInputMethodRequests;
+ import com.sun.javafx.scene.traversal.Algorithm;
+ import com.sun.javafx.scene.traversal.Direction;
+ import com.sun.javafx.scene.traversal.ParentTraversalEngine;
+ import com.sun.javafx.scene.traversal.TraversalContext;
import javafx.beans.InvalidationListener;
+ import javafx.event.EventHandler;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
+ import javafx.scene.input.DragEvent;
+ import javafx.scene.input.KeyCode;
+ import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.stage.WindowEvent;
+ import javafx.util.StringConverter;
public abstract class ComboBoxPopupControl<T> extends ComboBoxBaseSkin<T> {
protected PopupControl popup;
public static final String COMBO_BOX_STYLE_CLASS = "combo-box-popup";
private boolean popupNeedsReconfiguring = true;
! private final ComboBoxBase<T> comboBoxBase;
! private TextField textField;
!
! public ComboBoxPopupControl(ComboBoxBase<T> comboBoxBase, final ComboBoxBaseBehavior<T> behavior) {
! super(comboBoxBase, behavior);
! this.comboBoxBase = comboBoxBase;
!
! // editable input node
! this.textField = getEditor() != null ? getEditableInputNode() : null;
!
! // Fix for RT-29565. Without this the textField does not have a correct
! // pref width at startup, as it is not part of the scenegraph (and therefore
! // has no pref width until after the first measurements have been taken).
! if (this.textField != null) {
! getChildren().add(textField);
! }
!
! // move fake focus in to the textfield if the comboBox is editable
! comboBoxBase.focusedProperty().addListener((ov, t, hasFocus) -> {
! if (getEditor() != null) {
! // Fix for the regression noted in a comment in RT-29885.
! ((FakeFocusTextField)textField).setFakeFocus(hasFocus);
! }
! });
!
! comboBoxBase.addEventFilter(KeyEvent.ANY, ke -> {
! if (textField == null || getEditor() == null) {
! handleKeyEvent(ke, false);
! } else {
! // This prevents a stack overflow from our rebroadcasting of the
! // event to the textfield that occurs in the final else statement
! // 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();
! }
! });
!
! // RT-38978: Forward input method events to TextField if editable.
! if (comboBoxBase.getOnInputMethodTextChanged() == null) {
! comboBoxBase.setOnInputMethodTextChanged(event -> {
! if (textField != null && getEditor() != null && comboBoxBase.getScene().getFocusOwner() == comboBoxBase) {
! if (textField.getOnInputMethodTextChanged() != null) {
! textField.getOnInputMethodTextChanged().handle(event);
! }
! }
! });
! }
!
! // Fix for RT-36902, where focus traversal was getting stuck inside the ComboBox
! comboBoxBase.setImpl_traversalEngine(new ParentTraversalEngine(comboBoxBase, new Algorithm() {
! @Override public Node select(Node owner, Direction dir, TraversalContext context) {
! return null;
! }
!
! @Override public Node selectFirst(TraversalContext context) {
! return null;
! }
!
! @Override public Node selectLast(TraversalContext context) {
! return null;
! }
! }));
!
! updateEditable();
}
/**
* This method should return the Node that will be displayed when the user
* clicks on the ComboBox 'button' area.
*** 226,231 ****
--- 304,554 ----
((Region)popupContent).setMinSize(newWidth, newHeight);
((Region)popupContent).setPrefSize(newWidth, newHeight);
}
}
}
+
+
+
+
+
+ /***************************************************************************
+ * *
+ * TextField Listeners *
+ * *
+ **************************************************************************/
+
+ private EventHandler<KeyEvent> textFieldKeyEventHandler = event -> {
+ if (getEditor() != null && textField != null) {
+ handleKeyEvent(event, true);
+ }
+ };
+ private EventHandler<MouseEvent> textFieldMouseEventHandler = event -> {
+ ComboBoxBase<T> comboBoxBase = getSkinnable();
+ if (!event.getTarget().equals(comboBoxBase)) {
+ comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
+ event.consume();
+ }
+ };
+ private EventHandler<DragEvent> textFieldDragEventHandler = event -> {
+ ComboBoxBase<T> comboBoxBase = getSkinnable();
+ if (!event.getTarget().equals(comboBoxBase)) {
+ comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
+ event.consume();
+ }
+ };
+
+
+ /**
+ * Subclasses are responsible for getting the editor. This will be removed
+ * in FX 9 when the editor property is moved up to ComboBoxBase.
+ *
+ * Note: ComboBoxListViewSkin should return null if editable is false, even
+ * if the ComboBox does have an editor set.
+ */
+ protected abstract TextField getEditor();
+
+ /**
+ * Subclasses are responsible for getting the converter. This will be
+ * removed in FX 9 when the converter property is moved up to ComboBoxBase.
+ */
+ protected abstract StringConverter<T> getConverter();
+
+ private String initialTextFieldValue = null;
+ protected TextField getEditableInputNode() {
+ if (textField == null && getEditor() != null) {
+ textField = getEditor();
+ textField.focusTraversableProperty().bindBidirectional(comboBoxBase.focusTraversableProperty());
+ 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;
+ }
+
+ protected void setTextFromTextFieldIntoComboBoxValue() {
+ if (getEditor() != null) {
+ StringConverter<T> c = getConverter();
+ if (c != null) {
+ T oldValue = comboBoxBase.getValue();
+ T value = oldValue;
+ String text = textField.getText();
+
+ // conditional check here added due to RT-28245
+ if (oldValue == null && (text == null || text.isEmpty())) {
+ value = null;
+ } else {
+ try {
+ value = c.fromString(text);
+ } catch (/*DateTimeParseException*/ Exception ex) {
+ }
+ }
+
+ if ((value == null && oldValue == null) || (value != null && value.equals(oldValue))) {
+ // no point updating values needlessly (as they are the same)
+ return;
+ }
+
+ comboBoxBase.setValue(value);
+ updateDisplayNode();
+ }
+ }
+ }
+
+ protected void updateDisplayNode() {
+ if (textField != null && getEditor() != null) {
+ T value = comboBoxBase.getValue();
+ StringConverter<T> c = getConverter();
+
+ if (initialTextFieldValue != null && ! initialTextFieldValue.isEmpty()) {
+ // Remainder of fix for RT-21406: ComboBox do not show initial text value
+ textField.setText(initialTextFieldValue);
+ initialTextFieldValue = null;
+ // end of fix
+ } else {
+ String stringValue = c.toString(value);
+ if (value == null || stringValue == null) {
+ textField.setText("");
+ } else if (! stringValue.equals(textField.getText())) {
+ textField.setText(stringValue);
+ }
+ }
+ }
+ }
+
+
+ private void handleKeyEvent(KeyEvent ke, boolean doConsume) {
+ // When the user hits the enter or F4 keys, we respond before
+ // ever giving the event to the TextField.
+ if (ke.getCode() == KeyCode.ENTER) {
+ setTextFromTextFieldIntoComboBoxValue();
+
+ if (doConsume) ke.consume();
+ } 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();
+ }
+ }
+
+
+ protected void updateEditable() {
+ TextField newTextField = getEditor();
+
+ 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);
+
+ comboBoxBase.setInputMethodRequests(null);
+ }
+ } 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.
+ newTextField.addEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler);
+ newTextField.addEventFilter(DragEvent.ANY, textFieldDragEventHandler);
+
+ // RT-38978: Forward input method requests to TextField.
+ comboBoxBase.setInputMethodRequests(new ExtendedInputMethodRequests() {
+ @Override public Point2D getTextLocation(int offset) {
+ return newTextField.getInputMethodRequests().getTextLocation(offset);
+ }
+
+ @Override public int getLocationOffset(int x, int y) {
+ return newTextField.getInputMethodRequests().getLocationOffset(x, y);
+ }
+
+ @Override public void cancelLatestCommittedText() {
+ newTextField.getInputMethodRequests().cancelLatestCommittedText();
+ }
+
+ @Override public String getSelectedText() {
+ return newTextField.getInputMethodRequests().getSelectedText();
+ }
+
+ @Override public int getInsertPositionOffset() {
+ return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getInsertPositionOffset();
+ }
+
+ @Override public String getCommittedText(int begin, int end) {
+ return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedText(begin, end);
+ }
+
+ @Override public int getCommittedTextLength() {
+ return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedTextLength();
+ }
+ });
+ }
+
+ textField = newTextField;
+ }
+
+ /***************************************************************************
+ * *
+ * Support classes *
+ * *
+ **************************************************************************/
+
+ public static final class FakeFocusTextField extends TextField {
+
+ public void setFakeFocus(boolean b) {
+ setFocused(b);
+ }
+
+ @Override
+ public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
+ switch (attribute) {
+ case FOCUS_ITEM:
+ /* Internally comboBox reassign its focus the text field.
+ * For the accessibility perspective it is more meaningful
+ * if the focus stays with the comboBox control.
+ */
+ return getParent();
+ default: return super.queryAccessibleAttribute(attribute, parameters);
+ }
+ }
+ }
+
+
+
+ /***************************************************************************
+ * *
+ * Stylesheet Handling *
+ * *
+ **************************************************************************/
+
+ private static PseudoClass CONTAINS_FOCUS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("contains-focus");
+
}