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.binding.Bindings;
  29 import javafx.beans.property.BooleanProperty;
  30 import javafx.beans.property.ObjectProperty;
  31 import javafx.beans.property.SimpleObjectProperty;
  32 import javafx.beans.value.ObservableValue;
  33 import javafx.geometry.Pos;
  34 import javafx.scene.control.CheckBox;
  35 import javafx.scene.control.TableCell;
  36 import javafx.scene.control.TableColumn;
  37 import javafx.util.Callback;
  38 import javafx.util.StringConverter;
  39 
  40 /**
  41  * A class containing a {@link TableCell} 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>By default, the CheckBoxTableCell is rendered with a CheckBox centred in
  46  * the TableColumn. If a label is required, it is necessary to provide a
  47  * non-null StringConverter instance to the
  48  * {@link #CheckBoxTableCell(Callback, StringConverter)} constructor.
  49  *
  50  * <p>To construct an instance of this class, it is necessary to provide a
  51  * {@link Callback} that, given an object of type T, will return an
  52  * {@code ObservableProperty<Boolean>} that represents whether the given item is
  53  * selected or not. This ObservableValue will be bound bidirectionally (meaning
  54  * that the CheckBox in the cell will set/unset this property based on user
  55  * interactions, and the CheckBox will reflect the state of the ObservableValue,
  56  * if it changes externally).
  57  *
  58  * <p>Note that the CheckBoxTableCell renders the CheckBox 'live', meaning that
  59  * the CheckBox is always interactive and can be directly toggled by the user.
  60  * This means that it is not necessary that the cell enter its
  61  * {@link #editingProperty() editing state} (usually by the user double-clicking
  62  * on the cell). A side-effect of this is that the usual editing callbacks
  63  * (such as {@link javafx.scene.control.TableColumn#onEditCommitProperty() on edit commit})
  64  * will <strong>not</strong> be called. If you want to be notified of changes,
  65  * it is recommended to directly observe the boolean properties that are
  66  * manipulated by the CheckBox.</p>
  67  *
  68  * @param <T> The type of the elements contained within the TableColumn.
  69  * @since JavaFX 2.2
  70  */
  71 public class CheckBoxTableCell<S,T> extends TableCell<S,T> {
  72 
  73     /***************************************************************************
  74      *                                                                         *
  75      * Static cell factories                                                   *
  76      *                                                                         *
  77      **************************************************************************/
  78 
  79     /**
  80      * Creates a cell factory for use in a {@link TableColumn} cell factory.
  81      * This method requires that the TableColumn be of type {@link Boolean}.
  82      *
  83      * <p>When used in a TableColumn, the CheckBoxCell is rendered with a
  84      * CheckBox centered in the column.
  85      *
  86      * <p>The {@code ObservableValue<Boolean>} contained within each cell in the
  87      * column will be bound bidirectionally. This means that the  CheckBox in
  88      * the cell will set/unset this property based on user interactions, and the
  89      * CheckBox will reflect the state of the {@code ObservableValue<Boolean>},
  90      * if it changes externally).
  91      *
  92      * @param <S> The type of the TableView generic type
  93      * @param column The TableColumn of type Boolean
  94      * @return A {@link Callback} that will return a {@link TableCell} that is
  95      *      able to work on the type of element contained within the TableColumn.
  96      */
  97     public static <S> Callback<TableColumn<S,Boolean>, TableCell<S,Boolean>> forTableColumn(
  98             final TableColumn<S, Boolean> column) {
  99         return forTableColumn(null, null);
 100     }
 101 
 102     /**
 103      * Creates a cell factory for use in a {@link TableColumn} cell factory.
 104      * This method requires that the TableColumn be of type
 105      * {@code ObservableValue<Boolean>}.
 106      *
 107      * <p>When used in a TableColumn, the CheckBoxCell is rendered with a
 108      * CheckBox centered in the column.
 109      *
 110      * @param <S> The type of the TableView generic type
 111      * @param <T> The type of the elements contained within the {@link TableColumn}
 112      *      instance.
 113      * @param getSelectedProperty A Callback that, given an object of
 114      *      type {@code TableColumn<S,T>}, will return an
 115      *      {@code ObservableValue<Boolean>}
 116      *      that represents whether the given item is selected or not. This
 117      *      {@code ObservableValue<Boolean>} will be bound bidirectionally
 118      *      (meaning that the CheckBox in the cell will set/unset this property
 119      *      based on user interactions, and the CheckBox will reflect the state of
 120      *      the {@code ObservableValue<Boolean>}, if it changes externally).
 121      * @return A {@link Callback} that will return a {@link TableCell} that is
 122      *      able to work on the type of element contained within the TableColumn.
 123      */
 124     public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
 125             final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
 126         return forTableColumn(getSelectedProperty, null);
 127     }
 128 
 129     /**
 130      * Creates a cell factory for use in a {@link TableColumn} cell factory.
 131      * This method requires that the TableColumn be of type
 132      * {@code ObservableValue<Boolean>}.
 133      *
 134      * <p>When used in a TableColumn, the CheckBoxCell is rendered with a
 135      * CheckBox centered in the column.
 136      *
 137      * @param <S> The type of the TableView generic type
 138      * @param <T> The type of the elements contained within the {@link TableColumn}
 139      *      instance.
 140      * @param getSelectedProperty A Callback that, given an object of
 141      *      type {@code TableColumn<S,T>}, will return an
 142      *      {@code ObservableValue<Boolean>}
 143      *      that represents whether the given item is selected or not. This
 144      *      {@code ObservableValue<Boolean>} will be bound bidirectionally
 145      *      (meaning that the CheckBox in the cell will set/unset this property
 146      *      based on user interactions, and the CheckBox will reflect the state of
 147      *      the {@code ObservableValue<Boolean>}, if it changes externally).
 148      * @param showLabel In some cases, it may be desirable to show a label in
 149      *      the TableCell beside the {@link CheckBox}. By default a label is not
 150      *      shown, but by setting this to true the item in the cell will also
 151      *      have toString() called on it. If this is not the desired behavior,
 152      *      consider using
 153      *      {@link #forTableColumn(javafx.util.Callback, javafx.util.StringConverter) },
 154      *      which allows for you to provide a callback that specifies the label for a
 155      *      given row item.
 156      * @return A {@link Callback} that will return a {@link TableCell} that is
 157      *      able to work on the type of element contained within the TableColumn.
 158      */
 159     public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
 160             final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty,
 161             final boolean showLabel) {
 162         StringConverter<T> converter = ! showLabel ?
 163                 null : CellUtils.<T>defaultStringConverter();
 164         return forTableColumn(getSelectedProperty, converter);
 165     }
 166 
 167     /**
 168      * Creates a cell factory for use in a {@link TableColumn} cell factory.
 169      * This method requires that the TableColumn be of type
 170      * {@code ObservableValue<Boolean>}.
 171      *
 172      * <p>When used in a TableColumn, the CheckBoxCell is rendered with a
 173      * CheckBox centered in the column.
 174      *
 175      * @param <S> The type of the TableView generic type
 176      * @param <T> The type of the elements contained within the {@link TableColumn}
 177      *      instance.
 178      * @param getSelectedProperty A Callback that, given an object of type
 179      *      {@code TableColumn<S,T>}, will return an
 180      *      {@code ObservableValue<Boolean>} that represents whether the given
 181      *      item is selected or not. This {@code ObservableValue<Boolean>} will
 182      *      be bound bidirectionally (meaning that the CheckBox in the cell will
 183      *      set/unset this property based on user interactions, and the CheckBox
 184      *      will reflect the state of the {@code ObservableValue<Boolean>}, if
 185      *      it changes externally).
 186      * @param converter A StringConverter that, give an object of type T, will return a
 187      *      String that can be used to represent the object visually. The default
 188      *      implementation in {@link #forTableColumn(Callback, boolean)} (when
 189      *      showLabel is true) is to simply call .toString() on all non-null
 190      *      items (and to just return an empty string in cases where the given
 191      *      item is null).
 192      * @return A {@link Callback} that will return a {@link TableCell} that is
 193      *      able to work on the type of element contained within the TableColumn.
 194      */
 195     public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
 196             final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty,
 197             final StringConverter<T> converter) {
 198         return list -> new CheckBoxTableCell<S,T>(getSelectedProperty, converter);
 199     }
 200 
 201 
 202 
 203     /***************************************************************************
 204      *                                                                         *
 205      * Fields                                                                  *
 206      *                                                                         *
 207      **************************************************************************/
 208     private final CheckBox checkBox;
 209 
 210     private boolean showLabel;
 211 
 212     private ObservableValue<Boolean> booleanProperty;
 213 
 214 
 215 
 216     /***************************************************************************
 217      *                                                                         *
 218      * Constructors                                                            *
 219      *                                                                         *
 220      **************************************************************************/
 221 
 222     /**
 223      * Creates a default CheckBoxTableCell.
 224      */
 225     public CheckBoxTableCell() {
 226         this(null, null);
 227     }
 228 
 229     /**
 230      * Creates a default CheckBoxTableCell with a custom {@link Callback} to
 231      * retrieve an ObservableValue for a given cell index.
 232      *
 233      * @param getSelectedProperty A {@link Callback} that will return an {@link
 234      *      ObservableValue} given an index from the TableColumn.
 235      */
 236     public CheckBoxTableCell(
 237             final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) {
 238         this(getSelectedProperty, null);
 239     }
 240 
 241     /**
 242      * Creates a CheckBoxTableCell with a custom string converter.
 243      *
 244      * @param getSelectedProperty A {@link Callback} that will return a {@link
 245      *      ObservableValue} given an index from the TableColumn.
 246      * @param converter A StringConverter that, given an object of type T, will return a
 247      *      String that can be used to represent the object visually.
 248      */
 249     public CheckBoxTableCell(
 250             final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty,
 251             final StringConverter<T> converter) {
 252         // we let getSelectedProperty be null here, as we can always defer to the
 253         // TableColumn
 254         this.getStyleClass().add("check-box-table-cell");
 255 
 256         this.checkBox = new CheckBox();
 257 
 258         // by default the graphic is null until the cell stops being empty
 259         setGraphic(null);
 260 
 261         setSelectedStateCallback(getSelectedProperty);
 262         setConverter(converter);
 263 
 264 //        // alignment is styleable through css. Calling setAlignment
 265 //        // makes it look to css like the user set the value and css will not
 266 //        // override. Initializing alignment by calling set on the
 267 //        // CssMetaData ensures that css will be able to override the value.
 268 //        final CssMetaData prop = CssMetaData.getCssMetaData(alignmentProperty());
 269 //        prop.set(this, Pos.CENTER);
 270 
 271 
 272     }
 273 
 274 
 275     /***************************************************************************
 276      *                                                                         *
 277      * Properties                                                              *
 278      *                                                                         *
 279      **************************************************************************/
 280 
 281     // --- converter
 282     private ObjectProperty<StringConverter<T>> converter =
 283             new SimpleObjectProperty<StringConverter<T>>(this, "converter") {
 284         protected void invalidated() {
 285             updateShowLabel();
 286         }
 287     };
 288 
 289     /**
 290      * The {@link StringConverter} property.
 291      * @return the {@link StringConverter} property
 292      */
 293     public final ObjectProperty<StringConverter<T>> converterProperty() {
 294         return converter;
 295     }
 296 
 297     /**
 298      * Sets the {@link StringConverter} to be used in this cell.
 299      * @param value the {@link StringConverter} to be used in this cell
 300      */
 301     public final void setConverter(StringConverter<T> value) {
 302         converterProperty().set(value);
 303     }
 304 
 305     /**
 306      * Returns the {@link StringConverter} used in this cell.
 307      * @return the {@link StringConverter} used in this cell
 308      */
 309     public final StringConverter<T> getConverter() {
 310         return converterProperty().get();
 311     }
 312 
 313 
 314 
 315     // --- selected state callback property
 316     private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>>
 317             selectedStateCallback =
 318             new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>(
 319             this, "selectedStateCallback");
 320 
 321     /**
 322      * Property representing the {@link Callback} that is bound to by the
 323      * CheckBox shown on screen.
 324      * @return the property representing the {@link Callback} that is bound to
 325      * by the CheckBox shown on screen
 326      */
 327     public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
 328         return selectedStateCallback;
 329     }
 330 
 331     /**
 332      * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
 333      * @param value the {@link Callback} that is bound to by the CheckBox shown
 334      * on screen
 335      */
 336     public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) {
 337         selectedStateCallbackProperty().set(value);
 338     }
 339 
 340     /**
 341      * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
 342      * @return the {@link Callback} that is bound to by the CheckBox shown on screen
 343      */
 344     public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() {
 345         return selectedStateCallbackProperty().get();
 346     }
 347 
 348 
 349 
 350     /***************************************************************************
 351      *                                                                         *
 352      * Public API                                                              *
 353      *                                                                         *
 354      **************************************************************************/
 355 
 356     /** {@inheritDoc} */
 357     @SuppressWarnings("unchecked")
 358     @Override public void updateItem(T item, boolean empty) {
 359         super.updateItem(item, empty);
 360 
 361         if (empty) {
 362             setText(null);
 363             setGraphic(null);
 364         } else {
 365             StringConverter<T> c = getConverter();
 366 
 367             if (showLabel) {
 368                 setText(c.toString(item));
 369             }
 370             setGraphic(checkBox);
 371 
 372             if (booleanProperty instanceof BooleanProperty) {
 373                 checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
 374             }
 375             ObservableValue<?> obsValue = getSelectedProperty();
 376             if (obsValue instanceof BooleanProperty) {
 377                 booleanProperty = (ObservableValue<Boolean>) obsValue;
 378                 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
 379             }
 380 
 381             checkBox.disableProperty().bind(Bindings.not(
 382                     getTableView().editableProperty().and(
 383                     getTableColumn().editableProperty()).and(
 384                     editableProperty())
 385                 ));
 386         }
 387     }
 388 
 389 
 390 
 391     /***************************************************************************
 392      *                                                                         *
 393      * Private implementation                                                  *
 394      *                                                                         *
 395      **************************************************************************/
 396 
 397     private void updateShowLabel() {
 398         this.showLabel = converter != null;
 399         this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER);
 400     }
 401 
 402     private ObservableValue<?> getSelectedProperty() {
 403         return getSelectedStateCallback() != null ?
 404                 getSelectedStateCallback().call(getIndex()) :
 405                 getTableColumn().getCellObservableValue(getIndex());
 406     }
 407 }