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 }