/* * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.control; import javafx.scene.control.cell.CheckBoxTreeCell; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.event.Event; import javafx.event.EventType; import javafx.scene.Node; /** * TreeItem subclass that adds support for being in selected, unselected, and * indeterminate states. This is useful when used in conjunction with a TreeView * which has a {@link CheckBoxTreeCell} installed. * *

A CheckBoxTreeItem can be {@link #independentProperty() independent} or * dependent. By default, CheckBoxTreeItem instances are dependent, which means * that any changes to the selection state of a TreeItem will have an impact on * parent and children CheckBoxTreeItem instances. If a CheckBoxTreeItem is * set to be independent, this means that any changes to that CheckBoxTreeItem * will not directly impact the state of parent and children CheckBoxTreeItem * instances. * *

The {@link #indeterminateProperty() indeterminate} property is used to * represent the same concept as that in {@link CheckBox#indeterminateProperty()}, * namely, that the CheckBox is neither selected or unselected. This is commonly * used inside a TreeView when some, but not all, of a branches children are * selected. * *

A simple example of using the CheckBoxTreeItem class, in conjunction with * {@link CheckBoxTreeCell} is shown below: * *


 * // create the tree model
 * CheckBoxTreeItem<String> jonathanGiles = new CheckBoxTreeItem<String>("Jonathan");
 * CheckBoxTreeItem<String> juliaGiles = new CheckBoxTreeItem<String>("Julia");
 * CheckBoxTreeItem<String> mattGiles = new CheckBoxTreeItem<String>("Matt");
 * CheckBoxTreeItem<String> sueGiles = new CheckBoxTreeItem<String>("Sue");
 * CheckBoxTreeItem<String> ianGiles = new CheckBoxTreeItem<String>("Ian");
 *
 * CheckBoxTreeItem<String> gilesFamily = new CheckBoxTreeItem<String>("Giles Family");
 * gilesFamily.setExpanded(true);
 * gilesFamily.getChildren().addAll(jonathanGiles, juliaGiles, mattGiles, sueGiles, ianGiles);
 *
 * // create the treeView
 * final TreeView<String> treeView = new TreeView<String>();
 * treeView.setRoot(gilesFamily);
 *
 * // set the cell factory
 * treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
* * @param The type of the value contained within the TreeItem * @see CheckBoxTreeCell * @see TreeItem * @see CheckBox * @since JavaFX 2.2 */ // TODO the TreeModificationEvent doesn't actually bubble in the same way as // TreeItem - it just looks that way as the 'bubbling' is done via changing the // state of all parent items. public class CheckBoxTreeItem extends TreeItem { /** * An EventType used when the CheckBoxTreeItem selection / indeterminate * state changes. To use this, it is recommended that you use code along the * lines of the following: * *
     * {@code
     * child1.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), new EventHandler>() {
     *     public void handle(TreeModificationEvent event) {
     *          ...
     *     }
     * });}
     * 
* * @param The type of the value contained within the TreeItem. * @return the EventType used when the CheckBoxTreeItem selection / indeterminate * state changes */ @SuppressWarnings("unchecked") public static EventType> checkBoxSelectionChangedEvent() { return (EventType>) CHECK_BOX_SELECTION_CHANGED_EVENT; } private static final EventType CHECK_BOX_SELECTION_CHANGED_EVENT = new EventType(TreeModificationEvent.ANY, "checkBoxSelectionChangedEvent"); /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates an empty CheckBoxTreeItem. */ public CheckBoxTreeItem() { this(null); } /** * Creates a CheckBoxTreeItem with the value property set to the provided * object. * * @param value The object to be stored as the value of this TreeItem. */ public CheckBoxTreeItem(T value) { this(value, null, false); } /** * Creates a CheckBoxTreeItem with the value property set to the provided * object, and the graphic set to the provided Node. * * @param value The object to be stored as the value of this CheckBoxTreeItem. * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. */ public CheckBoxTreeItem(T value, Node graphic) { this(value, graphic, false); } /** * Creates a CheckBoxTreeItem with the value property set to the provided * object, the graphic set to the provided Node, and the initial state * of the {@link #selectedProperty()} set to the provided boolean value. * * @param value The object to be stored as the value of this CheckBoxTreeItem. * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. * @param selected The initial value of the * {@link #selectedProperty() selected} property. */ public CheckBoxTreeItem(T value, Node graphic, boolean selected) { this(value, graphic, selected, false); } /** * Creates a CheckBoxTreeItem with the value property set to the provided * object, the graphic set to the provided Node, the initial state * of the {@link #selectedProperty()} set to the provided boolean value, and * the initial state of the {@link #independentProperty() independent} * property to the provided boolean value. * * @param value The object to be stored as the value of this CheckBoxTreeItem. * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem. * @param selected The initial value of the * {@link #selectedProperty() selected} property. * @param independent The initial value of the * {@link #independentProperty() independent} property */ public CheckBoxTreeItem(T value, Node graphic, boolean selected, boolean independent) { super(value, graphic); setSelected(selected); setIndependent(independent); selectedProperty().addListener(stateChangeListener); indeterminateProperty().addListener(stateChangeListener); } /*************************************************************************** * * * Callbacks * * * **************************************************************************/ private final ChangeListener stateChangeListener = (ov, oldVal, newVal) -> { updateState(); }; /*************************************************************************** * * * Properties * * * **************************************************************************/ // --- Selected private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false) { @Override protected void invalidated() { super.invalidated(); fireEvent(CheckBoxTreeItem.this, true); } }; /** * Sets the selected state of this CheckBoxTreeItem. * @param value the selected state of this CheckBoxTreeItem */ public final void setSelected(boolean value) { selectedProperty().setValue(value); } /** * Returns the selected state of this CheckBoxTreeItem. * @return true if CheckBoxTreeItem is selected */ public final boolean isSelected() { return selected.getValue(); } /** * A {@link BooleanProperty} used to represent the selected state of this CheckBoxTreeItem. * @return the selected state property of this CheckBoxTreeItem */ public final BooleanProperty selectedProperty() { return selected; } // --- Indeterminate private final BooleanProperty indeterminate = new SimpleBooleanProperty(this, "indeterminate", false) { @Override protected void invalidated() { super.invalidated(); fireEvent(CheckBoxTreeItem.this, false); } }; /** * Sets the indeterminate state of this CheckBoxTreeItem. * @param value the indeterminate state of this CheckBoxTreeItem */ public final void setIndeterminate(boolean value) { indeterminateProperty().setValue(value); } /** * Returns the indeterminate state of this CheckBoxTreeItem. * @return true if CheckBoxTreeItem is indeterminate state */ public final boolean isIndeterminate() { return indeterminate.getValue(); } /** * A {@link BooleanProperty} used to represent the indeterminate state of this CheckBoxTreeItem. * @return the indeterminate state property of this CheckBoxTreeItem */ public final BooleanProperty indeterminateProperty() { return indeterminate; } // --- Independent /** * A {@link BooleanProperty} used to represent the independent state of this CheckBoxTreeItem. * The independent state is used to represent whether changes to a single * CheckBoxTreeItem should influence the state of its parent and children. * *

