1 /*
   2  * Copyright (c) 2012, 2014, 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.cell;
  27 
  28 import static javafx.scene.control.cell.CellUtils.createComboBox;
  29 import javafx.beans.property.BooleanProperty;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.beans.property.SimpleBooleanProperty;
  32 import javafx.beans.property.SimpleObjectProperty;
  33 import javafx.collections.FXCollections;
  34 import javafx.collections.ObservableList;
  35 import javafx.scene.control.*;
  36 import javafx.util.Callback;
  37 import javafx.util.StringConverter;
  38 
  39 /**
  40  * A class containing a {@link ListCell} implementation that draws a
  41  * {@link ComboBox} node inside the cell.
  42  *
  43  * <p>By default, the ComboBoxListCell is rendered as a {@link Label} when not
  44  * being edited, and as a ComboBox when in editing mode. The ComboBox will, by
  45  * default, stretch to fill the entire list cell.
  46  *
  47  * <p>To create a ComboBoxListCell, it is necessary to provide zero or more
  48  * items that will be shown to the user when the {@link ComboBox} menu is
  49  * showing. These items must be of the same type as the ListView items sequence,
  50  * such that upon selection, they replace the existing value in the
  51  * {@link ListView#itemsProperty() items} list.
  52  *
  53  * @param <T> The type of the elements contained within the ListView.
  54  * @since JavaFX 2.2
  55  */
  56 public class ComboBoxListCell<T> extends ListCell<T> {
  57 
  58     /***************************************************************************
  59      *                                                                         *
  60      * Static cell factories                                                   *
  61      *                                                                         *
  62      **************************************************************************/
  63 
  64     /**
  65      * Creates a ComboBox cell factory for use in {@link ListView} controls. By
  66      * default, the ComboBoxCell is rendered as a {@link Label} when not being
  67      * edited, and as a ComboBox when in editing mode. The ComboBox will, by
  68      * default, stretch to fill the entire list cell.
  69      *
  70      * @param <T> The type of the elements contained within the ListView.
  71      * @param items Zero or more items that will be shown to the user when the
  72      *      {@link ComboBox} menu is showing. These items must be of the same
  73      *      type as the ListView items list, such that upon selection, they
  74      *      replace the existing value in the
  75      *      {@link ListView#itemsProperty() items} list.
  76      * @return A {@link Callback} that will return a ListCell that is able to
  77      *      work on the type of element contained within the ListView.
  78      */
  79     @SafeVarargs
  80     public static <T> Callback<ListView<T>, ListCell<T>> forListView(final T... items) {
  81         return forListView(FXCollections.observableArrayList(items));
  82     }
  83 
  84     /**
  85      * Creates a ComboBox cell factory for use in {@link ListView} controls. By
  86      * default, the ComboBoxCell is rendered as a {@link Label} when not being
  87      * edited, and as a ComboBox when in editing mode. The ComboBox will, by
  88      * default, stretch to fill the entire list cell.
  89      *
  90      * @param <T> The type of the elements contained within the ListView.
  91      * @param converter A {@link StringConverter} to convert the given item (of
  92      *      type T) to a String for displaying to the user.
  93      * @param items Zero or more items that will be shown to the user when the
  94      *      {@link ComboBox} menu is showing. These items must be of the same
  95      *      type as the ListView items list, such that
  96      *      upon selection, they replace the existing value in the
  97      *      {@link ListView#itemsProperty() items} list.
  98      * @return A {@link Callback} that will return a ListCell that is able to
  99      *      work on the type of element contained within the ListView.
 100      */
 101     @SafeVarargs
 102     public static <T> Callback<ListView<T>, ListCell<T>> forListView(
 103                 final StringConverter<T> converter,
 104                 final T... items) {
 105         return forListView(converter, FXCollections.observableArrayList(items));
 106     }
 107 
 108     /**
 109      * Creates a ComboBox cell factory for use in {@link ListView} controls. By
 110      * default, the ComboBoxCell is rendered as a {@link Label} when not being
 111      * edited, and as a ComboBox when in editing mode. The ComboBox will, by
 112      * default, stretch to fill the entire list cell.
 113      *
 114      * @param <T> The type of the elements contained within the ListView.
 115      * @param items An {@link ObservableList} containing zero or more items that
 116      *      will be shown to the user when the {@link ComboBox} menu is showing.
 117      *      These items must be of the same type as the ListView items sequence,
 118      *      such that upon selection, they replace the existing value in the
 119      *      {@link ListView#itemsProperty() items} list.
 120      * @return A {@link Callback} that will return a ListCell that is able to
 121      *      work on the type of element contained within the ListView.
 122      */
 123     public static <T> Callback<ListView<T>, ListCell<T>> forListView(
 124             final ObservableList<T> items) {
 125         return forListView(null, items);
 126     }
 127 
 128     /**
 129      * Creates a ComboBox cell factory for use in {@link ListView} controls. By
 130      * default, the ComboBoxCell is rendered as a {@link Label} when not being
 131      * edited, and as a ComboBox when in editing mode. The ComboBox will, by
 132      * default, stretch to fill the entire list cell.
 133      *
 134      * @param <T> The type of the elements contained within the ListView.
 135      * @param converter A {@link StringConverter} to convert the given item (of
 136      *      type T) to a String for displaying to the user.
 137      * @param items An {@link ObservableList} containing zero or more items that
 138      *      will be shown to the user when the {@link ComboBox} menu is showing.
 139      *      These items must be of the same type as the ListView items sequence,
 140      *      such that upon selection, they replace the existing value in the
 141      *      {@link ListView#itemsProperty() items} list.
 142      * @return A {@link Callback} that will return a ListCell that is able to
 143      *      work on the type of element contained within the ListView.
 144      */
 145     public static <T> Callback<ListView<T>, ListCell<T>> forListView(
 146             final StringConverter<T> converter,
 147             final ObservableList<T> items) {
 148         return list -> new ComboBoxListCell<T>(converter, items);
 149     }
 150 
 151 
 152 
 153     /***************************************************************************
 154      *                                                                         *
 155      * Fields                                                                  *
 156      *                                                                         *
 157      **************************************************************************/
 158 
 159     private final ObservableList<T> items;
 160 
 161     private ComboBox<T> comboBox;
 162 
 163 
 164 
 165     /***************************************************************************
 166      *                                                                         *
 167      * Constructors                                                            *
 168      *                                                                         *
 169      **************************************************************************/
 170 
 171     /**
 172      * Creates a default ComboBoxListCell with an empty items list.
 173      */
 174     public ComboBoxListCell() {
 175         this(FXCollections.<T>observableArrayList());
 176     }
 177 
 178     /**
 179      * Creates a default {@link ComboBoxListCell} instance with the given items
 180      * being used to populate the {@link ComboBox} when it is shown.
 181      *
 182      * @param items The items to show in the ComboBox popup menu when selected
 183      *      by the user.
 184      */
 185     @SafeVarargs
 186     public ComboBoxListCell(T... items) {
 187         this(FXCollections.observableArrayList(items));
 188     }
 189 
 190     /**
 191      * Creates a {@link ComboBoxListCell} instance with the given items
 192      * being used to populate the {@link ComboBox} when it is shown, and the
 193      * {@link StringConverter} being used to convert the item in to a
 194      * user-readable form.
 195      *
 196      * @param converter A {@link StringConverter} that can convert an item of
 197      *      type T into a user-readable string so that it may then be shown in
 198      *      the ComboBox popup menu.
 199      * @param items The items to show in the ComboBox popup menu when selected
 200      *      by the user.
 201      */
 202     @SafeVarargs
 203     public ComboBoxListCell(StringConverter<T> converter, T... items) {
 204         this(converter, FXCollections.observableArrayList(items));
 205     }
 206 
 207     /**
 208      * Creates a default {@link ComboBoxListCell} instance with the given items
 209      * being used to populate the {@link ComboBox} when it is shown.
 210      *
 211      * @param items The items to show in the ComboBox popup menu when selected
 212      *      by the user.
 213      */
 214     public ComboBoxListCell(ObservableList<T> items) {
 215         this(null, items);
 216     }
 217 
 218     /**
 219      * Creates a {@link ComboBoxListCell} instance with the given items
 220      * being used to populate the {@link ComboBox} when it is shown, and the
 221      * {@link StringConverter} being used to convert the item in to a
 222      * user-readable form.
 223      *
 224      * @param converter A {@link StringConverter} that can convert an item of
 225      *      type T into a user-readable string so that it may then be shown in
 226      *      the ComboBox popup menu.
 227      * @param items The items to show in the ComboBox popup menu when selected
 228      *      by the user.
 229      */
 230     public ComboBoxListCell(StringConverter<T> converter, ObservableList<T> items) {
 231         this.getStyleClass().add("combo-box-list-cell");
 232         this.items = items;
 233         setConverter(converter != null ? converter : CellUtils.<T>defaultStringConverter());
 234     }
 235 
 236 
 237 
 238     /***************************************************************************
 239      *                                                                         *
 240      * Properties                                                              *
 241      *                                                                         *
 242      **************************************************************************/
 243 
 244     // --- converter
 245     private ObjectProperty<StringConverter<T>> converter =
 246             new SimpleObjectProperty<StringConverter<T>>(this, "converter");
 247 
 248     /**
 249      * The {@link StringConverter} property.
 250      * @return the {@link StringConverter} property
 251      */
 252     public final ObjectProperty<StringConverter<T>> converterProperty() {
 253         return converter;
 254     }
 255 
 256     /**
 257      * Sets the {@link StringConverter} to be used in this cell.
 258      * @param value the {@link StringConverter} to be used in this cell
 259      */
 260     public final void setConverter(StringConverter<T> value) {
 261         converterProperty().set(value);
 262     }
 263 
 264     /**
 265      * Returns the {@link StringConverter} used in this cell.
 266      * @return the {@link StringConverter} used in this cell
 267      */
 268     public final StringConverter<T> getConverter() {
 269         return converterProperty().get();
 270     }
 271 
 272 
 273     // --- comboBox editable
 274     private BooleanProperty comboBoxEditable =
 275             new SimpleBooleanProperty(this, "comboBoxEditable");
 276 
 277     /**
 278      * A property representing whether the ComboBox, when shown to the user,
 279      * is editable or not.
 280      * @return the property representing whether the ComboBox, when shown to
 281      * the user, is editable or not
 282      */
 283     public final BooleanProperty comboBoxEditableProperty() {
 284         return comboBoxEditable;
 285     }
 286 
 287     /**
 288      * Configures the ComboBox to be editable (to allow user input outside of the
 289      * options provide in the dropdown list).
 290      * @param value the editable value for this ComboBox
 291      */
 292     public final void setComboBoxEditable(boolean value) {
 293         comboBoxEditableProperty().set(value);
 294     }
 295 
 296     /**
 297      * Returns true if the ComboBox is editable.
 298      * @return true if the ComboBox is editable
 299      */
 300     public final boolean isComboBoxEditable() {
 301         return comboBoxEditableProperty().get();
 302     }
 303 
 304 
 305 
 306     /***************************************************************************
 307      *                                                                         *
 308      * Public API                                                              *
 309      *                                                                         *
 310      **************************************************************************/
 311 
 312     /**
 313      * Returns the items to be displayed in the ChoiceBox when it is showing.
 314      * @return the items to be displayed in the ChoiceBox when it is showing
 315      */
 316     public ObservableList<T> getItems() {
 317         return items;
 318     }
 319 
 320     /** {@inheritDoc} */
 321     @Override public void startEdit() {
 322         if (! isEditable() || ! getListView().isEditable()) {
 323             return;
 324         }
 325 
 326         if (comboBox == null) {
 327             comboBox = createComboBox(this, items, converterProperty());
 328             comboBox.editableProperty().bind(comboBoxEditableProperty());
 329         }
 330 
 331         comboBox.getSelectionModel().select(getItem());
 332 
 333         super.startEdit();
 334 
 335         if (isEditing()) {
 336             setText(null);
 337             setGraphic(comboBox);
 338         }
 339     }
 340 
 341     /** {@inheritDoc} */
 342     @Override public void cancelEdit() {
 343         super.cancelEdit();
 344 
 345         setText(getConverter().toString(getItem()));
 346         setGraphic(null);
 347     }
 348 
 349     /** {@inheritDoc} */
 350     @Override public void updateItem(T item, boolean empty) {
 351         super.updateItem(item, empty);
 352         CellUtils.updateItem(this, getConverter(), null, null, comboBox);
 353     }
 354 }