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