Spec-Zone .ru
спецификации, руководства, описания, API
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;
027
028import javafx.scene.control.cell.CheckBoxTreeCell;
029import javafx.beans.property.BooleanProperty;
030import javafx.beans.property.SimpleBooleanProperty;
031import javafx.beans.value.ChangeListener;
032import javafx.beans.value.ObservableValue;
033import javafx.event.Event;
034import javafx.event.EventHandler;
035import javafx.event.EventType;
036import javafx.scene.Node;
037
038/**
039 * TreeItem subclass that adds support for being in selected, unselected, and
040 * indeterminate states. This is useful when used in conjunction with a TreeView
041 * which has a {@link CheckBoxTreeCell} installed.
042 * 
043 * <p>A CheckBoxTreeItem can be {@link #independentProperty() independent} or 
044 * dependent. By default, CheckBoxTreeItem instances are dependent, which means 
045 * that any changes to the selection state of a TreeItem will have an impact on 
046 * parent and children CheckBoxTreeItem instances. If a CheckBoxTreeItem is
047 * set to be independent, this means that any changes to that CheckBoxTreeItem
048 * will not directly impact the state of parent and children CheckBoxTreeItem
049 * instances.
050 * 
051 * <p>The {@link #indeterminateProperty() indeterminate} property is used to
052 * represent the same concept as that in {@link CheckBox#indeterminateProperty()},
053 * namely, that the CheckBox is neither selected or unselected. This is commonly
054 * used inside a TreeView when some, but not all, of a branches children are
055 * selected.
056 * 
057 * <p>A simple example of using the CheckBoxTreeItem class, in conjunction with 
058 * {@link CheckBoxTreeCell} is shown below:
059 * 
060 * <pre><code>
061 * // create the tree model
062 * CheckBoxTreeItem&lt;String&gt; jonathanGiles = new CheckBoxTreeItem&lt;String&gt;("Jonathan");
063 * CheckBoxTreeItem&lt;String&gt; juliaGiles = new CheckBoxTreeItem&lt;String&gt;("Julia");
064 * CheckBoxTreeItem&lt;String&gt; mattGiles = new CheckBoxTreeItem&lt;String&gt;("Matt");
065 * CheckBoxTreeItem&lt;String&gt; sueGiles = new CheckBoxTreeItem&lt;String&gt;("Sue");
066 * CheckBoxTreeItem&lt;String&gt; ianGiles = new CheckBoxTreeItem&lt;String&gt;("Ian");
067 * 
068 * CheckBoxTreeItem&lt;String&gt; gilesFamily = new CheckBoxTreeItem&lt;String&gt;("Giles Family");
069 * gilesFamily.setExpanded(true);
070 * gilesFamily.getChildren().addAll(jonathanGiles, juliaGiles, mattGiles, sueGiles, ianGiles);
071 * 
072 * // create the treeView
073 * final TreeView&lt;String&gt; treeView = new TreeView&lt;String&gt;();
074 * treeView.setRoot(gilesFamily);
075 *       
076 * // set the cell factory
077 * treeView.setCellFactory(CheckBoxTreeCell.&lt;String&gt;forTreeView());</code></pre>
078 *
079 * @see CheckBoxTreeCell
080 * @see TreeItem
081 * @see CheckBox
082 * @since 2.2
083 */
084// TODO the TreeModificationEvent doesn't actually bubble in the same way as
085// TreeItem - it just looks that way as the 'bubbling' is done via changing the
086// state of all parent items.
087public class CheckBoxTreeItem<T> extends TreeItem<T> {
088    
089    /**
090     * An EventType used when the CheckBoxTreeItem selection / indeterminate
091     * state changes. To use this, it is recommended that you use code along the
092     * lines of the following:
093     *
094     *<pre>
095     * {@code
096     * child1.addEventHandler(CheckBoxTreeItem.<String>checkBoxSelectionChangedEvent(), new EventHandler<TreeModificationEvent<String>>() {
097     *     public void handle(TreeModificationEvent<String> event) {
098     *          ...     
099     *     }
100     * });}
101     * </pre>
102     * 
103     * @param <T> The type of the value contained within the TreeItem.
104     */
105    @SuppressWarnings("unchecked")
106    public static <T> EventType<TreeModificationEvent<T>> checkBoxSelectionChangedEvent() {
107        return (EventType<TreeModificationEvent<T>>) CHECK_BOX_SELECTION_CHANGED_EVENT;
108    }
109    private static final EventType<? extends Event> CHECK_BOX_SELECTION_CHANGED_EVENT
110            = new EventType<Event>(TreeModificationEvent.ANY, "checkBoxSelectionChangedEvent");
111
112    /***************************************************************************
113     *                                                                         *
114     * Constructors                                                            *
115     *                                                                         *
116     **************************************************************************/
117    
118    /**
119     * Creates an empty CheckBoxTreeItem.
120     */
121    public CheckBoxTreeItem() {
122        this(null);
123    }
124    
125    /**
126     * Creates a CheckBoxTreeItem with the value property set to the provided 
127     * object.
128     * 
129     * @param value The object to be stored as the value of this TreeItem.
130     */
131    public CheckBoxTreeItem(T value) {
132        this(value, null, false);
133    }
134
135    /**
136     * Creates a CheckBoxTreeItem with the value property set to the provided 
137     * object, and the graphic set to the provided Node.
138     * 
139     * @param value The object to be stored as the value of this CheckBoxTreeItem.
140     * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
141     */
142    public CheckBoxTreeItem(T value, Node graphic) {
143        this(value, graphic, false);
144    }
145    
146    /**
147     * Creates a CheckBoxTreeItem with the value property set to the provided 
148     * object, the graphic set to the provided Node, and the initial state
149     * of the {@link #selectedProperty()} set to the provided boolean value.
150     * 
151     * @param value The object to be stored as the value of this CheckBoxTreeItem.
152     * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
153     * @param selected The initial value of the 
154     *            {@link #selectedProperty() selected} property.
155     */
156    public CheckBoxTreeItem(T value, Node graphic, boolean selected) {
157        this(value, graphic, selected, false);
158    }
159
160    /**
161     * Creates a CheckBoxTreeItem with the value property set to the provided 
162     * object, the graphic set to the provided Node, the initial state
163     * of the {@link #selectedProperty()} set to the provided boolean value, and
164     * the initial state of the {@link #independentProperty() independent} 
165     * property to the provided boolean value.
166     * 
167     * @param value The object to be stored as the value of this CheckBoxTreeItem.
168     * @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
169     * @param selected The initial value of the 
170     *            {@link #selectedProperty() selected} property.
171     * @param independent The initial value of the 
172     *            {@link #independentProperty() independent} property
173     */
174    public CheckBoxTreeItem(T value, Node graphic, boolean selected, boolean independent) {
175        super(value, graphic);
176        setSelected(selected);
177        setIndependent(independent);
178        
179        selectedProperty().addListener(stateChangeListener);
180        indeterminateProperty().addListener(stateChangeListener);
181    }
182
183    
184    
185    /***************************************************************************
186     *                                                                         *
187     * Callbacks                                                               *
188     *                                                                         *
189     **************************************************************************/   
190    private final ChangeListener<Boolean> stateChangeListener = new ChangeListener<Boolean>() {
191        @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean oldVal, Boolean newVal) {
192            updateState();
193        }
194    };
195    
196    
197    /***************************************************************************
198     *                                                                         *
199     * Properties                                                              *
200     *                                                                         *
201     **************************************************************************/ 
202    
203    // --- Selected
204    private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false) {
205        @Override protected void invalidated() {
206            super.invalidated();
207            fireEvent(CheckBoxTreeItem.this, true);
208        }
209    };
210    /** Sets the selected state of this CheckBoxTreeItem. */
211    public final void setSelected(boolean value) { selectedProperty().setValue(value); }
212    /** Returns the selected state of this CheckBoxTreeItem. */
213    public final boolean isSelected() { return selected.getValue(); }
214    /** A {@link BooleanProperty} used to represent the selected state of this CheckBoxTreeItem. */
215    public final BooleanProperty selectedProperty() { return selected; }
216    
217    
218    // --- Indeterminate
219    private final BooleanProperty indeterminate = new SimpleBooleanProperty(this, "indeterminate", false) {
220        @Override protected void invalidated() {
221            super.invalidated();
222            fireEvent(CheckBoxTreeItem.this, false);
223        }
224    };
225    /** Sets the indeterminate state of this CheckBoxTreeItem. */
226    public final void setIndeterminate(boolean value) { indeterminateProperty().setValue(value); }
227    /** Returns the indeterminate state of this CheckBoxTreeItem. */
228    public final boolean isIndeterminate() { return indeterminate.getValue(); }
229    /** A {@link BooleanProperty} used to represent the indeterminate state of this CheckBoxTreeItem. */
230    public final BooleanProperty indeterminateProperty() { return indeterminate; }
231    
232    
233    // --- Independent
234    /** 
235     * A {@link BooleanProperty} used to represent the independent state of this CheckBoxTreeItem. 
236     * The independent state is used to represent whether changes to a single
237     * CheckBoxTreeItem should influence the state of its parent and children.
238     * 
239     * <p>By default, the independent property is false, which means that when
240     * a CheckBoxTreeItem has state changes to the selected or indeterminate
241     * properties, the state of related CheckBoxTreeItems will possibly be changed.
242     * If the independent property is set to true, the state of related CheckBoxTreeItems
243     * will <b>never</b> change.
244     */
245    public final BooleanProperty independentProperty() { return independent; }
246    private final BooleanProperty independent = new SimpleBooleanProperty(this, "independent", false);
247    public final void setIndependent(boolean value) { independentProperty().setValue(value); }
248    public final boolean isIndependent() { return independent.getValue(); }
249    
250    
251    
252    /***************************************************************************
253     *                                                                         *
254     * Private Implementation                                                  *
255     *                                                                         *
256     **************************************************************************/
257    
258    private static boolean updateLock = false;
259    
260    private void updateState() {
261        if (isIndependent()) return;
262        
263        boolean firstLock = ! updateLock;
264        
265        // toggle parent (recursively up to root)
266        updateLock = true;
267        updateUpwards();
268        
269        if (firstLock) updateLock = false;
270        
271        // toggle children
272        if (updateLock) return;
273        updateDownwards();
274    }
275
276    private void updateUpwards() {
277        if (! (getParent() instanceof CheckBoxTreeItem)) return;
278        
279        CheckBoxTreeItem<?> parent = (CheckBoxTreeItem<?>) getParent();
280        int selectCount = 0;
281        int indeterminateCount = 0;
282        for (TreeItem<?> child : parent.getChildren()) {
283            if (! (child instanceof CheckBoxTreeItem)) continue;
284            
285            CheckBoxTreeItem<?> cbti = (CheckBoxTreeItem<?>) child;
286            
287            selectCount += cbti.isSelected() && ! cbti.isIndeterminate() ? 1 : 0;
288            indeterminateCount += cbti.isIndeterminate() ? 1 : 0;
289        }
290        
291        if (selectCount == parent.getChildren().size()) {
292            parent.setSelected(true);
293            parent.setIndeterminate(false);
294        } else if (selectCount == 0 && indeterminateCount == 0) {
295            parent.setSelected(false);
296            parent.setIndeterminate(false);
297        } else {
298            parent.setIndeterminate(true);
299        }
300    }
301    
302    private void updateDownwards() {
303        // If this node is not a leaf, we also put all
304        // children into the same state as this branch
305        if (! isLeaf()) {
306            for (TreeItem<T> child : getChildren()) {
307                if (child instanceof CheckBoxTreeItem) {
308                    CheckBoxTreeItem<T> cbti = ((CheckBoxTreeItem<T>) child);
309                    cbti.setSelected(isSelected());
310                }
311            }
312        }
313    }
314    
315    private void fireEvent(CheckBoxTreeItem<T> item, boolean selectionChanged) {
316        Event evt = new CheckBoxTreeItem.TreeModificationEvent<T>(CHECK_BOX_SELECTION_CHANGED_EVENT, item, selectionChanged);
317        Event.fireEvent(this, evt);
318    }
319    
320    
321    /**
322     * A TreeModificationEvent class that works in a similar vein to the 
323     * {@link javafx.scene.control.TreeItem.TreeModificationEvent} class, in that
324     * this event will bubble up the CheckBoxTreeItem hierarchy, until the parent
325     * node is null.
326     * 
327     * @param <T> The type of the value contained within the
328     *      {@link CheckBoxTreeItem#valueProperty() value} property.
329     */
330    public static class TreeModificationEvent<T> extends Event {
331        private static final long serialVersionUID = -8445355590698862999L;
332
333        private transient final CheckBoxTreeItem<T> treeItem;
334        private final boolean selectionChanged;
335
336        /**
337         * Common supertype for all tree modification event types.
338         */
339        public static final EventType<Event> ANY =
340                new EventType<Event> (Event.ANY, "TREE_MODIFICATION");
341
342        /**
343         * Creates a default TreeModificationEvent instance to represent the 
344         * change in selection/indeterminate states for the given CheckBoxTreeItem
345         * instance.
346         */
347        public TreeModificationEvent(EventType<? extends Event> eventType, CheckBoxTreeItem<T> treeItem, boolean selectionChanged) {
348            super(eventType);
349            this.treeItem = treeItem;
350            this.selectionChanged = selectionChanged;
351        }
352        
353        /** 
354         * Returns the CheckBoxTreeItem that this event occurred upon.
355         * @return The CheckBoxTreeItem that this event occurred upon.
356         */
357        public CheckBoxTreeItem<T> getTreeItem() {
358            return treeItem;
359        }
360        
361        /**
362         * Indicates the the reason for this event is that the selection on the
363         * CheckBoxTreeItem changed (as opposed to it becoming indeterminate).
364         */
365        public boolean wasSelectionChanged() {
366            return selectionChanged;
367        }
368        
369        /**
370         * Indicates the the reason for this event is that the indeterminate
371         * state on the CheckBoxTreeItem changed (as opposed to it becoming 
372         * selected or unselected).
373         */
374        public boolean wasIndeterminateChanged() {
375            return ! selectionChanged;
376        }
377    }
378}