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
|