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