001/* 002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control.cell; 027 028import javafx.css.CssMetaData; 029import javafx.beans.binding.Bindings; 030import javafx.beans.property.BooleanProperty; 031import javafx.beans.property.ObjectProperty; 032import javafx.beans.property.SimpleObjectProperty; 033import javafx.beans.value.ObservableValue; 034import javafx.geometry.Pos; 035import javafx.scene.control.CheckBox; 036import javafx.scene.control.TreeTableCell; 037import javafx.scene.control.TreeTableColumn; 038import javafx.util.Callback; 039import javafx.util.StringConverter; 040 041/** 042 * A class containing a {@link TreeTableCell} implementation that draws a 043 * {@link CheckBox} node inside the cell, optionally with a label to indicate 044 * what the checkbox represents. 045 * 046 * <p>By default, the CheckBoxTreeTableCell is rendered with a CheckBox centred in 047 * the TreeTableColumn. If a label is required, it is necessary to provide a 048 * non-null StringConverter instance to the 049 * {@link #CheckBoxTreeTableCell(Callback, StringConverter)} constructor. 050 * 051 * <p>To construct an instance of this class, it is necessary to provide a 052 * {@link Callback} that, given an object of type T, will return an 053 * {@code ObservableProperty<Boolean>} that represents whether the given item is 054 * selected or not. This ObservableValue will be bound bidirectionally (meaning 055 * that the CheckBox in the cell will set/unset this property based on user 056 * interactions, and the CheckBox will reflect the state of the ObservableValue, 057 * if it changes externally). 058 * 059 * @param <T> The type of the elements contained within the TreeTableColumn. 060 * @since 8.0 061 */ 062public class CheckBoxTreeTableCell<S,T> extends TreeTableCell<S,T> { 063 064 /*************************************************************************** 065 * * 066 * Static cell factories * 067 * * 068 **************************************************************************/ 069 070 /** 071 * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 072 * This method requires that the TreeTableColumn be of type {@link Boolean}. 073 * 074 * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 075 * CheckBox centered in the column. 076 * 077 * <p>The {@code ObservableValue<Boolean>} contained within each cell in the 078 * column will be bound bidirectionally. This means that the CheckBox in 079 * the cell will set/unset this property based on user interactions, and the 080 * CheckBox will reflect the state of the {@code ObservableValue<Boolean>}, 081 * if it changes externally).</li> 082 * 083 * @return A {@link Callback} that will return a {@link TreeTableCell} that is 084 * able to work on the type of element contained within the TreeTableColumn. 085 */ 086 public static <S> Callback<TreeTableColumn<S,Boolean>, TreeTableCell<S,Boolean>> forTreeTableColumn( 087 final TreeTableColumn<S, Boolean> column) { 088 return forTreeTableColumn(null, null); 089 } 090 091 /** 092 * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 093 * This method requires that the TreeTableColumn be of type 094 * {@code ObservableValue<Boolean>}. 095 * 096 * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 097 * CheckBox centered in the column. 098 * 099 * @param <T> The type of the elements contained within the {@link TreeTableColumn} 100 * instance. 101 * @param getSelectedProperty A Callback that, given an object of 102 * type {@code TreeTableColumn<S,T>}, will return an 103 * {@code ObservableValue<Boolean>} 104 * that represents whether the given item is selected or not. This 105 * {@code ObservableValue<Boolean>} will be bound bidirectionally 106 * (meaning that the CheckBox in the cell will set/unset this property 107 * based on user interactions, and the CheckBox will reflect the state of 108 * the {@code ObservableValue<Boolean>}, if it changes externally). 109 * @return A {@link Callback} that will return a {@link TreeTableCell} that is 110 * able to work on the type of element contained within the TreeTableColumn. 111 */ 112 public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn( 113 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) { 114 return forTreeTableColumn(getSelectedProperty, null); 115 } 116 117 /** 118 * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 119 * This method requires that the TreeTableColumn be of type 120 * {@code ObservableValue<Boolean>}. 121 * 122 * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 123 * CheckBox centered in the column. 124 * 125 * @param <T> The type of the elements contained within the {@link TreeTableColumn} 126 * instance. 127 * @param getSelectedProperty A Callback that, given an object of 128 * type {@code TreeTableColumn<S,T>}, will return an 129 * {@code ObservableValue<Boolean>} 130 * that represents whether the given item is selected or not. This 131 * {@code ObservableValue<Boolean>} will be bound bidirectionally 132 * (meaning that the CheckBox in the cell will set/unset this property 133 * based on user interactions, and the CheckBox will reflect the state of 134 * the {@code ObservableValue<Boolean>}, if it changes externally). 135 * @param showLabel In some cases, it may be desirable to show a label in 136 * the TreeTableCell beside the {@link CheckBox}. By default a label is not 137 * shown, but by setting this to true the item in the cell will also 138 * have toString() called on it. If this is not the desired behavior, 139 * consider using 140 * {@link #forTreeTableColumn(javafx.util.Callback, javafx.util.StringConverter) }, 141 * which allows for you to provide a callback that specifies the label for a 142 * given row item. 143 * @return A {@link Callback} that will return a {@link TreeTableCell} that is 144 * able to work on the type of element contained within the TreeTableColumn. 145 */ 146 public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn( 147 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 148 final boolean showLabel) { 149 StringConverter<T> converter = ! showLabel ? 150 null : CellUtils.<T>defaultStringConverter(); 151 return forTreeTableColumn(getSelectedProperty, converter); 152 } 153 154 /** 155 * Creates a cell factory for use in a {@link TreeTableColumn} cell factory. 156 * This method requires that the TreeTableColumn be of type 157 * {@code ObservableValue<Boolean>}. 158 * 159 * <p>When used in a TreeTableColumn, the CheckBoxCell is rendered with a 160 * CheckBox centered in the column. 161 * 162 * @param <T> The type of the elements contained within the {@link TreeTableColumn} 163 * instance. 164 * @param getSelectedProperty A Callback that, given an object of type 165 * {@code TreeTableColumn<S,T>}, will return an 166 * {@code ObservableValue<Boolean>} that represents whether the given 167 * item is selected or not. This {@code ObservableValue<Boolean>} will 168 * be bound bidirectionally (meaning that the CheckBox in the cell will 169 * set/unset this property based on user interactions, and the CheckBox 170 * will reflect the state of the {@code ObservableValue<Boolean>}, if 171 * it changes externally). 172 * @param converter A StringConverter that, give an object of type T, will return a 173 * String that can be used to represent the object visually. The default 174 * implementation in {@link #forTreeTableColumn(Callback, boolean)} (when 175 * showLabel is true) is to simply call .toString() on all non-null 176 * items (and to just return an empty string in cases where the given 177 * item is null). 178 * @return A {@link Callback} that will return a {@link TreeTableCell} that is 179 * able to work on the type of element contained within the TreeTableColumn. 180 */ 181 public static <S,T> Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>> forTreeTableColumn( 182 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 183 final StringConverter<T> converter) { 184 return new Callback<TreeTableColumn<S,T>, TreeTableCell<S,T>>() { 185 @Override public TreeTableCell<S,T> call(TreeTableColumn<S,T> list) { 186 return new CheckBoxTreeTableCell<S,T>(getSelectedProperty, converter); 187 } 188 }; 189 } 190 191 192 193 /*************************************************************************** 194 * * 195 * Fields * 196 * * 197 **************************************************************************/ 198 private final CheckBox checkBox; 199 200 private boolean showLabel; 201 202 private ObservableValue<Boolean> booleanProperty; 203 204 205 206 /*************************************************************************** 207 * * 208 * Constructors * 209 * * 210 **************************************************************************/ 211 212 /** 213 * Creates a default CheckBoxTreeTableCell. 214 */ 215 public CheckBoxTreeTableCell() { 216 this(null, null); 217 } 218 219 /** 220 * Creates a default CheckBoxTreeTableCell with a custom {@link Callback} to 221 * retrieve an ObservableValue for a given cell index. 222 * 223 * @param getSelectedProperty A {@link Callback} that will return an {@link 224 * ObservableValue} given an index from the TreeTableColumn. 225 */ 226 public CheckBoxTreeTableCell( 227 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty) { 228 this(getSelectedProperty, null); 229 } 230 231 /** 232 * Creates a CheckBoxTreeTableCell with a custom string converter. 233 * 234 * @param getSelectedProperty A {@link Callback} that will return a {@link 235 * ObservableValue} given an index from the TreeTableColumn. 236 * @param converter A StringConverter that, given an object of type T, will return a 237 * String that can be used to represent the object visually. 238 */ 239 public CheckBoxTreeTableCell( 240 final Callback<Integer, ObservableValue<Boolean>> getSelectedProperty, 241 final StringConverter<T> converter) { 242 // we let getSelectedProperty be null here, as we can always defer to the 243 // TreeTableColumn 244 this.getStyleClass().add("check-box-tree-table-cell"); 245 246 this.checkBox = new CheckBox(); 247 setGraphic(checkBox); 248 249 setSelectedStateCallback(getSelectedProperty); 250 setConverter(converter); 251 252// // alignment is styleable through css. Calling setAlignment 253// // makes it look to css like the user set the value and css will not 254// // override. Initializing alignment by calling set on the 255// // CssMetaData ensures that css will be able to override the value. 256// final CssMetaData prop = CssMetaData.getCssMetaData(alignmentProperty()); 257// prop.set(this, Pos.CENTER); 258 } 259 260 261 /*************************************************************************** 262 * * 263 * Properties * 264 * * 265 **************************************************************************/ 266 267 // --- converter 268 private ObjectProperty<StringConverter<T>> converter = 269 new SimpleObjectProperty<StringConverter<T>>(this, "converter") { 270 protected void invalidated() { 271 updateShowLabel(); 272 } 273 }; 274 275 /** 276 * The {@link StringConverter} property. 277 */ 278 public final ObjectProperty<StringConverter<T>> converterProperty() { 279 return converter; 280 } 281 282 /** 283 * Sets the {@link StringConverter} to be used in this cell. 284 */ 285 public final void setConverter(StringConverter<T> value) { 286 converterProperty().set(value); 287 } 288 289 /** 290 * Returns the {@link StringConverter} used in this cell. 291 */ 292 public final StringConverter<T> getConverter() { 293 return converterProperty().get(); 294 } 295 296 297 298 // --- selected state callback property 299 private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> 300 selectedStateCallback = 301 new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>( 302 this, "selectedStateCallback"); 303 304 /** 305 * Property representing the {@link Callback} that is bound to by the 306 * CheckBox shown on screen. 307 */ 308 public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 309 return selectedStateCallback; 310 } 311 312 /** 313 * Sets the {@link Callback} that is bound to by the CheckBox shown on screen. 314 */ 315 public final void setSelectedStateCallback(Callback<Integer, ObservableValue<Boolean>> value) { 316 selectedStateCallbackProperty().set(value); 317 } 318 319 /** 320 * Returns the {@link Callback} that is bound to by the CheckBox shown on screen. 321 */ 322 public final Callback<Integer, ObservableValue<Boolean>> getSelectedStateCallback() { 323 return selectedStateCallbackProperty().get(); 324 } 325 326 327 328 /*************************************************************************** 329 * * 330 * Public API * 331 * * 332 **************************************************************************/ 333 334 /** {@inheritDoc} */ 335 @Override public void updateItem(T item, boolean empty) { 336 super.updateItem(item, empty); 337 338 if (empty) { 339 setText(null); 340 setGraphic(null); 341 } else { 342 StringConverter c = getConverter(); 343 344 if (showLabel) { 345 setText(c.toString(item)); 346 } 347 setGraphic(checkBox); 348 349 if (booleanProperty instanceof BooleanProperty) { 350 checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); 351 } 352 ObservableValue obsValue = getSelectedProperty(); 353 if (obsValue instanceof BooleanProperty) { 354 booleanProperty = obsValue; 355 checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); 356 } 357 358 checkBox.disableProperty().bind(Bindings.not( 359 getTreeTableView().editableProperty().and( 360 getTableColumn().editableProperty()).and( 361 editableProperty()) 362 )); 363 } 364 } 365 366 367 368 /*************************************************************************** 369 * * 370 * Private implementation * 371 * * 372 **************************************************************************/ 373 374 private void updateShowLabel() { 375 this.showLabel = converter != null; 376 this.checkBox.setAlignment(showLabel ? Pos.CENTER_LEFT : Pos.CENTER); 377 } 378 379 private ObservableValue getSelectedProperty() { 380 return getSelectedStateCallback() != null ? 381 getSelectedStateCallback().call(getIndex()) : 382 getTableColumn().getCellObservableValue(getIndex()); 383 } 384}