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 javafx.beans.property.BooleanProperty;
  29 import javafx.beans.property.ObjectProperty;
  30 import javafx.beans.property.SimpleObjectProperty;
  31 import javafx.beans.value.ObservableValue;
  32 import javafx.geometry.Pos;
  33 import javafx.scene.control.CheckBox;
  34 import javafx.scene.control.ContentDisplay;
  35 import javafx.scene.control.ListCell;
  36 import javafx.scene.control.ListView;
  37 import javafx.util.Callback;
  38 import javafx.util.StringConverter;
  39 
  40 /**
  41  * A class containing a {@link ListCell} implementation that draws a
  42  * {@link CheckBox} node inside the cell, optionally with a label to indicate
  43  * what the checkbox represents.
  44  *
  45  * <p>The CheckBoxListCell is rendered with a CheckBox on the left-hand side of
  46  * the {@link ListView}, and the text related to the list item taking up all
  47  * remaining horizontal space.
  48  *
  49  * <p>To construct an instance of this class, it is necessary to provide a
  50  * {@link Callback} that, given an object of type T, will return a
  51  * {@code ObservableValue<Boolean>} that represents whether the given item is
  52  * selected or not. This ObservableValue will be bound bidirectionally (meaning
  53  * that the CheckBox in the cell will set/unset this property based on user
  54  * interactions, and the CheckBox will reflect the state of the
  55  * ObservableValue<Boolean>, if it changes externally).
  56  *
  57  * <p>Note that the CheckBoxListCell renders the CheckBox 'live', meaning that
  58  * the CheckBox is always interactive and can be directly toggled by the user.
  59  * This means that it is not necessary that the cell enter its
  60  * {@link #editingProperty() editing state} (usually by the user double-clicking
  61  * on the cell). A side-effect of this is that the usual editing callbacks
  62  * (such as {@link javafx.scene.control.ListView#onEditCommitProperty() on edit commit})
  63  * will <strong>not</strong> be called. If you want to be notified of changes,
  64  * it is recommended to directly observe the boolean properties that are
  65  * manipulated by the CheckBox.</p>
  66  *
  67  * @see CheckBox
  68  * @see ListCell
  69  * @param <T> The type of the elements contained within the ListView.
  70  * @since JavaFX 2.2
  71  */
  72 public class CheckBoxListCell<T> extends ListCell<T> {
  73 
  74     /***************************************************************************
  75      *                                                                         *
  76      * Static cell factories                                                   *
  77      *                                                                         *
  78      **************************************************************************/
  79 
  80     /**
  81      * Creates a cell factory for use in ListView controls. When used in a
  82      * ListView, the {@link CheckBoxListCell} is rendered with a CheckBox on the
  83      * left-hand side of the ListView, with the text related to the list item
  84      * taking up all remaining horizontal space.
  85      *
  86      * @param <T> The type of the elements contained within the ListView.
  87      * @param getSelectedProperty A {@link Callback} that, given an object of
  88      *      type T (which is a value taken out of the
  89      *      {@code ListView<T>.items} list),
  90      *      will return an {@code ObservableValue<Boolean>} that represents
  91      *      whether the given item is selected or not. This ObservableValue will
  92      *      be bound bidirectionally (meaning that the CheckBox in the cell will
  93      *      set/unset this property based on user interactions, and the CheckBox
  94      *      will reflect the state of the ObservableValue, if it changes
  95      *      externally).
  96      * @return A {@link Callback} that will return a ListCell that is able to
  97      *      work on the type of element contained within the ListView items list.
  98      */
  99     public static <T> Callback<ListView<T>, ListCell<T>> forListView(
 100             final Callback<T, ObservableValue<Boolean>> getSelectedProperty) {
 101         return forListView(getSelectedProperty, CellUtils.<T>defaultStringConverter());
 102     }
 103 
 104     /**
 105      * Creates a cell factory for use in ListView controls. When used in a
 106      * ListView, the {@link CheckBoxListCell} is rendered with a CheckBox on the
 107      * left-hand side of the ListView, with the text related to the list item
 108      * taking up all remaining horizontal space.
 109      *
 110      * @param <T> The type of the elements contained within the ListView.
 111      * @param getSelectedProperty A {@link Callback} that, given an object
 112      *      of type T (which is a value taken out of the
 113      *      {@code ListView<T>.items} list),
 114      *      will return an {@code ObservableValue<Boolean>} that represents
 115      *      whether the given item is selected or not. This ObservableValue will
 116      *      be bound bidirectionally (meaning that the CheckBox in the cell will
 117      *      set/unset this property based on user interactions, and the CheckBox
 118      *      will reflect the state of the ObservableValue, if it changes
 119      *      externally).
 120      * @param converter A StringConverter that, give an object of type T, will
 121      *      return a String that can be used to represent the object visually.
 122      * @return A {@link Callback} that will return a ListCell that is able to
 123      *      work on the type of element contained within the ListView.
 124      */
 125     public static <T> Callback<ListView<T>, ListCell<T>> forListView(
 126             final Callback<T, ObservableValue<Boolean>> getSelectedProperty,
 127             final StringConverter<T> converter) {
 128         return list -> new CheckBoxListCell<T>(getSelectedProperty, converter);
 129     }
 130 
 131     /***************************************************************************
 132      *                                                                         *
 133      * Fields                                                                  *
 134      *                                                                         *
 135      **************************************************************************/
 136 
 137     private final CheckBox checkBox;
 138 
 139     private ObservableValue<Boolean> booleanProperty;
 140 
 141 
 142 
 143     /***************************************************************************
 144      *                                                                         *
 145      * Constructors                                                            *
 146      *                                                                         *
 147      **************************************************************************/
 148 
 149     /**
 150      * Creates a default CheckBoxListCell.
 151      */
 152     public CheckBoxListCell() {
 153         this(null);
 154     }
 155 
 156     /**
 157      * Creates a default CheckBoxListCell.
 158      *
 159      * @param getSelectedProperty A {@link Callback} that will return an
 160      *      {@code ObservableValue<Boolean>} given an item from the ListView.
 161      */
 162     public CheckBoxListCell(
 163             final Callback<T, ObservableValue<Boolean>> getSelectedProperty) {
 164         this(getSelectedProperty, CellUtils.<T>defaultStringConverter());
 165     }
 166 
 167     /**
 168      * Creates a CheckBoxListCell with a custom string converter.
 169      *
 170      * @param getSelectedProperty A {@link Callback} that will return an
 171      *      {@code ObservableValue<Boolean>} given an item from the ListView.
 172      * @param converter A StringConverter that, given an object of type T, will
 173      *      return a String that can be used to represent the object visually.
 174      */
 175     public CheckBoxListCell(
 176             final Callback<T, ObservableValue<Boolean>> getSelectedProperty,
 177             final StringConverter<T> converter) {
 178         this.getStyleClass().add("check-box-list-cell");
 179         setSelectedStateCallback(getSelectedProperty);
 180         setConverter(converter);
 181 
 182         this.checkBox = new CheckBox();
 183 
 184         setAlignment(Pos.CENTER_LEFT);
 185         setContentDisplay(ContentDisplay.LEFT);
 186 
 187         // by default the graphic is null until the cell stops being empty
 188         setGraphic(null);
 189     }
 190 
 191 
 192     /***************************************************************************
 193      *                                                                         *
 194      * Properties                                                              *
 195      *                                                                         *
 196      **************************************************************************/
 197 
 198     // --- converter
 199     private ObjectProperty<StringConverter<T>> converter =
 200             new SimpleObjectProperty<StringConverter<T>>(this, "converter");
 201 
 202     /**
 203      * The {@link StringConverter} property.
 204      */
 205     public final ObjectProperty<StringConverter<T>> converterProperty() {
 206         return converter;
 207     }
 208 
 209     /**
 210      * Sets the {@link StringConverter} to be used in this cell.
 211      */
 212     public final void setConverter(StringConverter<T> value) {
 213         converterProperty().set(value);
 214     }
 215 
 216     /**
 217      * Returns the {@link StringConverter} used in this cell.
 218      */
 219     public final StringConverter<T> getConverter() {
 220         return converterProperty().get();
 221     }
 222 
 223 
 224     // --- selected state callback property
 225     private ObjectProperty<Callback<T, ObservableValue<Boolean>>>
 226             selectedStateCallback =
 227             new SimpleObjectProperty<Callback<T, ObservableValue<Boolean>>>(
 228             this, "selectedStateCallback");
 229 
 230     /**
 231      * Property representing the {@link Callback} that is bound to by the
 232      * CheckBox shown on screen.
 233      */
 234     public final ObjectProperty<Callback<T, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
 235         return selectedStateCallback;
 236     }
 237 
 238     /**
 239      * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
 240      */
 241     public final void setSelectedStateCallback(Callback<T, ObservableValue<Boolean>> value) {
 242         selectedStateCallbackProperty().set(value);
 243     }
 244 
 245     /**
 246      * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
 247      */
 248     public final Callback<T, ObservableValue<Boolean>> getSelectedStateCallback() {
 249         return selectedStateCallbackProperty().get();
 250     }
 251 
 252 
 253 
 254     /***************************************************************************
 255      *                                                                         *
 256      * Public API                                                              *
 257      *                                                                         *
 258      **************************************************************************/
 259 
 260     /** {@inheritDoc} */
 261     @Override public void updateItem(T item, boolean empty) {
 262         super.updateItem(item, empty);
 263 
 264         if (! empty) {
 265             StringConverter<T> c = getConverter();
 266             Callback<T, ObservableValue<Boolean>> callback = getSelectedStateCallback();
 267             if (callback == null) {
 268                 throw new NullPointerException(
 269                         "The CheckBoxListCell selectedStateCallbackProperty can not be null");
 270             }
 271 
 272             setGraphic(checkBox);
 273             setText(c != null ? c.toString(item) : (item == null ? "" : item.toString()));
 274 
 275             if (booleanProperty != null) {
 276                 checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
 277             }
 278             booleanProperty = callback.call(item);
 279             if (booleanProperty != null) {
 280                 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
 281             }
 282         } else {
 283             setGraphic(null);
 284             setText(null);
 285         }
 286     }
 287 }