modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ComboBoxPopupControl.java

Print this page
rev 9038 : RT-34620: [ComboBox, DatePicker] Buttons set to default/cancel are not reacting to ComboBox enter/esc keys


  72             getChildren().add(textField);
  73         }
  74         
  75         // move fake focus in to the textfield if the comboBox is editable
  76         comboBoxBase.focusedProperty().addListener((ov, t, hasFocus) -> {
  77             if (getEditor() != null) {
  78                 // Fix for the regression noted in a comment in RT-29885.
  79                 ((FakeFocusTextField)textField).setFakeFocus(hasFocus);
  80             }
  81         });
  82 
  83         comboBoxBase.addEventFilter(KeyEvent.ANY, ke -> {
  84             if (textField == null || getEditor() == null) {
  85                 handleKeyEvent(ke, false);
  86             } else {
  87                 // This prevents a stack overflow from our rebroadcasting of the
  88                 // event to the textfield that occurs in the final else statement
  89                 // of the conditions below.
  90                 if (ke.getTarget().equals(textField)) return;
  91 











  92                 // Fix for the regression noted in a comment in RT-29885.
  93                 // This forwards the event down into the TextField when
  94                 // the key event is actually received by the ComboBox.
  95                 textField.fireEvent(ke.copyFor(textField, textField));
  96                 ke.consume();
  97             }

  98         });
  99 
 100         // RT-38978: Forward input method events to TextField if editable.
 101         if (comboBoxBase.getOnInputMethodTextChanged() == null) {
 102             comboBoxBase.setOnInputMethodTextChanged(event -> {
 103                 if (textField != null && getEditor() != null && comboBoxBase.getScene().getFocusOwner() == comboBoxBase) {
 104                     if (textField.getOnInputMethodTextChanged() != null) {
 105                         textField.getOnInputMethodTextChanged().handle(event);
 106                     }
 107                 }
 108             });
 109         }
 110 
 111         // Fix for RT-36902, where focus traversal was getting stuck inside the ComboBox
 112         comboBoxBase.setImpl_traversalEngine(new ParentTraversalEngine(comboBoxBase, new Algorithm() {
 113             @Override public Node select(Node owner, Direction dir, TraversalContext context) {
 114                 return null;
 115             }
 116 
 117             @Override public Node selectFirst(TraversalContext context) {


 300             // Resizing content to resolve issues such as RT-32582 and RT-33700
 301             // (where RT-33700 was introduced due to a previous fix for RT-32582)
 302             popupContent.resize(newWidth, newHeight);
 303             if (popupContent instanceof Region) {
 304                 ((Region)popupContent).setMinSize(newWidth, newHeight);
 305                 ((Region)popupContent).setPrefSize(newWidth, newHeight);
 306             }
 307         }
 308     }
 309 
 310 
 311 
 312 
 313 
 314     /***************************************************************************
 315      *                                                                         *
 316      * TextField Listeners                                                     *
 317      *                                                                         *
 318      **************************************************************************/
 319     
 320     private EventHandler<KeyEvent> textFieldKeyEventHandler = event -> {
 321         if (getEditor() != null && textField != null) {
 322             handleKeyEvent(event, true);
 323         }
 324     };
 325     private EventHandler<MouseEvent> textFieldMouseEventHandler = event -> {
 326         ComboBoxBase<T> comboBoxBase = getSkinnable();
 327         if (!event.getTarget().equals(comboBoxBase)) {
 328             comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
 329             event.consume();
 330         }
 331     };
 332     private EventHandler<DragEvent> textFieldDragEventHandler = event -> {
 333         ComboBoxBase<T> comboBoxBase = getSkinnable();
 334         if (!event.getTarget().equals(comboBoxBase)) {
 335             comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
 336             event.consume();
 337         }
 338     };
 339 
 340 
 341     /**
 342      * Subclasses are responsible for getting the editor. This will be removed
 343      * in FX 9 when the editor property is moved up to ComboBoxBase.
 344      *
 345      * Note: ComboBoxListViewSkin should return null if editable is false, even
 346      * if the ComboBox does have an editor set.
 347      */
 348     protected abstract TextField getEditor();
 349 
 350     /**
 351      * Subclasses are responsible for getting the converter. This will be
 352      * removed in FX 9 when the converter property is moved up to ComboBoxBase.
 353      */
 354     protected abstract StringConverter<T> getConverter();
 355 
 356     private String initialTextFieldValue = null;
 357     protected TextField getEditableInputNode() {
 358         if (textField == null && getEditor() != null) {
 359             textField = getEditor();
 360             textField.focusTraversableProperty().bindBidirectional(comboBoxBase.focusTraversableProperty());
 361             textField.promptTextProperty().bind(comboBoxBase.promptTextProperty());
 362             textField.tooltipProperty().bind(comboBoxBase.tooltipProperty());
 363 
 364             // Fix for RT-21406: ComboBox do not show initial text value
 365             initialTextFieldValue = textField.getText();
 366             // End of fix (see updateDisplayNode below for the related code)
 367 
 368             textField.focusedProperty().addListener((ov, t, hasFocus) -> {
 369                 if (getEditor() != null) {
 370                     // Fix for RT-29885
 371                     comboBoxBase.getProperties().put("FOCUSED", hasFocus);
 372                     // --- end of RT-29885
 373 
 374                     // RT-21454 starts here
 375                     if (!hasFocus) {
 376                         setTextFromTextFieldIntoComboBoxValue();
 377                     }
 378                     pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, hasFocus);
 379                     // --- end of RT-21454
 380                 }
 381             });
 382         }
 383 
 384         return textField;
 385     }
 386 
 387     protected void setTextFromTextFieldIntoComboBoxValue() {
 388         if (getEditor() != null) {
 389             StringConverter<T> c = getConverter();
 390             if (c != null) {
 391                 T oldValue = comboBoxBase.getValue();
 392                 T value = oldValue;
 393                 String text = textField.getText();
 394 
 395                 // conditional check here added due to RT-28245
 396                 if (oldValue == null && (text == null || text.isEmpty())) {
 397                     value = null;
 398                 } else {
 399                     try {
 400                         value = c.fromString(text);
 401                     } catch (Exception ex) {


 424                 initialTextFieldValue = null;
 425                 // end of fix
 426             } else {
 427                 String stringValue = c.toString(value);
 428                 if (value == null || stringValue == null) {
 429                     textField.setText("");
 430                 } else if (! stringValue.equals(textField.getText())) {
 431                     textField.setText(stringValue);
 432                 }
 433             }
 434         }
 435     }
 436 
 437 
 438     private void handleKeyEvent(KeyEvent ke, boolean doConsume) {
 439         // When the user hits the enter or F4 keys, we respond before
 440         // ever giving the event to the TextField.
 441         if (ke.getCode() == KeyCode.ENTER) {
 442             setTextFromTextFieldIntoComboBoxValue();
 443 
 444             if (doConsume) ke.consume();




 445         } else if (ke.getCode() == KeyCode.F4) {
 446             if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
 447                 if (comboBoxBase.isShowing()) comboBoxBase.hide();
 448                 else comboBoxBase.show();
 449             }
 450             ke.consume(); // we always do a consume here (otherwise unit tests fail)
 451         } else if (ke.getCode() == KeyCode.F10 || ke.getCode() == KeyCode.ESCAPE) {
 452             // RT-23275: The TextField fires F10 and ESCAPE key events
 453             // up to the parent, which are then fired back at the
 454             // TextField, and this ends up in an infinite loop until
 455             // the stack overflows. So, here we consume these two
 456             // events and stop them from going any further.
 457             if (doConsume) ke.consume();
 458         }
 459     }
 460 





 461 
 462     protected void updateEditable() {
 463         TextField newTextField = getEditor();
 464 
 465         if (getEditor() == null) {
 466             // remove event filters
 467             if (textField != null) {
 468                 textField.removeEventFilter(KeyEvent.ANY, textFieldKeyEventHandler);
 469                 textField.removeEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler);
 470                 textField.removeEventFilter(DragEvent.ANY, textFieldDragEventHandler);
 471 
 472                 comboBoxBase.setInputMethodRequests(null);
 473             }
 474         } else if (newTextField != null) {
 475             // add event filters
 476             newTextField.addEventFilter(KeyEvent.ANY, textFieldKeyEventHandler);
 477 
 478             // Fix for RT-31093 - drag events from the textfield were not surfacing
 479             // properly for the ComboBox.
 480             newTextField.addEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler);
 481             newTextField.addEventFilter(DragEvent.ANY, textFieldDragEventHandler);
 482 
 483             // RT-38978: Forward input method requests to TextField.
 484             comboBoxBase.setInputMethodRequests(new ExtendedInputMethodRequests() {
 485                 @Override public Point2D getTextLocation(int offset) {
 486                     return newTextField.getInputMethodRequests().getTextLocation(offset);
 487                 }
 488 
 489                 @Override public int getLocationOffset(int x, int y) {
 490                     return newTextField.getInputMethodRequests().getLocationOffset(x, y);
 491                 }
 492 
 493                 @Override public void cancelLatestCommittedText() {
 494                     newTextField.getInputMethodRequests().cancelLatestCommittedText();
 495                 }
 496 


 505                 @Override public String getCommittedText(int begin, int end) {
 506                     return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedText(begin, end);
 507                 }
 508 
 509                 @Override public int getCommittedTextLength() {
 510                     return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedTextLength();
 511                 }
 512             });
 513         }
 514 
 515         textField = newTextField;
 516     }
 517 
 518     /***************************************************************************
 519      *                                                                         *
 520      * Support classes                                                         *
 521      *                                                                         *
 522      **************************************************************************/
 523 
 524     public static final class FakeFocusTextField extends TextField {






 525 
 526         public void setFakeFocus(boolean b) {
 527             setFocused(b);
 528         }
 529 
 530         @Override
 531         public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 532             switch (attribute) {
 533                 case FOCUS_ITEM: 
 534                     /* Internally comboBox reassign its focus the text field.
 535                      * For the accessibility perspective it is more meaningful
 536                      * if the focus stays with the comboBox control.
 537                      */
 538                     return getParent();
 539                 default: return super.queryAccessibleAttribute(attribute, parameters);
 540             }
 541         }
 542     }
 543 
 544 


  72             getChildren().add(textField);
  73         }
  74         
  75         // move fake focus in to the textfield if the comboBox is editable
  76         comboBoxBase.focusedProperty().addListener((ov, t, hasFocus) -> {
  77             if (getEditor() != null) {
  78                 // Fix for the regression noted in a comment in RT-29885.
  79                 ((FakeFocusTextField)textField).setFakeFocus(hasFocus);
  80             }
  81         });
  82 
  83         comboBoxBase.addEventFilter(KeyEvent.ANY, ke -> {
  84             if (textField == null || getEditor() == null) {
  85                 handleKeyEvent(ke, false);
  86             } else {
  87                 // This prevents a stack overflow from our rebroadcasting of the
  88                 // event to the textfield that occurs in the final else statement
  89                 // of the conditions below.
  90                 if (ke.getTarget().equals(textField)) return;
  91 
  92                 switch (ke.getCode()) {
  93                   case ESCAPE:
  94                   case F10:
  95                       // Allow to bubble up.
  96                       break;
  97 
  98                   case ENTER:
  99                     handleKeyEvent(ke, true);
 100                     break;
 101 
 102                   default:
 103                     // Fix for the regression noted in a comment in RT-29885.
 104                     // This forwards the event down into the TextField when
 105                     // the key event is actually received by the ComboBox.
 106                     textField.fireEvent(ke.copyFor(textField, textField));
 107                     ke.consume();
 108                 }
 109             }
 110         });
 111 
 112         // RT-38978: Forward input method events to TextField if editable.
 113         if (comboBoxBase.getOnInputMethodTextChanged() == null) {
 114             comboBoxBase.setOnInputMethodTextChanged(event -> {
 115                 if (textField != null && getEditor() != null && comboBoxBase.getScene().getFocusOwner() == comboBoxBase) {
 116                     if (textField.getOnInputMethodTextChanged() != null) {
 117                         textField.getOnInputMethodTextChanged().handle(event);
 118                     }
 119                 }
 120             });
 121         }
 122 
 123         // Fix for RT-36902, where focus traversal was getting stuck inside the ComboBox
 124         comboBoxBase.setImpl_traversalEngine(new ParentTraversalEngine(comboBoxBase, new Algorithm() {
 125             @Override public Node select(Node owner, Direction dir, TraversalContext context) {
 126                 return null;
 127             }
 128 
 129             @Override public Node selectFirst(TraversalContext context) {


 312             // Resizing content to resolve issues such as RT-32582 and RT-33700
 313             // (where RT-33700 was introduced due to a previous fix for RT-32582)
 314             popupContent.resize(newWidth, newHeight);
 315             if (popupContent instanceof Region) {
 316                 ((Region)popupContent).setMinSize(newWidth, newHeight);
 317                 ((Region)popupContent).setPrefSize(newWidth, newHeight);
 318             }
 319         }
 320     }
 321 
 322 
 323 
 324 
 325 
 326     /***************************************************************************
 327      *                                                                         *
 328      * TextField Listeners                                                     *
 329      *                                                                         *
 330      **************************************************************************/
 331     





 332     private EventHandler<MouseEvent> textFieldMouseEventHandler = event -> {
 333         ComboBoxBase<T> comboBoxBase = getSkinnable();
 334         if (!event.getTarget().equals(comboBoxBase)) {
 335             comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
 336             event.consume();
 337         }
 338     };
 339     private EventHandler<DragEvent> textFieldDragEventHandler = event -> {
 340         ComboBoxBase<T> comboBoxBase = getSkinnable();
 341         if (!event.getTarget().equals(comboBoxBase)) {
 342             comboBoxBase.fireEvent(event.copyFor(comboBoxBase, comboBoxBase));
 343             event.consume();
 344         }
 345     };
 346 
 347 
 348     /**
 349      * Subclasses are responsible for getting the editor. This will be removed
 350      * in FX 9 when the editor property is moved up to ComboBoxBase.
 351      *
 352      * Note: ComboBoxListViewSkin should return null if editable is false, even
 353      * if the ComboBox does have an editor set.
 354      */
 355     protected abstract TextField getEditor();
 356 
 357     /**
 358      * Subclasses are responsible for getting the converter. This will be
 359      * removed in FX 9 when the converter property is moved up to ComboBoxBase.
 360      */
 361     protected abstract StringConverter<T> getConverter();
 362 
 363     private String initialTextFieldValue = null;
 364     protected TextField getEditableInputNode() {
 365         if (textField == null && getEditor() != null) {
 366             textField = getEditor();
 367             textField.setFocusTraversable(false);
 368             textField.promptTextProperty().bind(comboBoxBase.promptTextProperty());
 369             textField.tooltipProperty().bind(comboBoxBase.tooltipProperty());
 370 
 371             // Fix for RT-21406: ComboBox do not show initial text value
 372             initialTextFieldValue = textField.getText();
 373             // End of fix (see updateDisplayNode below for the related code)















 374         }
 375 
 376         return textField;
 377     }
 378 
 379     protected void setTextFromTextFieldIntoComboBoxValue() {
 380         if (getEditor() != null) {
 381             StringConverter<T> c = getConverter();
 382             if (c != null) {
 383                 T oldValue = comboBoxBase.getValue();
 384                 T value = oldValue;
 385                 String text = textField.getText();
 386 
 387                 // conditional check here added due to RT-28245
 388                 if (oldValue == null && (text == null || text.isEmpty())) {
 389                     value = null;
 390                 } else {
 391                     try {
 392                         value = c.fromString(text);
 393                     } catch (Exception ex) {


 416                 initialTextFieldValue = null;
 417                 // end of fix
 418             } else {
 419                 String stringValue = c.toString(value);
 420                 if (value == null || stringValue == null) {
 421                     textField.setText("");
 422                 } else if (! stringValue.equals(textField.getText())) {
 423                     textField.setText(stringValue);
 424                 }
 425             }
 426         }
 427     }
 428 
 429 
 430     private void handleKeyEvent(KeyEvent ke, boolean doConsume) {
 431         // When the user hits the enter or F4 keys, we respond before
 432         // ever giving the event to the TextField.
 433         if (ke.getCode() == KeyCode.ENTER) {
 434             setTextFromTextFieldIntoComboBoxValue();
 435 
 436             if (doConsume && comboBoxBase.getOnAction() != null) {
 437                 ke.consume();
 438             } else {
 439                 forwardToParent(ke);
 440             }
 441         } else if (ke.getCode() == KeyCode.F4) {
 442             if (ke.getEventType() == KeyEvent.KEY_RELEASED) {
 443                 if (comboBoxBase.isShowing()) comboBoxBase.hide();
 444                 else comboBoxBase.show();
 445             }
 446             ke.consume(); // we always do a consume here (otherwise unit tests fail)







 447         }
 448     }
 449 
 450     private void forwardToParent(KeyEvent event) {
 451         if (comboBoxBase.getParent() != null) {
 452             comboBoxBase.getParent().fireEvent(event);
 453         }
 454     }
 455 
 456     protected void updateEditable() {
 457         TextField newTextField = getEditor();
 458 
 459         if (getEditor() == null) {
 460             // remove event filters
 461             if (textField != null) {

 462                 textField.removeEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler);
 463                 textField.removeEventFilter(DragEvent.ANY, textFieldDragEventHandler);
 464 
 465                 comboBoxBase.setInputMethodRequests(null);
 466             }
 467         } else if (newTextField != null) {
 468             // add event filters

 469 
 470             // Fix for RT-31093 - drag events from the textfield were not surfacing
 471             // properly for the ComboBox.
 472             newTextField.addEventFilter(MouseEvent.DRAG_DETECTED, textFieldMouseEventHandler);
 473             newTextField.addEventFilter(DragEvent.ANY, textFieldDragEventHandler);
 474 
 475             // RT-38978: Forward input method requests to TextField.
 476             comboBoxBase.setInputMethodRequests(new ExtendedInputMethodRequests() {
 477                 @Override public Point2D getTextLocation(int offset) {
 478                     return newTextField.getInputMethodRequests().getTextLocation(offset);
 479                 }
 480 
 481                 @Override public int getLocationOffset(int x, int y) {
 482                     return newTextField.getInputMethodRequests().getLocationOffset(x, y);
 483                 }
 484 
 485                 @Override public void cancelLatestCommittedText() {
 486                     newTextField.getInputMethodRequests().cancelLatestCommittedText();
 487                 }
 488 


 497                 @Override public String getCommittedText(int begin, int end) {
 498                     return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedText(begin, end);
 499                 }
 500 
 501                 @Override public int getCommittedTextLength() {
 502                     return ((ExtendedInputMethodRequests)newTextField.getInputMethodRequests()).getCommittedTextLength();
 503                 }
 504             });
 505         }
 506 
 507         textField = newTextField;
 508     }
 509 
 510     /***************************************************************************
 511      *                                                                         *
 512      * Support classes                                                         *
 513      *                                                                         *
 514      **************************************************************************/
 515 
 516     public static final class FakeFocusTextField extends TextField {
 517 
 518         @Override public void requestFocus() {
 519             if (getParent() != null) {
 520                 getParent().requestFocus();
 521             }
 522         }
 523 
 524         public void setFakeFocus(boolean b) {
 525             setFocused(b);
 526         }
 527 
 528         @Override
 529         public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 530             switch (attribute) {
 531                 case FOCUS_ITEM: 
 532                     /* Internally comboBox reassign its focus the text field.
 533                      * For the accessibility perspective it is more meaningful
 534                      * if the focus stays with the comboBox control.
 535                      */
 536                     return getParent();
 537                 default: return super.queryAccessibleAttribute(attribute, parameters);
 538             }
 539         }
 540     }
 541 
 542