1 /* 2 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control; 27 28 import com.sun.javafx.scene.control.FakeFocusTextField; 29 import javafx.beans.InvalidationListener; 30 import javafx.beans.Observable; 31 import javafx.beans.WeakInvalidationListener; 32 import javafx.collections.WeakListChangeListener; 33 import javafx.scene.control.skin.ComboBoxListViewSkin; 34 import javafx.beans.property.*; 35 import javafx.beans.value.ChangeListener; 36 import javafx.beans.value.ObservableValue; 37 import javafx.collections.FXCollections; 38 import javafx.collections.ListChangeListener; 39 import javafx.collections.ObservableList; 40 import javafx.scene.AccessibleAttribute; 41 import javafx.scene.AccessibleRole; 42 import javafx.scene.Node; 43 import javafx.util.Callback; 44 import javafx.util.StringConverter; 45 46 import java.lang.ref.WeakReference; 47 48 /** 49 * An implementation of the {@link ComboBoxBase} abstract class for the most common 50 * form of ComboBox, where a popup list is shown to users providing them with 51 * a choice that they may select from. For more information around the general 52 * concepts and API of ComboBox, refer to the {@link ComboBoxBase} class 53 * documentation. 54 * 55 * <p>On top of ComboBoxBase, the ComboBox class introduces additional API. Most 56 * importantly, it adds an {@link #itemsProperty() items} property that works in 57 * much the same way as the ListView {@link ListView#itemsProperty() items} 58 * property. In other words, it is the content of the items list that is displayed 59 * to users when they click on the ComboBox button. 60 * 61 * <p>The ComboBox exposes the {@link #valueProperty()} from 62 * {@link javafx.scene.control.ComboBoxBase}, but there are some important points 63 * of the value property that need to be understood in relation to ComboBox. 64 * These include: 65 * 66 * <ol> 67 * <li>The value property <strong>is not</strong> constrained to items contained 68 * within the items list - it can be anything as long as it is a valid value 69 * of type T.</li> 70 * <li>If the value property is set to a non-null object, and subsequently the 71 * items list is cleared, the value property <strong>is not</strong> nulled out.</li> 72 * <li>Clearing the {@link javafx.scene.control.SelectionModel#clearSelection() 73 * selection} in the selection model <strong>does not</strong> null the value 74 * property - it remains the same as before.</li> 75 * <li>It is valid for the selection model to have a selection set to a given 76 * index even if there is no items in the list (or less items in the list than 77 * the given index). Once the items list is further populated, such that the 78 * list contains enough items to have an item in the given index, both the 79 * selection model {@link SelectionModel#selectedItemProperty()} and 80 * value property will be updated to have this value. This is inconsistent with 81 * other controls that use a selection model, but done intentionally for ComboBox.</li> 82 * </ol> 83 * 84 * <p>By default, when the popup list is showing, the maximum number of rows 85 * visible is 10, but this can be changed by modifying the 86 * {@link #visibleRowCountProperty() visibleRowCount} property. If the number of 87 * items in the ComboBox is less than the value of <code>visibleRowCount</code>, 88 * then the items size will be used instead so that the popup list is not 89 * exceedingly long. 90 * 91 * <p>As with ListView, it is possible to modify the 92 * {@link javafx.scene.control.SelectionModel selection model} that is used, 93 * although this is likely to be rarely changed. This is because the ComboBox 94 * enforces the need for a {@link javafx.scene.control.SingleSelectionModel} 95 * instance, and it is not likely that there is much need for alternate 96 * implementations. Nonetheless, the option is there should use cases be found 97 * for switching the selection model. 98 * 99 * <p>As the ComboBox internally renders content with a ListView, API exists in 100 * the ComboBox class to allow for a custom cell factory to be set. For more 101 * information on cell factories, refer to the {@link Cell} and {@link ListCell} 102 * classes. It is important to note that if a cell factory is set on a ComboBox, 103 * cells will only be used in the ListView that shows when the ComboBox is 104 * clicked. If you also want to customize the rendering of the 'button' area 105 * of the ComboBox, you can set a custom {@link ListCell} instance in the 106 * {@link #buttonCellProperty() button cell} property. One way of doing this 107 * is with the following code (note the use of {@code setButtonCell}: 108 * 109 * <pre> 110 * {@code 111 * Callback<ListView<String>, ListCell<String>> cellFactory = ...; 112 * ComboBox comboBox = new ComboBox(); 113 * comboBox.setItems(items); 114 * comboBox.setButtonCell(cellFactory.call(null)); 115 * comboBox.setCellFactory(cellFactory);}</pre> 116 * 117 * <p>Because a ComboBox can be {@link #editableProperty() editable}, and the 118 * default means of allowing user input is via a {@link TextField}, a 119 * {@link #converterProperty() string converter} property is provided to allow 120 * for developers to specify how to translate a users string into an object of 121 * type T, such that the {@link #valueProperty() value} property may contain it. 122 * By default the converter simply returns the String input as the user typed it, 123 * which therefore assumes that the type of the editable ComboBox is String. If 124 * a different type is specified and the ComboBox is to be editable, it is 125 * necessary to specify a custom {@link StringConverter}. 126 * 127 * <h3>A warning about inserting Nodes into the ComboBox items list</h3> 128 * ComboBox allows for the items list to contain elements of any type, including 129 * {@link Node} instances. Putting nodes into 130 * the items list is <strong>strongly not recommended</strong>. This is because 131 * the default {@link #cellFactoryProperty() cell factory} simply inserts Node 132 * items directly into the cell, including in the ComboBox 'button' area too. 133 * Because the scenegraph only allows for Nodes to be in one place at a time, 134 * this means that when an item is selected it becomes removed from the ComboBox 135 * list, and becomes visible in the button area. When selection changes the 136 * previously selected item returns to the list and the new selection is removed. 137 * 138 * <p>The recommended approach, rather than inserting Node instances into the 139 * items list, is to put the relevant information into the ComboBox, and then 140 * provide a custom {@link #cellFactoryProperty() cell factory}. For example, 141 * rather than use the following code: 142 * 143 * <pre> 144 * {@code 145 * ComboBox<Rectangle> cmb = new ComboBox<Rectangle>(); 146 * cmb.getItems().addAll( 147 * new Rectangle(10, 10, Color.RED), 148 * new Rectangle(10, 10, Color.GREEN), 149 * new Rectangle(10, 10, Color.BLUE));}</pre> 150 * 151 * <p>You should do the following:</p> 152 * 153 * <pre><code> 154 * ComboBox<Color> cmb = new ComboBox<Color>(); 155 * cmb.getItems().addAll( 156 * Color.RED, 157 * Color.GREEN, 158 * Color.BLUE); 159 * 160 * cmb.setCellFactory(new Callback<ListView<Color>, ListCell<Color>>() { 161 * @Override public ListCell<Color> call(ListView<Color> p) { 162 * return new ListCell<Color>() { 163 * private final Rectangle rectangle; 164 * { 165 * setContentDisplay(ContentDisplay.GRAPHIC_ONLY); 166 * rectangle = new Rectangle(10, 10); 167 * } 168 * 169 * @Override protected void updateItem(Color item, boolean empty) { 170 * super.updateItem(item, empty); 171 * 172 * if (item == null || empty) { 173 * setGraphic(null); 174 * } else { 175 * rectangle.setFill(item); 176 * setGraphic(rectangle); 177 * } 178 * } 179 * }; 180 * } 181 *});</code></pre> 182 * 183 * <p>Admittedly the above approach is far more verbose, but it offers the 184 * required functionality without encountering the scenegraph constraints. 185 * 186 * @param <T> The type of the value that has been selected or otherwise entered 187 * in to this ComboBox 188 * @see ComboBoxBase 189 * @see Cell 190 * @see ListCell 191 * @see StringConverter 192 * @since JavaFX 2.1 193 */ 194 public class ComboBox<T> extends ComboBoxBase<T> { 195 196 /*************************************************************************** 197 * * 198 * Static properties and methods * 199 * * 200 **************************************************************************/ 201 202 private static <T> StringConverter<T> defaultStringConverter() { 203 return new StringConverter<T>() { 204 @Override public String toString(T t) { 205 return t == null ? null : t.toString(); 206 } 207 208 @Override public T fromString(String string) { 209 return (T) string; 210 } 211 }; 212 } 213 214 215 216 /*************************************************************************** 217 * * 218 * Constructors * 219 * * 220 **************************************************************************/ 221 222 /** 223 * Creates a default ComboBox instance with an empty 224 * {@link #itemsProperty() items} list and default 225 * {@link #selectionModelProperty() selection model}. 226 */ 227 public ComboBox() { 228 this(FXCollections.<T>observableArrayList()); 229 } 230 231 /** 232 * Creates a default ComboBox instance with the provided items list and 233 * a default {@link #selectionModelProperty() selection model}. 234 * @param items the list of items 235 */ 236 public ComboBox(ObservableList<T> items) { 237 getStyleClass().add(DEFAULT_STYLE_CLASS); 238 setAccessibleRole(AccessibleRole.COMBO_BOX); 239 setItems(items); 240 setSelectionModel(new ComboBoxSelectionModel<T>(this)); 241 242 // listen to the value property input by the user, and if the value is 243 // set to something that exists in the items list, we should update the 244 // selection model to indicate that this is the selected item 245 valueProperty().addListener((ov, t, t1) -> { 246 if (getItems() == null) return; 247 248 SelectionModel<T> sm = getSelectionModel(); 249 int index = getItems().indexOf(t1); 250 251 if (index == -1) { 252 Runnable r = () -> { 253 sm.setSelectedIndex(-1); 254 sm.setSelectedItem(t1); 255 }; 256 if (sm instanceof ComboBoxSelectionModel) { 257 ((ComboBoxSelectionModel)sm).doAtomic(r); 258 } else { 259 r.run(); 260 } 261 } else { 262 // we must compare the value here with the currently selected 263 // item. If they are different, we overwrite the selection 264 // properties to reflect the new value. 265 // We do this as there can be circumstances where there are 266 // multiple instances of a value in the ComboBox items list, 267 // and if we don't check here we may change the selection 268 // mistakenly because the indexOf above will return the first 269 // instance always, and selection may be on the second or 270 // later instances. This is RT-19227. 271 T selectedItem = sm.getSelectedItem(); 272 if (selectedItem == null || ! selectedItem.equals(getValue())) { 273 sm.clearAndSelect(index); 274 } 275 } 276 }); 277 278 editableProperty().addListener(o -> { 279 // When we change from being editable to non-editable, we look for the 280 // current value in the items list. If it exists, we do not clear selection. 281 // When we change from being non-editable to editable, we do nothing 282 if (!isEditable()) { 283 // check if value is in items list 284 if (getItems() != null && !getItems().contains(getValue())) { 285 getSelectionModel().clearSelection(); 286 } 287 } 288 }); 289 290 focusedProperty().addListener(o -> { 291 if (!isFocused()) { 292 commitValue(); 293 } 294 }); 295 } 296 297 298 299 /*************************************************************************** 300 * * 301 * Properties * 302 * * 303 **************************************************************************/ 304 305 // --- items 306 /** 307 * The list of items to show within the ComboBox popup. 308 */ 309 private ObjectProperty<ObservableList<T>> items = new SimpleObjectProperty<ObservableList<T>>(this, "items"); 310 public final void setItems(ObservableList<T> value) { itemsProperty().set(value); } 311 public final ObservableList<T> getItems() {return items.get(); } 312 public ObjectProperty<ObservableList<T>> itemsProperty() { return items; } 313 314 315 // --- string converter 316 /** 317 * Converts the user-typed input (when the ComboBox is 318 * {@link #editableProperty() editable}) to an object of type T, such that 319 * the input may be retrieved via the {@link #valueProperty() value} property. 320 * @return the converter property 321 */ 322 public ObjectProperty<StringConverter<T>> converterProperty() { return converter; } 323 private ObjectProperty<StringConverter<T>> converter = 324 new SimpleObjectProperty<StringConverter<T>>(this, "converter", ComboBox.<T>defaultStringConverter()); 325 public final void setConverter(StringConverter<T> value) { converterProperty().set(value); } 326 public final StringConverter<T> getConverter() {return converterProperty().get(); } 327 328 329 // --- cell factory 330 /** 331 * Providing a custom cell factory allows for complete customization of the 332 * rendering of items in the ComboBox. Refer to the {@link Cell} javadoc 333 * for more information on cell factories. 334 */ 335 private ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory = 336 new SimpleObjectProperty<Callback<ListView<T>, ListCell<T>>>(this, "cellFactory"); 337 public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) { cellFactoryProperty().set(value); } 338 public final Callback<ListView<T>, ListCell<T>> getCellFactory() {return cellFactoryProperty().get(); } 339 public ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactoryProperty() { return cellFactory; } 340 341 342 // --- button cell 343 /** 344 * The button cell is used to render what is shown in the ComboBox 'button' 345 * area. If a cell is set here, it does not change the rendering of the 346 * ComboBox popup list - that rendering is controlled via the 347 * {@link #cellFactoryProperty() cell factory} API. 348 * @return the button cell property 349 * @since JavaFX 2.2 350 */ 351 public ObjectProperty<ListCell<T>> buttonCellProperty() { return buttonCell; } 352 private ObjectProperty<ListCell<T>> buttonCell = 353 new SimpleObjectProperty<ListCell<T>>(this, "buttonCell"); 354 public final void setButtonCell(ListCell<T> value) { buttonCellProperty().set(value); } 355 public final ListCell<T> getButtonCell() {return buttonCellProperty().get(); } 356 357 358 // --- Selection Model 359 /** 360 * The selection model for the ComboBox. A ComboBox only supports 361 * single selection. 362 */ 363 private ObjectProperty<SingleSelectionModel<T>> selectionModel = new SimpleObjectProperty<SingleSelectionModel<T>>(this, "selectionModel") { 364 private SingleSelectionModel<T> oldSM = null; 365 @Override protected void invalidated() { 366 if (oldSM != null) { 367 oldSM.selectedItemProperty().removeListener(selectedItemListener); 368 } 369 SingleSelectionModel<T> sm = get(); 370 oldSM = sm; 371 if (sm != null) { 372 sm.selectedItemProperty().addListener(selectedItemListener); 373 } 374 } 375 }; 376 public final void setSelectionModel(SingleSelectionModel<T> value) { selectionModel.set(value); } 377 public final SingleSelectionModel<T> getSelectionModel() { return selectionModel.get(); } 378 public final ObjectProperty<SingleSelectionModel<T>> selectionModelProperty() { return selectionModel; } 379 380 381 // --- Visible Row Count 382 /** 383 * The maximum number of rows to be visible in the ComboBox popup when it is 384 * showing. By default this value is 10, but this can be changed to increase 385 * or decrease the height of the popup. 386 */ 387 private IntegerProperty visibleRowCount 388 = new SimpleIntegerProperty(this, "visibleRowCount", 10); 389 public final void setVisibleRowCount(int value) { visibleRowCount.set(value); } 390 public final int getVisibleRowCount() { return visibleRowCount.get(); } 391 public final IntegerProperty visibleRowCountProperty() { return visibleRowCount; } 392 393 394 // --- Editor 395 private TextField textField; 396 /** 397 * The editor for the ComboBox. The editor is null if the ComboBox is not 398 * {@link #editableProperty() editable}. 399 * @since JavaFX 2.2 400 */ 401 private ReadOnlyObjectWrapper<TextField> editor; 402 public final TextField getEditor() { 403 return editorProperty().get(); 404 } 405 public final ReadOnlyObjectProperty<TextField> editorProperty() { 406 if (editor == null) { 407 editor = new ReadOnlyObjectWrapper<>(this, "editor"); 408 textField = new FakeFocusTextField(); 409 editor.set(textField); 410 } 411 return editor.getReadOnlyProperty(); 412 } 413 414 415 // --- Placeholder Node 416 private ObjectProperty<Node> placeholder; 417 /** 418 * This Node is shown to the user when the ComboBox has no content to show. 419 * The placeholder node is shown in the ComboBox popup area 420 * when the items list is null or empty. 421 * @return the placeholder property 422 * @since JavaFX 8.0 423 */ 424 public final ObjectProperty<Node> placeholderProperty() { 425 if (placeholder == null) { 426 placeholder = new SimpleObjectProperty<Node>(this, "placeholder"); 427 } 428 return placeholder; 429 } 430 public final void setPlaceholder(Node value) { 431 placeholderProperty().set(value); 432 } 433 public final Node getPlaceholder() { 434 return placeholder == null ? null : placeholder.get(); 435 } 436 437 438 439 /*************************************************************************** 440 * * 441 * Methods * 442 * * 443 **************************************************************************/ 444 445 /** {@inheritDoc} */ 446 @Override protected Skin<?> createDefaultSkin() { 447 return new ComboBoxListViewSkin<T>(this); 448 } 449 450 /** 451 * If the ComboBox is {@link #editableProperty() editable}, calling this method will attempt to 452 * commit the current text and convert it to a {@link #valueProperty() value}. 453 * @since 9 454 */ 455 public final void commitValue() { 456 if (!isEditable()) return; 457 String text = getEditor().getText(); 458 StringConverter<T> converter = getConverter(); 459 if (converter != null) { 460 T value = converter.fromString(text); 461 setValue(value); 462 } 463 } 464 465 /** 466 * If the ComboBox is {@link #editableProperty() editable}, calling this method will attempt to 467 * replace the editor text with the last committed {@link #valueProperty() value}. 468 * @since 9 469 */ 470 public final void cancelEdit() { 471 if (!isEditable()) return; 472 final T committedValue = getValue(); 473 StringConverter<T> converter = getConverter(); 474 if (converter != null) { 475 String valueString = converter.toString(committedValue); 476 getEditor().setText(valueString); 477 } 478 } 479 480 481 482 /*************************************************************************** 483 * * 484 * Callbacks and Events * 485 * * 486 **************************************************************************/ 487 488 // Listen to changes in the selectedItem property of the SelectionModel. 489 // When it changes, set the selectedItem in the value property. 490 private ChangeListener<T> selectedItemListener = new ChangeListener<T>() { 491 @Override public void changed(ObservableValue<? extends T> ov, T t, T t1) { 492 if (wasSetAllCalled && t1 == null) { 493 // no-op: fix for RT-22572 where the developer was completely 494 // replacing all items in the ComboBox, and expecting the 495 // selection (and ComboBox.value) to remain set. If this isn't 496 // here, we would updateValue(null). 497 // Additional fix for RT-22937: adding the '&& t1 == null'. 498 // Without this, there would be circumstances where the user 499 // selecting a new value from the ComboBox would end up in here, 500 // when we really should go into the updateValue(t1) call below. 501 // We should only ever go into this clause if t1 is null. 502 } else { 503 updateValue(t1); 504 } 505 506 wasSetAllCalled = false; 507 } 508 }; 509 510 511 512 /*************************************************************************** 513 * * 514 * Private methods * 515 * * 516 **************************************************************************/ 517 518 private void updateValue(T newValue) { 519 if (! valueProperty().isBound()) { 520 setValue(newValue); 521 } 522 } 523 524 525 526 527 /*************************************************************************** 528 * * 529 * Stylesheet Handling * 530 * * 531 **************************************************************************/ 532 533 private static final String DEFAULT_STYLE_CLASS = "combo-box"; 534 535 private boolean wasSetAllCalled = false; 536 private int previousItemCount = -1; 537 538 // package for testing 539 static class ComboBoxSelectionModel<T> extends SingleSelectionModel<T> { 540 private final ComboBox<T> comboBox; 541 542 private boolean atomic = false; 543 private void doAtomic(Runnable r) { 544 atomic = true; 545 r.run(); 546 atomic = false; 547 } 548 549 public ComboBoxSelectionModel(final ComboBox<T> cb) { 550 if (cb == null) { 551 throw new NullPointerException("ComboBox can not be null"); 552 } 553 this.comboBox = cb; 554 this.comboBox.previousItemCount = getItemCount(); 555 556 selectedIndexProperty().addListener(valueModel -> { 557 // we used to lazily retrieve the selected item, but now we just 558 // do it when the selection changes. 559 if (atomic) return; 560 setSelectedItem(getModelItem(getSelectedIndex())); 561 }); 562 563 /* 564 * The following two listeners are used in conjunction with 565 * SelectionModel.select(T obj) to allow for a developer to select 566 * an item that is not actually in the data model. When this occurs, 567 * we actively try to find an index that matches this object, going 568 * so far as to actually watch for all changes to the items list, 569 * rechecking each time. 570 */ 571 itemsObserver = new InvalidationListener() { 572 private WeakReference<ObservableList<T>> weakItemsRef = new WeakReference<>(comboBox.getItems()); 573 574 @Override public void invalidated(Observable observable) { 575 ObservableList<T> oldItems = weakItemsRef.get(); 576 weakItemsRef = new WeakReference<>(comboBox.getItems()); 577 updateItemsObserver(oldItems, comboBox.getItems()); 578 comboBox.previousItemCount = getItemCount(); 579 } 580 }; 581 this.comboBox.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver)); 582 if (comboBox.getItems() != null) { 583 this.comboBox.getItems().addListener(weakItemsContentObserver); 584 } 585 } 586 587 // watching for changes to the items list content 588 private final ListChangeListener<T> itemsContentObserver = new ListChangeListener<T>() { 589 @Override public void onChanged(Change<? extends T> c) { 590 if (comboBox.getItems() == null || comboBox.getItems().isEmpty()) { 591 setSelectedIndex(-1); 592 } else if (getSelectedIndex() == -1 && getSelectedItem() != null) { 593 int newIndex = comboBox.getItems().indexOf(getSelectedItem()); 594 if (newIndex != -1) { 595 setSelectedIndex(newIndex); 596 } 597 } 598 599 int shift = 0; 600 while (c.next()) { 601 comboBox.wasSetAllCalled = comboBox.previousItemCount == c.getRemovedSize(); 602 603 if (c.wasReplaced()) { 604 // no-op 605 } else if (c.wasAdded() || c.wasRemoved()) { 606 if (c.getFrom() <= getSelectedIndex() && getSelectedIndex()!= -1) { 607 shift += c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize(); 608 } 609 } 610 } 611 612 if (shift != 0) { 613 clearAndSelect(getSelectedIndex() + shift); 614 } else if (comboBox.wasSetAllCalled && getSelectedIndex() >= 0 && getSelectedItem() != null) { 615 // try to find the previously selected item 616 T selectedItem = getSelectedItem(); 617 for (int i = 0; i < comboBox.getItems().size(); i++) { 618 if (selectedItem.equals(comboBox.getItems().get(i))) { 619 comboBox.setValue(null); 620 setSelectedItem(null); 621 setSelectedIndex(i); 622 break; 623 } 624 } 625 } 626 627 comboBox.previousItemCount = getItemCount(); 628 } 629 }; 630 631 // watching for changes to the items list 632 private final InvalidationListener itemsObserver; 633 634 private WeakListChangeListener<T> weakItemsContentObserver = 635 new WeakListChangeListener<T>(itemsContentObserver); 636 637 638 private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) { 639 // update listeners 640 if (oldList != null) { 641 oldList.removeListener(weakItemsContentObserver); 642 } 643 if (newList != null) { 644 newList.addListener(weakItemsContentObserver); 645 } 646 647 // when the items list totally changes, we should clear out 648 // the selection and focus 649 int newValueIndex = -1; 650 if (newList != null) { 651 T value = comboBox.getValue(); 652 if (value != null) { 653 newValueIndex = newList.indexOf(value); 654 } 655 } 656 setSelectedIndex(newValueIndex); 657 } 658 659 // API Implementation 660 @Override protected T getModelItem(int index) { 661 final ObservableList<T> items = comboBox.getItems(); 662 if (items == null) return null; 663 if (index < 0 || index >= items.size()) return null; 664 return items.get(index); 665 } 666 667 @Override protected int getItemCount() { 668 final ObservableList<T> items = comboBox.getItems(); 669 return items == null ? 0 : items.size(); 670 } 671 } 672 673 /*************************************************************************** 674 * * 675 * Accessibility handling * 676 * * 677 **************************************************************************/ 678 679 @Override 680 public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { 681 switch(attribute) { 682 case TEXT: 683 String accText = getAccessibleText(); 684 if (accText != null && !accText.isEmpty()) return accText; 685 686 //let the skin first. 687 Object title = super.queryAccessibleAttribute(attribute, parameters); 688 if (title != null) return title; 689 StringConverter<T> converter = getConverter(); 690 if (converter == null) { 691 return getValue() != null ? getValue().toString() : ""; 692 } 693 return converter.toString(getValue()); 694 default: return super.queryAccessibleAttribute(attribute, parameters); 695 } 696 } 697 698 }