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; 27 28 import javafx.scene.control.cell.CheckBoxTreeCell; 29 import javafx.beans.property.BooleanProperty; 30 import javafx.beans.property.SimpleBooleanProperty; 31 import javafx.beans.value.ChangeListener; 32 import javafx.event.Event; 33 import javafx.event.EventType; 34 import javafx.scene.Node; 35 36 /** 37 * TreeItem subclass that adds support for being in selected, unselected, and 38 * indeterminate states. This is useful when used in conjunction with a TreeView 39 * which has a {@link CheckBoxTreeCell} installed. 40 * 41 * <p>A CheckBoxTreeItem can be {@link #independentProperty() independent} or 42 * dependent. By default, CheckBoxTreeItem instances are dependent, which means 43 * that any changes to the selection state of a TreeItem will have an impact on 44 * parent and children CheckBoxTreeItem instances. If a CheckBoxTreeItem is 45 * set to be independent, this means that any changes to that CheckBoxTreeItem 46 * will not directly impact the state of parent and children CheckBoxTreeItem 47 * instances. 48 * 49 * <p>The {@link #indeterminateProperty() indeterminate} property is used to 50 * represent the same concept as that in {@link CheckBox#indeterminateProperty()}, 51 * namely, that the CheckBox is neither selected or unselected. This is commonly 52 * used inside a TreeView when some, but not all, of a branches children are 53 * selected. 54 * 55 * <p>A simple example of using the CheckBoxTreeItem class, in conjunction with 56 * {@link CheckBoxTreeCell} is shown below: 57 * 58 * <pre><code> 59 * // create the tree model 60 * CheckBoxTreeItem<String> jonathanGiles = new CheckBoxTreeItem<String>("Jonathan"); 61 * CheckBoxTreeItem<String> juliaGiles = new CheckBoxTreeItem<String>("Julia"); 62 * CheckBoxTreeItem<String> mattGiles = new CheckBoxTreeItem<String>("Matt"); 63 * CheckBoxTreeItem<String> sueGiles = new CheckBoxTreeItem<String>("Sue"); 64 * CheckBoxTreeItem<String> ianGiles = new CheckBoxTreeItem<String>("Ian"); 65 * 66 * CheckBoxTreeItem<String> gilesFamily = new CheckBoxTreeItem<String>("Giles Family"); 67 * gilesFamily.setExpanded(true); 68 * gilesFamily.getChildren().addAll(jonathanGiles, juliaGiles, mattGiles, sueGiles, ianGiles); 69 * 70 * // create the treeView 71 * final TreeView<String> treeView = new TreeView<String>(); 72 * treeView.setRoot(gilesFamily); 73 * 74 * // set the cell factory 75 * treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());</code></pre> 76 * 77 * @param <T> The type of the value contained within the TreeItem 78 * @see CheckBoxTreeCell 79 * @see TreeItem 80 * @see CheckBox 81 * @since JavaFX 2.2 82 */ 83 // TODO the TreeModificationEvent doesn't actually bubble in the same way as 84 // TreeItem - it just looks that way as the 'bubbling' is done via changing the 85 // state of all parent items. 86 public class CheckBoxTreeItem<T> extends TreeItem<T> { 87 88 /** 89 * An EventType used when the CheckBoxTreeItem selection / indeterminate 90 * state changes. To use this, it is recommended that you use code along the 91 * lines of the following: 92 * 93 *<pre> 94 * {@code 95 * child1.addEventHandler(CheckBoxTreeItem.<String>checkBoxSelectionChangedEvent(), new EventHandler<TreeModificationEvent<String>>() { 96 * public void handle(TreeModificationEvent<String> event) { 97 * ... 98 * } 99 * });} 100 * </pre> 101 * 102 * @param <T> The type of the value contained within the TreeItem. 103 * @return the EventType used when the CheckBoxTreeItem selection / indeterminate 104 * state changes 105 */ 106 @SuppressWarnings("unchecked") 107 public static <T> EventType<TreeModificationEvent<T>> checkBoxSelectionChangedEvent() { 108 return (EventType<TreeModificationEvent<T>>) CHECK_BOX_SELECTION_CHANGED_EVENT; 109 } 110 private static final EventType<? extends Event> CHECK_BOX_SELECTION_CHANGED_EVENT 111 = new EventType<Event>(TreeModificationEvent.ANY, "checkBoxSelectionChangedEvent"); 112 113 /*************************************************************************** 114 * * 115 * Constructors * 116 * * 117 **************************************************************************/ 118 119 /** 120 * Creates an empty CheckBoxTreeItem. 121 */ 122 public CheckBoxTreeItem() { 123 this(null); 124 } 125 126 /** 127 * Creates a CheckBoxTreeItem with the value property set to the provided 128 * object. 129 * 130 * @param value The object to be stored as the value of this TreeItem. 131 */ 132 public CheckBoxTreeItem(T value) { 133 this(value, null, false); 134 } 135 136 /** 137 * Creates a CheckBoxTreeItem with the value property set to the provided 138 * object, and the graphic set to the provided Node. 139 * 140 * @param value The object to be stored as the value of this CheckBoxTreeItem. 141 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 142 */ 143 public CheckBoxTreeItem(T value, Node graphic) { 144 this(value, graphic, false); 145 } 146 147 /** 148 * Creates a CheckBoxTreeItem with the value property set to the provided 149 * object, the graphic set to the provided Node, and the initial state 150 * of the {@link #selectedProperty()} set to the provided boolean value. 151 * 152 * @param value The object to be stored as the value of this CheckBoxTreeItem. 153 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 154 * @param selected The initial value of the 155 * {@link #selectedProperty() selected} property. 156 */ 157 public CheckBoxTreeItem(T value, Node graphic, boolean selected) { 158 this(value, graphic, selected, false); 159 } 160 161 /** 162 * Creates a CheckBoxTreeItem with the value property set to the provided 163 * object, the graphic set to the provided Node, the initial state 164 * of the {@link #selectedProperty()} set to the provided boolean value, and 165 * the initial state of the {@link #independentProperty() independent} 166 * property to the provided boolean value. 167 * 168 * @param value The object to be stored as the value of this CheckBoxTreeItem. 169 * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. 170 * @param selected The initial value of the 171 * {@link #selectedProperty() selected} property. 172 * @param independent The initial value of the 173 * {@link #independentProperty() independent} property 174 */ 175 public CheckBoxTreeItem(T value, Node graphic, boolean selected, boolean independent) { 176 super(value, graphic); 177 setSelected(selected); 178 setIndependent(independent); 179 180 selectedProperty().addListener(stateChangeListener); 181 indeterminateProperty().addListener(stateChangeListener); 182 } 183 184 185 186 /*************************************************************************** 187 * * 188 * Callbacks * 189 * * 190 **************************************************************************/ 191 private final ChangeListener<Boolean> stateChangeListener = (ov, oldVal, newVal) -> { 192 updateState(); 193 }; 194 195 196 /*************************************************************************** 197 * * 198 * Properties * 199 * * 200 **************************************************************************/ 201 202 // --- Selected 203 private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false) { 204 @Override protected void invalidated() { 205 super.invalidated(); 206 fireEvent(CheckBoxTreeItem.this, true); 207 } 208 }; 209 210 /** 211 * Sets the selected state of this CheckBoxTreeItem. 212 * @param value the selected state of this CheckBoxTreeItem 213 */ 214 public final void setSelected(boolean value) { selectedProperty().setValue(value); } 215 216 /** 217 * Returns the selected state of this CheckBoxTreeItem. 218 * @return true if CheckBoxTreeItem is selected 219 */ 220 public final boolean isSelected() { return selected.getValue(); } 221 222 /** 223 * A {@link BooleanProperty} used to represent the selected state of this CheckBoxTreeItem. 224 * @return the selected state property of this CheckBoxTreeItem 225 */ 226 public final BooleanProperty selectedProperty() { return selected; } 227 228 229 // --- Indeterminate 230 private final BooleanProperty indeterminate = new SimpleBooleanProperty(this, "indeterminate", false) { 231 @Override protected void invalidated() { 232 super.invalidated(); 233 fireEvent(CheckBoxTreeItem.this, false); 234 } 235 }; 236 237 /** 238 * Sets the indeterminate state of this CheckBoxTreeItem. 239 * @param value the indeterminate state of this CheckBoxTreeItem 240 */ 241 public final void setIndeterminate(boolean value) { indeterminateProperty().setValue(value); } 242 243 /** 244 * Returns the indeterminate state of this CheckBoxTreeItem. 245 * @return true if CheckBoxTreeItem is indeterminate state 246 */ 247 public final boolean isIndeterminate() { return indeterminate.getValue(); } 248 249 /** 250 * A {@link BooleanProperty} used to represent the indeterminate state of this CheckBoxTreeItem. 251 * @return the indeterminate state property of this CheckBoxTreeItem 252 */ 253 public final BooleanProperty indeterminateProperty() { return indeterminate; } 254 255 256 // --- Independent 257 /** 258 * A {@link BooleanProperty} used to represent the independent state of this CheckBoxTreeItem. 259 * The independent state is used to represent whether changes to a single 260 * CheckBoxTreeItem should influence the state of its parent and children. 261 * 262 * <p>By default, the independent property is false, which means that when 263 * a CheckBoxTreeItem has state changes to the selected or indeterminate 264 * properties, the state of related CheckBoxTreeItems will possibly be changed. 265 * If the independent property is set to true, the state of related CheckBoxTreeItems 266 * will <b>never</b> change. 267 * @return the independent state property of this CheckBoxTreeItem 268 */ 269 public final BooleanProperty independentProperty() { return independent; } 270 private final BooleanProperty independent = new SimpleBooleanProperty(this, "independent", false); 271 public final void setIndependent(boolean value) { independentProperty().setValue(value); } 272 public final boolean isIndependent() { return independent.getValue(); } 273 274 275 276 /*************************************************************************** 277 * * 278 * Private Implementation * 279 * * 280 **************************************************************************/ 281 282 private static boolean updateLock = false; 283 284 private void updateState() { 285 if (isIndependent()) return; 286 287 boolean firstLock = ! updateLock; 288 289 // toggle parent (recursively up to root) 290 updateLock = true; 291 updateUpwards(); 292 293 if (firstLock) updateLock = false; 294 295 // toggle children 296 if (updateLock) return; 297 updateDownwards(); 298 } 299 300 private void updateUpwards() { 301 if (! (getParent() instanceof CheckBoxTreeItem)) return; 302 303 CheckBoxTreeItem<?> parent = (CheckBoxTreeItem<?>) getParent(); 304 int selectCount = 0; 305 int indeterminateCount = 0; 306 for (TreeItem<?> child : parent.getChildren()) { 307 if (! (child instanceof CheckBoxTreeItem)) continue; 308 309 CheckBoxTreeItem<?> cbti = (CheckBoxTreeItem<?>) child; 310 311 selectCount += cbti.isSelected() && ! cbti.isIndeterminate() ? 1 : 0; 312 indeterminateCount += cbti.isIndeterminate() ? 1 : 0; 313 } 314 315 if (selectCount == parent.getChildren().size()) { 316 parent.setSelected(true); 317 parent.setIndeterminate(false); 318 } else if (selectCount == 0 && indeterminateCount == 0) { 319 parent.setSelected(false); 320 parent.setIndeterminate(false); 321 } else { 322 parent.setIndeterminate(true); 323 } 324 } 325 326 private void updateDownwards() { 327 // If this node is not a leaf, we also put all 328 // children into the same state as this branch 329 if (! isLeaf()) { 330 for (TreeItem<T> child : getChildren()) { 331 if (child instanceof CheckBoxTreeItem) { 332 CheckBoxTreeItem<T> cbti = ((CheckBoxTreeItem<T>) child); 333 cbti.setSelected(isSelected()); 334 } 335 } 336 } 337 } 338 339 private void fireEvent(CheckBoxTreeItem<T> item, boolean selectionChanged) { 340 Event evt = new CheckBoxTreeItem.TreeModificationEvent<T>(CHECK_BOX_SELECTION_CHANGED_EVENT, item, selectionChanged); 341 Event.fireEvent(this, evt); 342 } 343 344 345 /** 346 * A TreeModificationEvent class that works in a similar vein to the 347 * {@link javafx.scene.control.TreeItem.TreeModificationEvent} class, in that 348 * this event will bubble up the CheckBoxTreeItem hierarchy, until the parent 349 * node is null. 350 * 351 * @param <T> The type of the value contained within the 352 * {@link CheckBoxTreeItem#valueProperty() value} property. 353 * @since JavaFX 2.2 354 */ 355 public static class TreeModificationEvent<T> extends Event { 356 private static final long serialVersionUID = -8445355590698862999L; 357 358 private transient final CheckBoxTreeItem<T> treeItem; 359 private final boolean selectionChanged; 360 361 /** 362 * Common supertype for all tree modification event types. 363 */ 364 public static final EventType<Event> ANY = 365 new EventType<Event> (Event.ANY, "TREE_MODIFICATION"); 366 367 /** 368 * Creates a default TreeModificationEvent instance to represent the 369 * change in selection/indeterminate states for the given CheckBoxTreeItem 370 * instance. 371 * @param eventType the eventType 372 * @param treeItem the treeItem 373 * @param selectionChanged the selectioonChanged 374 */ 375 public TreeModificationEvent(EventType<? extends Event> eventType, CheckBoxTreeItem<T> treeItem, boolean selectionChanged) { 376 super(eventType); 377 this.treeItem = treeItem; 378 this.selectionChanged = selectionChanged; 379 } 380 381 /** 382 * Returns the CheckBoxTreeItem that this event occurred upon. 383 * @return The CheckBoxTreeItem that this event occurred upon. 384 */ 385 public CheckBoxTreeItem<T> getTreeItem() { 386 return treeItem; 387 } 388 389 /** 390 * Indicates the reason for this event is that the selection on the 391 * CheckBoxTreeItem changed (as opposed to it becoming indeterminate). 392 * @return has the CheckBoxTreeItem's selection changed 393 */ 394 public boolean wasSelectionChanged() { 395 return selectionChanged; 396 } 397 398 /** 399 * Indicates the reason for this event is that the indeterminate 400 * state on the CheckBoxTreeItem changed (as opposed to it becoming 401 * selected or unselected). 402 * @return has the CheckBoxTreeItem's indeterminate changed 403 */ 404 public boolean wasIndeterminateChanged() { 405 return ! selectionChanged; 406 } 407 } 408 }