001/*
002 * Copyright (c) 2008, 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 java.util.ArrayList;
029import java.util.Collections;
030import java.util.List;
031
032import javafx.beans.property.BooleanProperty;
033import javafx.beans.property.DoubleProperty;
034import javafx.beans.property.IntegerProperty;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.ObjectPropertyBase;
037import javafx.beans.property.SimpleDoubleProperty;
038import javafx.beans.property.SimpleIntegerProperty;
039import javafx.beans.property.SimpleObjectProperty;
040import javafx.beans.value.ChangeListener;
041import javafx.beans.value.ObservableValue;
042import javafx.css.CssMetaData;
043import javafx.css.Styleable;
044import javafx.css.StyleableDoubleProperty;
045import javafx.css.StyleableProperty;
046import javafx.event.Event;
047import javafx.event.EventHandler;
048import javafx.event.EventType;
049import javafx.scene.control.TreeItem.TreeModificationEvent;
050import javafx.scene.layout.Region;
051import javafx.util.Callback;
052
053import javafx.event.WeakEventHandler;
054import com.sun.javafx.css.converters.SizeConverter;
055import com.sun.javafx.scene.control.skin.TreeViewSkin;
056import com.sun.javafx.scene.control.skin.VirtualContainerBase;
057import java.lang.ref.WeakReference;
058import javafx.application.Platform;
059import javafx.beans.DefaultProperty;
060import javafx.beans.property.ReadOnlyIntegerProperty;
061import javafx.beans.property.ReadOnlyIntegerWrapper;
062import javafx.beans.property.SimpleBooleanProperty;
063import javafx.beans.property.ReadOnlyObjectProperty;
064import javafx.beans.property.ReadOnlyObjectWrapper;
065import javafx.beans.value.WeakChangeListener;
066
067/**
068 * The TreeView control provides a view on to a tree root (of type 
069 * {@link TreeItem}). By using a TreeView, it is possible to drill down into the
070 * children of a TreeItem, recursively until a TreeItem has no children (that is,
071 * it is a <i>leaf</i> node in the tree). To facilitate this, unlike controls
072 * like {@link ListView}, in TreeView it is necessary to <strong>only</strong> 
073 * specify the {@link #rootProperty() root} node. 
074 *
075 * <p>
076 * For more information on building up a tree using this approach, refer to the 
077 * {@link TreeItem} class documentation. Briefly however, to create a TreeView, 
078 * you should do something along the lines of the following:
079 * <pre><code>
080 * TreeItem&lt;String&gt; root = new TreeItem&lt;String&gt;("Root Node");
081 * root.setExpanded(true);
082 * root.getChildren().addAll(
083 *     new TreeItem&lt;String&gt;("Item 1"),
084 *     new TreeItem&lt;String&gt;("Item 2"),
085 *     new TreeItem&lt;String&gt;("Item 3")
086 * );
087 * TreeView&lt;String&gt; treeView = new TreeView&lt;String&gt;(root);
088 * </code></pre>
089 * 
090 * <p>
091 * A TreeView may be configured to optionally hide the root node by setting the
092 * {@link #setShowRoot(boolean) showRoot} property to {@code false}. If the root
093 * node is hidden, there is one less level of indentation, and all children
094 * nodes of the root node are shown. By default, the root node is shown in the
095 * TreeView.
096 * 
097 * <h3>TreeView Selection / Focus APIs</h3>
098 * <p>To track selection and focus, it is necessary to become familiar with the
099 * {@link SelectionModel} and {@link FocusModel} classes. A TreeView has at most
100 * one instance of each of these classes, available from 
101 * {@link #selectionModelProperty() selectionModel} and 
102 * {@link #focusModelProperty() focusModel} properties respectively.
103 * Whilst it is possible to use this API to set a new selection model, in
104 * most circumstances this is not necessary - the default selection and focus
105 * models should work in most circumstances.
106 * 
107 * <p>The default {@link SelectionModel} used when instantiating a TreeView is
108 * an implementation of the {@link MultipleSelectionModel} abstract class. 
109 * However, as noted in the API documentation for
110 * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
111 * property, the default value is {@link SelectionMode#SINGLE}. To enable 
112 * multiple selection in a default TreeView instance, it is therefore necessary
113 * to do the following:
114 * 
115 * <pre>
116 * {@code 
117 * treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
118 * 
119 * <h3>Customizing TreeView Visuals</h3>
120 * <p>The visuals of the TreeView can be entirely customized by replacing the 
121 * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to
122 * generate {@link TreeCell} instances, which are used to represent an item in the
123 * TreeView. See the {@link Cell} class documentation for a more complete
124 * description of how to write custom Cells.
125 *
126 * @see TreeItem
127 * @see TreeCell
128 * @param <T> The type of the item contained within the {@link TreeItem} value
129 *      property for all tree items in this TreeView.
130 */
131@DefaultProperty("root")
132public class TreeView<T> extends Control {
133    
134    /***************************************************************************
135     *                                                                         *
136     * Static properties and methods                                           *
137     *                                                                         *
138     **************************************************************************/
139    
140    /** 
141     * An EventType that indicates some edit event has occurred. It is the parent
142     * type of all other edit events: {@link #editStartEvent},
143     *  {@link #editCommitEvent} and {@link #editCancelEvent}.
144     * 
145     * @return An EventType that indicates some edit event has occurred.
146     */
147    @SuppressWarnings("unchecked")
148    public static <T> EventType<EditEvent<T>> editAnyEvent() {
149        return (EventType<EditEvent<T>>) EDIT_ANY_EVENT;
150    }
151    private static final EventType<?> EDIT_ANY_EVENT =
152            new EventType(Event.ANY, "TREE_VIEW_EDIT");
153
154    /**
155     * An EventType used to indicate that an edit event has started within the
156     * TreeView upon which the event was fired.
157     * 
158     * @return An EventType used to indicate that an edit event has started.
159     */
160    @SuppressWarnings("unchecked")
161    public static <T> EventType<EditEvent<T>> editStartEvent() {
162        return (EventType<EditEvent<T>>) EDIT_START_EVENT;
163    }
164    private static final EventType<?> EDIT_START_EVENT =
165            new EventType(editAnyEvent(), "EDIT_START");
166
167    /**
168     * An EventType used to indicate that an edit event has just been canceled
169     * within the TreeView upon which the event was fired.
170     * 
171     * @return An EventType used to indicate that an edit event has just been
172     *      canceled.
173     */
174    @SuppressWarnings("unchecked")
175    public static <T> EventType<EditEvent<T>> editCancelEvent() {
176        return (EventType<EditEvent<T>>) EDIT_CANCEL_EVENT;
177    }
178    private static final EventType<?> EDIT_CANCEL_EVENT =
179            new EventType(editAnyEvent(), "EDIT_CANCEL");
180
181    /**
182     * An EventType that is used to indicate that an edit in a TreeView has been
183     * committed. This means that user has made changes to the data of a
184     * TreeItem, and that the UI should be updated.
185     * 
186     * @return An EventType that is used to indicate that an edit in a TreeView
187     *      has been committed.
188     */
189    @SuppressWarnings("unchecked")
190    public static <T> EventType<EditEvent<T>> editCommitEvent() {
191        return (EventType<EditEvent<T>>) EDIT_COMMIT_EVENT;
192    }
193    private static final EventType<?> EDIT_COMMIT_EVENT =
194            new EventType(editAnyEvent(), "EDIT_COMMIT");
195    
196    /**
197     * Returns the number of levels of 'indentation' of the given TreeItem, 
198     * based on how many times getParent() can be recursively called. If the 
199     * given TreeItem is the root node, or if the TreeItem does not have any 
200     * parent set, the returned value will be zero. For each time getParent() is 
201     * recursively called, the returned value is incremented by one.
202     * 
203     * @param node The TreeItem for which the level is needed.
204     * @return An integer representing the number of parents above the given node,
205     *         or -1 if the given TreeItem is null.
206     */
207    public static int getNodeLevel(TreeItem<?> node) {
208        if (node == null) return -1;
209
210        int level = 0;
211        TreeItem parent = node.getParent();
212        while (parent != null) {
213            level++;
214            parent = parent.getParent();
215        }
216
217        return level;
218    }
219
220    
221    /***************************************************************************
222     *                                                                         *
223     * Constructors                                                            *
224     *                                                                         *
225     **************************************************************************/
226
227    /**
228     * Creates an empty TreeView.
229     * 
230     * <p>Refer to the {@link TreeView} class documentation for details on the
231     * default state of other properties.
232     */
233    public TreeView() {
234        this(null);
235    }
236
237    /**
238     * Creates a TreeView with the provided root node.
239     * 
240     * <p>Refer to the {@link TreeView} class documentation for details on the
241     * default state of other properties.
242     * 
243     * @param root The node to be the root in this TreeView.
244     */
245    public TreeView(TreeItem<T> root) {
246        getStyleClass().setAll("tree-view");
247
248        setRoot(root);
249        updateExpandedItemCount(root);
250
251        // install default selection and focus models - it's unlikely this will be changed
252        // by many users.
253        MultipleSelectionModel sm = new TreeViewBitSetSelectionModel<T>(this);
254        setSelectionModel(sm);
255        setFocusModel(new TreeViewFocusModel<T>(this));
256    }
257    
258    
259    
260    /***************************************************************************
261     *                                                                         *
262     * Instance Variables                                                      *
263     *                                                                         *
264     **************************************************************************/ 
265    
266    // used in the tree item modification event listener. Used by the 
267    // layoutChildren method to determine whether the tree item count should
268    // be recalculated.
269    private boolean expandedItemCountDirty = true;
270    
271    
272    /***************************************************************************
273     *                                                                         *
274     * Callbacks and Events                                                    *
275     *                                                                         *
276     **************************************************************************/
277    
278    // we use this to forward events that have bubbled up TreeItem instances
279    // to the TreeViewSkin, to force it to recalculate teh item count and redraw
280    // if necessary
281    private final EventHandler<TreeModificationEvent<T>> rootEvent = new EventHandler<TreeModificationEvent<T>>() {
282        @Override public void handle(TreeModificationEvent<T> e) {
283            // this forces layoutChildren at the next pulse, and therefore
284            // updates the item count if necessary
285            EventType eventType = e.getEventType();
286            boolean match = false;
287            while (eventType != null) {
288                if (eventType.equals(TreeItem.<T>expandedItemCountChangeEvent())) {
289                    match = true;
290                    break;
291                }
292                eventType = eventType.getSuperType();
293            }
294            
295            if (match) {
296                expandedItemCountDirty = true;
297                requestLayout();
298            }
299        }
300    };
301    
302    private WeakEventHandler weakRootEventListener;
303    
304    
305    /***************************************************************************
306     *                                                                         *
307     * Properties                                                              *
308     *                                                                         *
309     **************************************************************************/
310    
311
312    // --- Cell Factory
313    private ObjectProperty<Callback<TreeView<T>, TreeCell<T>>> cellFactory;
314    
315    /**
316     * Sets the cell factory that will be used for creating TreeCells,
317     * which are used to represent items in the
318     * TreeView. The factory works identically to the cellFactory in ListView
319     * and other complex composite controls. It is called to create a new
320     * TreeCell only when the system has determined that it doesn't have enough
321     * cells to represent the currently visible items. The TreeCell is reused
322     * by the system to represent different items in the tree when possible.
323     *
324     * <p>Refer to the {@link Cell} class documentation for more details.
325     * 
326     * @param value The {@link Callback} to use for generating TreeCell instances,
327     *      or null if the default cell factory should be used.
328     */
329    public final void setCellFactory(Callback<TreeView<T>, TreeCell<T>> value) { 
330        cellFactoryProperty().set(value); 
331    }
332    
333    /**
334     * <p>Returns the cell factory that will be used for creating TreeCells,
335     * which are used to represent items in the TreeView, or null if no custom
336     * cell factory has been set.
337     */
338    public final Callback<TreeView<T>, TreeCell<T>> getCellFactory() { 
339        return cellFactory == null ? null : cellFactory.get(); 
340    }
341    
342    /**
343     * Represents the cell factory that will be used for creating TreeCells,
344     * which are used to represent items in the TreeView. 
345     */
346    public final ObjectProperty<Callback<TreeView<T>, TreeCell<T>>> cellFactoryProperty() {
347        if (cellFactory == null) {
348            cellFactory = new SimpleObjectProperty<Callback<TreeView<T>, TreeCell<T>>>(this, "cellFactory");
349        }
350        return cellFactory;
351    }
352
353    
354    // --- Root
355    private ObjectProperty<TreeItem<T>> root = new SimpleObjectProperty<TreeItem<T>>(this, "root") {
356        private WeakReference<TreeItem<T>> weakOldItem;
357
358        @Override protected void invalidated() {
359            TreeItem<T> oldTreeItem = weakOldItem == null ? null : weakOldItem.get();
360            if (oldTreeItem != null && weakRootEventListener != null) {
361                oldTreeItem.removeEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootEventListener);
362            }
363
364            TreeItem<T> root = getRoot();
365            if (root != null) {
366                weakRootEventListener = new WeakEventHandler(rootEvent);
367                getRoot().addEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootEventListener);
368                weakOldItem = new WeakReference<TreeItem<T>>(root);
369            }
370
371            expandedItemCountDirty = true;
372            updateRootExpanded();
373        }
374    };
375    
376    /**
377     * Sets the root node in this TreeView. See the {@link TreeItem} class level
378     * documentation for more details.
379     * 
380     * @param value The {@link TreeItem} that will be placed at the root of the
381     *      TreeView.
382     */
383    public final void setRoot(TreeItem<T> value) {
384        rootProperty().set(value);
385    }
386
387    /**
388     * Returns the current root node of this TreeView, or null if no root node
389     * is specified.
390     * @return The current root node, or null if no root node exists.
391     */
392    public final TreeItem<T> getRoot() {
393        return root == null ? null : root.get();
394    }
395
396    /**
397     * Property representing the root node of the TreeView.
398     */
399    public final ObjectProperty<TreeItem<T>> rootProperty() {
400        return root;
401    }
402
403    
404    
405    // --- Show Root
406    private BooleanProperty showRoot;
407    
408    /**
409     * Specifies whether the root {@code TreeItem} should be shown within this 
410     * TreeView.
411     * 
412     * @param value If true, the root TreeItem will be shown, and if false it
413     *      will be hidden.
414     */
415    public final void setShowRoot(boolean value) {
416        showRootProperty().set(value);
417    }
418
419    /**
420     * Returns true if the root of the TreeView should be shown, and false if
421     * it should not. By default, the root TreeItem is visible in the TreeView.
422     */
423    public final boolean isShowRoot() {
424        return showRoot == null ? true : showRoot.get();
425    }
426
427    /**
428     * Property that represents whether or not the TreeView root node is visible.
429     */
430    public final BooleanProperty showRootProperty() {
431        if (showRoot == null) {
432            showRoot = new SimpleBooleanProperty(this, "showRoot", true) {
433                @Override protected void invalidated() {
434                    updateRootExpanded();
435                    updateExpandedItemCount(getRoot());
436                }
437            };
438        }
439        return showRoot;
440    }
441    
442    
443    // --- Selection Model
444    private ObjectProperty<MultipleSelectionModel<TreeItem<T>>> selectionModel;
445
446    /**
447     * Sets the {@link MultipleSelectionModel} to be used in the TreeView. 
448     * Despite a TreeView requiring a <code><b>Multiple</b>SelectionModel</code>,
449     * it is possible to configure it to only allow single selection (see 
450     * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
451     * for more information).
452     */
453    public final void setSelectionModel(MultipleSelectionModel<TreeItem<T>> value) {
454        selectionModelProperty().set(value);
455    }
456
457    /**
458     * Returns the currently installed selection model.
459     */
460    public final MultipleSelectionModel<TreeItem<T>> getSelectionModel() {
461        return selectionModel == null ? null : selectionModel.get();
462    }
463
464    /**
465     * The SelectionModel provides the API through which it is possible
466     * to select single or multiple items within a TreeView, as  well as inspect
467     * which rows have been selected by the user. Note that it has a generic
468     * type that must match the type of the TreeView itself.
469     */
470    public final ObjectProperty<MultipleSelectionModel<TreeItem<T>>> selectionModelProperty() {
471        if (selectionModel == null) {
472            selectionModel = new SimpleObjectProperty<MultipleSelectionModel<TreeItem<T>>>(this, "selectionModel");
473        }
474        return selectionModel;
475    }
476    
477    
478    // --- Focus Model
479    private ObjectProperty<FocusModel<TreeItem<T>>> focusModel;
480
481    /**
482     * Sets the {@link FocusModel} to be used in the TreeView. 
483     */
484    public final void setFocusModel(FocusModel<TreeItem<T>> value) {
485        focusModelProperty().set(value);
486    }
487
488    /**
489     * Returns the currently installed {@link FocusModel}.
490     */
491    public final FocusModel<TreeItem<T>> getFocusModel() {
492        return focusModel == null ? null : focusModel.get();
493    }
494
495    /**
496     * The FocusModel provides the API through which it is possible
497     * to control focus on zero or one rows of the TreeView. Generally the
498     * default implementation should be more than sufficient.
499     */
500    public final ObjectProperty<FocusModel<TreeItem<T>>> focusModelProperty() {
501        if (focusModel == null) {
502            focusModel = new SimpleObjectProperty<FocusModel<TreeItem<T>>>(this, "focusModel");
503        }
504        return focusModel;
505    }
506    
507    
508    // --- Expanded node count
509    /**
510     * <p>Represents the number of tree nodes presently able to be visible in the
511     * TreeView. This is essentially the count of all expanded tree items, and
512     * their children.
513     *
514     * <p>For example, if just the root node is visible, the expandedItemCount will
515     * be one. If the root had three children and the root was expanded, the value
516     * will be four.
517     */
518    private ReadOnlyIntegerWrapper expandedItemCount = new ReadOnlyIntegerWrapper(this, "expandedItemCount", 0);
519    public final ReadOnlyIntegerProperty expandedItemCountProperty() {
520        return expandedItemCount.getReadOnlyProperty();
521    }
522    private void setExpandedItemCount(int value) {
523        expandedItemCount.set(value);
524    }
525    public final int getExpandedItemCount() {
526        if (expandedItemCountDirty) {
527            updateExpandedItemCount(getRoot());
528        }
529        return expandedItemCount.get();
530    }
531
532
533    // --- Fixed cell size
534    private DoubleProperty fixedCellSize;
535
536    /**
537     * Sets the new fixed cell size for this control. Any value greater than
538     * zero will enable fixed cell size mode, whereas a zero or negative value
539     * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
540     * mode.
541     *
542     * @param value The new fixed cell size value, or -1 (or Region.USE_COMPUTED_SIZE)
543     *                  to disable.
544     */
545    public final void setFixedCellSize(double value) {
546        fixedCellSizeProperty().set(value);
547    }
548
549    /**
550     * Returns the fixed cell size value, which may be -1 to represent fixed cell
551     * size mode is disabled, or a value greater than zero to represent the size
552     * of all cells in this control.
553     *
554     * @return A double representing the fixed cell size of this control, or -1
555     *      if fixed cell size mode is disabled.
556     */
557    public final double getFixedCellSize() {
558        return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
559    }
560    /**
561     * Specifies whether this control has cells that are a fixed height (of the
562     * specified value). If this value is -1 (i.e. {@link Region#USE_COMPUTED_SIZE}),
563     * then all cells are individually sized and positioned. This is a slow
564     * operation. Therefore, when performance matters and developers are not
565     * dependent on variable cell sizes it is a good idea to set the fixed cell
566     * size value. Generally cells are around 24px, so setting a fixed cell size
567     * of 24 is likely to result in very little difference in visuals, but a
568     * improvement to performance.
569     *
570     * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
571     * This should not be confused with the -fx-cell-size property. The difference
572     * between these two CSS properties is that -fx-cell-size will size all
573     * cells to the specified size, but it will not enforce that this is the
574     * only size (thus allowing for variable cell sizes, and preventing the
575     * performance gains from being possible). Therefore, when performance matters
576     * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
577     * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
578     */
579    public final DoubleProperty fixedCellSizeProperty() {
580        if (fixedCellSize == null) {
581            fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
582                @Override public CssMetaData<TreeView<?>,Number> getCssMetaData() {
583                    return StyleableProperties.FIXED_CELL_SIZE;
584                }
585
586                @Override public Object getBean() {
587                    return TreeView.this;
588                }
589
590                @Override public String getName() {
591                    return "fixedCellSize";
592                }
593            };
594        }
595        return fixedCellSize;
596    }
597
598    
599    // --- Editable
600    private BooleanProperty editable;
601    public final void setEditable(boolean value) {
602        editableProperty().set(value);
603    }
604    public final boolean isEditable() {
605        return editable == null ? false : editable.get();
606    }
607    /**
608     * Specifies whether this TreeView is editable - only if the TreeView and
609     * the TreeCells within it are both editable will a TreeCell be able to go
610     * into their editing state.
611     */
612    public final BooleanProperty editableProperty() {
613        if (editable == null) {
614            editable = new SimpleBooleanProperty(this, "editable", false);
615        }
616        return editable;
617    }
618    
619    
620    // --- Editing Item
621    private ReadOnlyObjectWrapper<TreeItem<T>> editingItem;
622
623    private void setEditingItem(TreeItem<T> value) {
624        editingItemPropertyImpl().set(value);
625    }
626
627    /**
628     * Returns the TreeItem that is currently being edited in the TreeView,
629     * or null if no item is being edited.
630     */
631    public final TreeItem<T> getEditingItem() {
632        return editingItem == null ? null : editingItem.get();
633    }
634
635    /**
636     * <p>A property used to represent the TreeItem currently being edited
637     * in the TreeView, if editing is taking place, or -1 if no item is being edited.
638     * 
639     * <p>It is not possible to set the editing item, instead it is required that
640     * you call {@link #edit(javafx.scene.control.TreeItem)}.
641     */
642    public final ReadOnlyObjectProperty<TreeItem<T>> editingItemProperty() {
643        return editingItemPropertyImpl().getReadOnlyProperty();
644    }
645
646    private ReadOnlyObjectWrapper<TreeItem<T>> editingItemPropertyImpl() {
647        if (editingItem == null) {
648            editingItem = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "editingItem");
649        }
650        return editingItem;
651    }
652    
653    
654    // --- On Edit Start
655    private ObjectProperty<EventHandler<EditEvent<T>>> onEditStart;
656
657    /**
658     * Sets the {@link EventHandler} that will be called when the user begins
659     * an edit. 
660     */
661    public final void setOnEditStart(EventHandler<EditEvent<T>> value) {
662        onEditStartProperty().set(value);
663    }
664
665    /**
666     * Returns the {@link EventHandler} that will be called when the user begins
667     * an edit.
668     */
669    public final EventHandler<EditEvent<T>> getOnEditStart() {
670        return onEditStart == null ? null : onEditStart.get();
671    }
672
673    /**
674     * This event handler will be fired when the user successfully initiates
675     * editing.
676     */
677    public final ObjectProperty<EventHandler<EditEvent<T>>> onEditStartProperty() {
678        if (onEditStart == null) {
679            onEditStart = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditStart") {
680                @Override protected void invalidated() {
681                    setEventHandler(TreeView.<T>editStartEvent(), get());
682                }
683            };
684        }
685        return onEditStart;
686    }
687
688
689    // --- On Edit Commit
690    private ObjectProperty<EventHandler<EditEvent<T>>> onEditCommit;
691
692    /**
693     * Sets the {@link EventHandler} that will be called when the user commits
694     * an edit. 
695     */
696    public final void setOnEditCommit(EventHandler<EditEvent<T>> value) {
697        onEditCommitProperty().set(value);
698    }
699
700    /**
701     * Returns the {@link EventHandler} that will be called when the user commits
702     * an edit.
703     */
704    public final EventHandler<EditEvent<T>> getOnEditCommit() {
705        return onEditCommit == null ? null : onEditCommit.get();
706    }
707
708    /**
709     * <p>This property is used when the user performs an action that should
710     * result in their editing input being persisted.</p>
711     *
712     * <p>The EventHandler in this property should not be called directly - 
713     * instead call {@link TreeCell#commitEdit(java.lang.Object)} from within
714     * your custom TreeCell. This will handle firing this event, updating the 
715     * view, and switching out of the editing state.</p>
716     */
717    public final ObjectProperty<EventHandler<EditEvent<T>>> onEditCommitProperty() {
718        if (onEditCommit == null) {
719            onEditCommit = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditCommit") {
720                @Override protected void invalidated() {
721                    setEventHandler(TreeView.<T>editCommitEvent(), get());
722                }
723            };
724        }
725        return onEditCommit;
726    }
727
728
729    // --- On Edit Cancel
730    private ObjectProperty<EventHandler<EditEvent<T>>> onEditCancel;
731
732    /**
733     * Sets the {@link EventHandler} that will be called when the user cancels
734     * an edit.
735     */
736    public final void setOnEditCancel(EventHandler<EditEvent<T>> value) {
737        onEditCancelProperty().set(value);
738    }
739
740    /**
741     * Returns the {@link EventHandler} that will be called when the user cancels
742     * an edit.
743     */
744    public final EventHandler<EditEvent<T>> getOnEditCancel() {
745        return onEditCancel == null ? null : onEditCancel.get();
746    }
747
748    /**
749     * This event handler will be fired when the user cancels editing a cell.
750     */
751    public final ObjectProperty<EventHandler<EditEvent<T>>> onEditCancelProperty() {
752        if (onEditCancel == null) {
753            onEditCancel = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditCancel") {
754                @Override protected void invalidated() {
755                    setEventHandler(TreeView.<T>editCancelEvent(), get());
756                }
757            };
758        }
759        return onEditCancel;
760    }
761
762
763    
764    /***************************************************************************
765     *                                                                         *
766     * Public API                                                              *
767     *                                                                         *
768     **************************************************************************/
769    
770    
771    /** {@inheritDoc} */
772    @Override protected void layoutChildren() {
773        if (expandedItemCountDirty) {
774            updateExpandedItemCount(getRoot());
775        }
776        
777        super.layoutChildren();
778    }
779    
780    
781    /**
782     * Instructs the TreeView to begin editing the given TreeItem, if 
783     * the TreeView is {@link #editableProperty() editable}. Once
784     * this method is called, if the current 
785     * {@link #cellFactoryProperty() cell factory} is set up to support editing,
786     * the Cell will switch its visual state to enable the user input to take place.
787     * 
788     * @param item The TreeItem in the TreeView that should be edited.
789     */
790    public void edit(TreeItem<T> item) {
791        if (!isEditable()) return;
792        setEditingItem(item);
793    }
794    
795
796    /**
797     * Scrolls the TreeView such that the item in the given index is visible to
798     * the end user.
799     * 
800     * @param index The index that should be made visible to the user, assuming
801     *      of course that it is greater than, or equal to 0, and less than the
802     *      number of the visible items in the TreeView.
803     */
804    public void scrollTo(int index) {
805        ControlUtils.scrollToIndex(this, index);
806    }
807
808    /**
809     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
810     */
811    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
812    
813    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
814        onScrollToProperty().set(value);
815    }
816    
817    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
818        if( onScrollTo != null ) {
819            return onScrollTo.get();
820        }
821        return null;
822    }
823    
824    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
825        if( onScrollTo == null ) {
826            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
827                @Override
828                protected void invalidated() {
829                    setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
830                }
831                @Override
832                public Object getBean() {
833                    return TreeView.this;
834                }
835
836                @Override
837                public String getName() {
838                    return "onScrollTo";
839                }
840            };
841        }
842        return onScrollTo;
843    }
844    
845    /**
846     * Returns the index position of the given TreeItem, taking into account the
847     * current state of each TreeItem (i.e. whether or not it is expanded).
848     * 
849     * @param item The TreeItem for which the index is sought.
850     * @return An integer representing the location in the current TreeView of the
851     *      first instance of the given TreeItem, or -1 if it is null or can not 
852     *      be found.
853     */
854    public int getRow(TreeItem<T> item) {
855        return TreeUtil.getRow(item, getRoot(), expandedItemCountDirty, isShowRoot());
856    }
857
858    /**
859     * Returns the TreeItem in the given index, or null if it is out of bounds.
860     * 
861     * @param row The index of the TreeItem being sought.
862     * @return The TreeItem in the given index, or null if it is out of bounds.
863     */
864    public TreeItem<T> getTreeItem(int row) {
865        // normalize the requested row based on whether showRoot is set
866        int r = isShowRoot() ? row : (row + 1);
867        return TreeUtil.getItem(getRoot(), r, expandedItemCountDirty);
868    }
869
870    /** {@inheritDoc} */
871    @Override protected Skin<?> createDefaultSkin() {
872        return new TreeViewSkin(this);
873    }
874    
875    /***************************************************************************
876     *                                                                         *
877     * Private Implementation                                                  *
878     *                                                                         *
879     **************************************************************************/  
880    
881    private void updateExpandedItemCount(TreeItem treeItem) {
882        setExpandedItemCount(TreeUtil.updateExpandedItemCount(treeItem, expandedItemCountDirty, isShowRoot()));
883        expandedItemCountDirty = false;
884    }
885
886    private void updateRootExpanded() {
887        // if we aren't showing the root, and the root isn't expanded, we expand
888        // it now so that something is shown.
889        if (!isShowRoot() && getRoot() != null && ! getRoot().isExpanded()) {
890            getRoot().setExpanded(true);
891        }
892    }
893
894
895    
896    /***************************************************************************
897     *                                                                         *
898     * Stylesheet Handling                                                     *
899     *                                                                         *
900     **************************************************************************/
901
902    private static final String DEFAULT_STYLE_CLASS = "table-view";
903
904    /** @treatAsPrivate */
905    private static class StyleableProperties {
906        private static final CssMetaData<TreeView<?>,Number> FIXED_CELL_SIZE =
907                new CssMetaData<TreeView<?>,Number>("-fx-fixed-cell-size",
908                                                     SizeConverter.getInstance(),
909                                                     Region.USE_COMPUTED_SIZE) {
910
911                    @Override public Double getInitialValue(TreeView node) {
912                        return node.getFixedCellSize();
913                    }
914
915                    @Override public boolean isSettable(TreeView n) {
916                        return n.fixedCellSize == null || !n.fixedCellSize.isBound();
917                    }
918
919                    @Override public StyleableProperty<Number> getStyleableProperty(TreeView n) {
920                        return (StyleableProperty<Number>) n.fixedCellSizeProperty();
921                    }
922                };
923
924        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
925        static {
926            final List<CssMetaData<? extends Styleable, ?>> styleables =
927                    new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
928            styleables.add(FIXED_CELL_SIZE);
929            STYLEABLES = Collections.unmodifiableList(styleables);
930        }
931    }
932
933    /**
934     * @return The CssMetaData associated with this class, which may include the
935     * CssMetaData of its super classes.
936     */
937    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
938        return StyleableProperties.STYLEABLES;
939    }
940
941    /**
942     * {@inheritDoc}
943     */
944    @Override
945    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
946        return getClassCssMetaData();
947    }
948
949
950
951    /***************************************************************************
952     *                                                                         *
953     * Support Interfaces                                                      *
954     *                                                                         *
955     **************************************************************************/
956
957
958
959    /***************************************************************************
960     *                                                                         *
961     * Support Classes                                                         *
962     *                                                                         *
963     **************************************************************************/
964
965
966    /**
967     * An {@link Event} subclass used specifically in TreeView for representing
968     * edit-related events. It provides additional API to easily access the 
969     * TreeItem that the edit event took place on, as well as the input provided
970     * by the end user.
971     * 
972     * @param <T> The type of the input, which is the same type as the TreeView 
973     *      itself.
974     */
975    public static class EditEvent<T> extends Event {
976        private static final long serialVersionUID = -4437033058917528976L;
977
978        /**
979         * Common supertype for all edit event types.
980         */
981        public static final EventType<?> ANY = EDIT_ANY_EVENT;
982
983        private final T oldValue;
984        private final T newValue;
985        private transient final TreeItem<T> treeItem;
986        
987        /**
988         * Creates a new EditEvent instance to represent an edit event. This 
989         * event is used for {@link #EDIT_START_EVENT}, 
990         * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
991         */
992        public EditEvent(TreeView<T> source,
993                         EventType<? extends EditEvent> eventType,
994                         TreeItem<T> treeItem, T oldValue, T newValue) {
995            super(source, Event.NULL_SOURCE_TARGET, eventType);
996            this.oldValue = oldValue;
997            this.newValue = newValue;
998            this.treeItem = treeItem;
999        }
1000
1001        /**
1002         * Returns the TreeView upon which the edit took place.
1003         */
1004        @Override public TreeView<T> getSource() {
1005            return (TreeView) super.getSource();
1006        }
1007
1008        /**
1009         * Returns the {@link TreeItem} upon which the edit took place.
1010         */
1011        public TreeItem<T> getTreeItem() {
1012            return treeItem;
1013        }
1014        
1015        /**
1016         * Returns the new value input into the TreeItem by the end user.
1017         */
1018        public T getNewValue() {
1019            return newValue;
1020        }
1021        
1022        /**
1023         * Returns the old value that existed in the TreeItem prior to the current
1024         * edit event.
1025         */
1026        public T getOldValue() {
1027            return oldValue;
1028        }
1029    }
1030    
1031    
1032    
1033    
1034
1035
1036
1037    // package for testing
1038    static class TreeViewBitSetSelectionModel<T> extends MultipleSelectionModelBase<TreeItem<T>> {
1039
1040        /***********************************************************************
1041         *                                                                     *
1042         * Constructors                                                        *
1043         *                                                                     *
1044         **********************************************************************/
1045
1046        public TreeViewBitSetSelectionModel(final TreeView<T> treeView) {
1047            if (treeView == null) {
1048                throw new IllegalArgumentException("TreeView can not be null");
1049            }
1050
1051            this.treeView = treeView;
1052            this.treeView.rootProperty().addListener(weakRootPropertyListener);
1053                    
1054            updateTreeEventListener(null, treeView.getRoot());
1055        }
1056        
1057        private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
1058            if (oldRoot != null && weakTreeItemListener != null) {
1059                oldRoot.removeEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1060            }
1061            
1062            if (newRoot != null) {
1063                weakTreeItemListener = new WeakEventHandler(treeItemListener);
1064                newRoot.addEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1065            }
1066        }
1067        
1068        private ChangeListener rootPropertyListener = new ChangeListener<TreeItem<T>>() {
1069            @Override public void changed(ObservableValue<? extends TreeItem<T>> observable, 
1070                    TreeItem<T> oldValue, TreeItem<T> newValue) {
1071                clearSelection();
1072                updateTreeEventListener(oldValue, newValue);
1073            }
1074        };
1075        
1076        private EventHandler<TreeModificationEvent<T>> treeItemListener = new EventHandler<TreeModificationEvent<T>>() {
1077            @Override public void handle(TreeModificationEvent<T> e) {
1078                
1079                if (getSelectedIndex() == -1 && getSelectedItem() == null) return;
1080                
1081                final TreeItem<T> treeItem = e.getTreeItem();
1082                if (treeItem == null) return;
1083                
1084                // we only shift selection from this row - everything before it
1085                // is safe. We might change this below based on certain criteria
1086                int startRow = treeView.getRow(treeItem);
1087                
1088                int shift = 0;
1089                if (e.wasExpanded()) {
1090                    // need to shuffle selection by the number of visible children
1091                    shift = treeItem.getExpandedDescendentCount(false) - 1;
1092                    startRow++;
1093                } else if (e.wasCollapsed()) {
1094                    // remove selection from any child treeItem
1095                    treeItem.getExpandedDescendentCount(false);
1096                    int count = treeItem.previousExpandedDescendentCount;
1097                    boolean wasAnyChildSelected = false;
1098                    for (int i = startRow; i < startRow + count; i++) {
1099                        if (isSelected(i)) {
1100                            wasAnyChildSelected = true;
1101                            break;
1102                        }
1103                    }
1104
1105                    // put selection onto the newly-collapsed tree item
1106                    if (wasAnyChildSelected) {
1107                        select(startRow);
1108                    }
1109
1110                    shift = - count + 1;
1111                    startRow++;
1112                } else if (e.wasAdded()) {
1113                    // shuffle selection by the number of added items
1114                    shift = treeItem.isExpanded() ? e.getAddedSize() : 0;
1115                } else if (e.wasRemoved()) {
1116                    // shuffle selection by the number of removed items
1117                    shift = treeItem.isExpanded() ? -e.getRemovedSize() : 0;
1118                    
1119                    // whilst we are here, we should check if the removed items
1120                    // are part of the selectedItems list - and remove them
1121                    // from selection if they are (as per RT-15446)
1122                    final List<Integer> selectedIndices = getSelectedIndices();
1123                    final int selectedIndex = getSelectedIndex();
1124                    final List<TreeItem<T>> selectedItems = getSelectedItems();
1125                    final TreeItem<T> selectedItem = getSelectedItem();
1126                    final List<? extends TreeItem<T>> removedChildren = e.getRemovedChildren();
1127                    
1128                    for (int i = 0; i < selectedIndices.size() && ! selectedItems.isEmpty(); i++) {
1129                        int index = selectedIndices.get(i);
1130                        if (index > selectedItems.size()) break;
1131                        
1132                        TreeItem<T> item = selectedItems.get(index);
1133                        if (item == null || removedChildren.contains(item)) {
1134                            clearSelection(index);
1135                        } else if (removedChildren.size() == 1 && 
1136                                selectedItems.size() == 1 && 
1137                                selectedItem != null && 
1138                                selectedItem.equals(removedChildren.get(0))) {
1139                            // Bug fix for RT-28637
1140                            if (selectedIndex < getItemCount()) {
1141                                TreeItem<T> newSelectedItem = getModelItem(selectedIndex);
1142                                if (! selectedItem.equals(newSelectedItem)) {
1143                                    setSelectedItem(newSelectedItem);
1144                                }
1145                            }
1146                        }
1147                    }
1148                }
1149                
1150                treeView.expandedItemCountDirty = true;
1151                shiftSelection(startRow, shift, null);
1152            }
1153        };
1154        
1155        private WeakChangeListener weakRootPropertyListener =
1156                new WeakChangeListener(rootPropertyListener);
1157        
1158        private WeakEventHandler weakTreeItemListener;
1159
1160
1161        /***********************************************************************
1162         *                                                                     *
1163         * Internal properties                                                 *
1164         *                                                                     *
1165         **********************************************************************/
1166
1167        private final TreeView<T> treeView;
1168
1169
1170        
1171        /***********************************************************************
1172         *                                                                     *
1173         * Public selection API                                                *
1174         *                                                                     *
1175         **********************************************************************/
1176
1177        /** {@inheritDoc} */
1178        @Override public void select(TreeItem<T> obj) {
1179//        if (getRowCount() <= 0) return;
1180            
1181            if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
1182                clearSelection();
1183                return;
1184            }
1185            
1186            // we firstly expand the path down such that the given object is
1187            // visible. This fixes RT-14456, where selection was not happening
1188            // correctly on TreeItems that are not visible.
1189            TreeItem<?> item = obj;
1190            while (item != null) {
1191                item.setExpanded(true);
1192                item = item.getParent();
1193            }
1194            
1195            // Fix for RT-15419. We eagerly update the tree item count, such that
1196            // selection occurs on the row
1197            treeView.updateExpandedItemCount(treeView.getRoot());
1198            
1199            // We have no option but to iterate through the model and select the
1200            // first occurrence of the given object. Once we find the first one, we
1201            // don't proceed to select any others.
1202            int row = treeView.getRow(obj);
1203            
1204            if (row == -1) {
1205                // if we are here, we did not find the item in the entire data model.
1206                // Even still, we allow for this item to be set to the give object.
1207                // We expect that in concrete subclasses of this class we observe the
1208                // data model such that we check to see if the given item exists in it,
1209                // whilst SelectedIndex == -1 && SelectedItem != null.
1210                setSelectedItem(obj);
1211            } else {
1212                select(row);
1213            }
1214        }
1215
1216
1217
1218        /***********************************************************************
1219         *                                                                     *
1220         * Support code                                                        *
1221         *                                                                     *
1222         **********************************************************************/
1223        
1224        /** {@inheritDoc} */
1225        @Override protected void focus(int itemIndex) {
1226            if (treeView.getFocusModel() != null) {
1227                treeView.getFocusModel().focus(itemIndex);
1228            }
1229        }
1230
1231        /** {@inheritDoc} */
1232        @Override protected int getFocusedIndex() {
1233            if (treeView.getFocusModel() == null) return -1;
1234            return treeView.getFocusModel().getFocusedIndex();
1235        }
1236        
1237        /** {@inheritDoc} */
1238        @Override protected int getItemCount() {
1239            return treeView == null ? 0 : treeView.getExpandedItemCount();
1240        }
1241
1242        /** {@inheritDoc} */
1243        @Override public TreeItem<T> getModelItem(int index) {
1244            if (treeView == null) return null;
1245
1246            if (index < 0 || index >= treeView.getExpandedItemCount()) return null;
1247
1248            return treeView.getTreeItem(index);
1249        }
1250    }
1251    
1252    
1253
1254    /**
1255     * 
1256     * @param <T> 
1257     */
1258    static class TreeViewFocusModel<T> extends FocusModel<TreeItem<T>> {
1259
1260        private final TreeView<T> treeView;
1261
1262        public TreeViewFocusModel(final TreeView<T> treeView) {
1263            this.treeView = treeView;
1264            this.treeView.rootProperty().addListener(weakRootPropertyListener);
1265            updateTreeEventListener(null, treeView.getRoot());
1266        }
1267        
1268        private final ChangeListener rootPropertyListener = new ChangeListener<TreeItem<T>>() {
1269            @Override
1270            public void changed(ObservableValue<? extends TreeItem<T>> observable, TreeItem<T> oldValue, TreeItem<T> newValue) {
1271                updateTreeEventListener(oldValue, newValue);
1272            }
1273        };
1274                
1275        private final WeakChangeListener weakRootPropertyListener =
1276                new WeakChangeListener(rootPropertyListener);
1277        
1278        private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
1279            if (oldRoot != null && weakTreeItemListener != null) {
1280                oldRoot.removeEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1281            }
1282            
1283            if (newRoot != null) {
1284                weakTreeItemListener = new WeakEventHandler(treeItemListener);
1285                newRoot.addEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1286            }
1287        }
1288        
1289        private EventHandler<TreeModificationEvent<T>> treeItemListener = new EventHandler<TreeModificationEvent<T>>() {
1290            @Override public void handle(TreeModificationEvent<T> e) {
1291                // don't shift focus if the event occurred on a tree item after
1292                // the focused row, or if there is no focus index at present
1293                if (getFocusedIndex() == -1) return;
1294                
1295                int row = treeView.getRow(e.getTreeItem());
1296                int shift = 0;
1297                if (e.wasExpanded()) {
1298                    if (row < getFocusedIndex()) {
1299                        // need to shuffle selection by the number of visible children
1300                        shift = e.getTreeItem().getExpandedDescendentCount(false) - 1;
1301                    }
1302                } else if (e.wasCollapsed()) {
1303                    if (row < getFocusedIndex()) {
1304                        // need to shuffle selection by the number of visible children
1305                        // that were just hidden
1306                        shift = - e.getTreeItem().previousExpandedDescendentCount + 1;
1307                    }
1308                } else if (e.wasAdded()) {
1309                    for (int i = 0; i < e.getAddedChildren().size(); i++) {
1310                        TreeItem item = e.getAddedChildren().get(i);
1311                        row = treeView.getRow(item);
1312                        
1313                        if (item != null && row <= getFocusedIndex()) {
1314//                            shift = e.getTreeItem().isExpanded() ? e.getAddedSize() : 0;
1315                            shift += item.getExpandedDescendentCount(false);
1316                        }
1317                    }
1318                } else if (e.wasRemoved()) {
1319                    for (int i = 0; i < e.getRemovedChildren().size(); i++) {
1320                        TreeItem item = e.getRemovedChildren().get(i);
1321                        if (item != null && item.equals(getFocusedItem())) {
1322                            focus(-1);
1323                            return;
1324                        }
1325                    }
1326                    
1327                    if (row <= getFocusedIndex()) {
1328                        // shuffle selection by the number of removed items
1329                        shift = e.getTreeItem().isExpanded() ? -e.getRemovedSize() : 0;
1330                    }
1331                }
1332                
1333                if(shift != 0) {
1334                    final int newFocus = getFocusedIndex() + shift;
1335                    Platform.runLater(new Runnable() {
1336                        @Override public void run() {
1337                            focus(newFocus);
1338                        }
1339                    });
1340                } 
1341            }
1342        };
1343        
1344        private WeakEventHandler weakTreeItemListener;
1345
1346        @Override protected int getItemCount() {
1347            return treeView == null ? -1 : treeView.getExpandedItemCount();
1348        }
1349
1350        @Override protected TreeItem<T> getModelItem(int index) {
1351            if (treeView == null) return null;
1352
1353            if (index < 0 || index >= treeView.getExpandedItemCount()) return null;
1354
1355            return treeView.getTreeItem(index);
1356        }
1357
1358        /** {@inheritDoc} */
1359        @Override public void focus(int index) {
1360            if (treeView.expandedItemCountDirty) {
1361                treeView.updateExpandedItemCount(treeView.getRoot());
1362            }
1363            
1364            super.focus(index);
1365        }
1366        
1367        
1368    }
1369}