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 }