By default, the independent property is false, which means that when * a CheckBoxTreeItem has state changes to the selected or indeterminate * properties, the state of related CheckBoxTreeItems will possibly be changed. * If the independent property is set to true, the state of related CheckBoxTreeItems * will never change. * @return the independent state property of this CheckBoxTreeItem */ public final BooleanProperty independentProperty() { return independent; } private final BooleanProperty independent = new SimpleBooleanProperty(this, "independent", false); public final void setIndependent(boolean value) { independentProperty().setValue(value); } public final boolean isIndependent() { return independent.getValue(); } /*************************************************************************** * * * Private Implementation * * * **************************************************************************/ private static boolean updateLock = false; private void updateState() { if (isIndependent()) return; boolean firstLock = ! updateLock; // toggle parent (recursively up to root) updateLock = true; updateUpwards(); if (firstLock) updateLock = false; // toggle children if (updateLock) return; updateDownwards(); } private void updateUpwards() { if (! (getParent() instanceof CheckBoxTreeItem)) return; CheckBoxTreeItem parent = (CheckBoxTreeItem) getParent(); int selectCount = 0; int indeterminateCount = 0; for (TreeItem child : parent.getChildren()) { if (! (child instanceof CheckBoxTreeItem)) continue; CheckBoxTreeItem cbti = (CheckBoxTreeItem) child; selectCount += cbti.isSelected() && ! cbti.isIndeterminate() ? 1 : 0; indeterminateCount += cbti.isIndeterminate() ? 1 : 0; } if (selectCount == parent.getChildren().size()) { parent.setSelected(true); parent.setIndeterminate(false); } else if (selectCount == 0 && indeterminateCount == 0) { parent.setSelected(false); parent.setIndeterminate(false); } else { parent.setIndeterminate(true); } } private void updateDownwards() { // If this node is not a leaf, we also put all // children into the same state as this branch if (! isLeaf()) { for (TreeItem child : getChildren()) { if (child instanceof CheckBoxTreeItem) { CheckBoxTreeItem cbti = ((CheckBoxTreeItem) child); cbti.setSelected(isSelected()); } } } } private void fireEvent(CheckBoxTreeItem item, boolean selectionChanged) { Event evt = new CheckBoxTreeItem.TreeModificationEvent(CHECK_BOX_SELECTION_CHANGED_EVENT, item, selectionChanged); Event.fireEvent(this, evt); } /** * A TreeModificationEvent class that works in a similar vein to the * {@link javafx.scene.control.TreeItem.TreeModificationEvent} class, in that * this event will bubble up the CheckBoxTreeItem hierarchy, until the parent * node is null. * * @param The type of the value contained within the * {@link CheckBoxTreeItem#valueProperty() value} property. * @since JavaFX 2.2 */ public static class TreeModificationEvent extends Event { private static final long serialVersionUID = -8445355590698862999L; private transient final CheckBoxTreeItem treeItem; private final boolean selectionChanged; /** * Common supertype for all tree modification event types. */ public static final EventType ANY = new EventType (Event.ANY, "TREE_MODIFICATION"); /** * Creates a default TreeModificationEvent instance to represent the * change in selection/indeterminate states for the given CheckBoxTreeItem * instance. * @param eventType the eventType * @param treeItem the treeItem * @param selectionChanged the selectioonChanged */ public TreeModificationEvent(EventType eventType, CheckBoxTreeItem treeItem, boolean selectionChanged) { super(eventType); this.treeItem = treeItem; this.selectionChanged = selectionChanged; } /** * Returns the CheckBoxTreeItem that this event occurred upon. * @return The CheckBoxTreeItem that this event occurred upon. */ public CheckBoxTreeItem getTreeItem() { return treeItem; } /** * Indicates the reason for this event is that the selection on the * CheckBoxTreeItem changed (as opposed to it becoming indeterminate). * @return has the CheckBoxTreeItem's selection changed */ public boolean wasSelectionChanged() { return selectionChanged; } /** * Indicates the reason for this event is that the indeterminate * state on the CheckBoxTreeItem changed (as opposed to it becoming * selected or unselected). * @return has the CheckBoxTreeItem's indeterminate changed */ public boolean wasIndeterminateChanged() { return ! selectionChanged; } } }