1 /*
   2  * Copyright (c) 2011, 2017, 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.css.PseudoClass;
  29 import javafx.beans.InvalidationListener;
  30 import javafx.beans.Observable;
  31 import javafx.beans.WeakInvalidationListener;
  32 import javafx.beans.property.ObjectProperty;
  33 import javafx.beans.property.SimpleObjectProperty;
  34 import javafx.collections.ListChangeListener;
  35 import javafx.scene.AccessibleAction;
  36 import javafx.scene.AccessibleAttribute;
  37 import javafx.scene.AccessibleRole;
  38 import javafx.scene.Node;
  39 import javafx.scene.control.skin.TreeCellSkin;
  40 import javafx.collections.WeakListChangeListener;
  41 import java.lang.ref.WeakReference;
  42 
  43 import javafx.beans.property.BooleanProperty;
  44 import javafx.beans.property.ReadOnlyObjectProperty;
  45 import javafx.beans.property.ReadOnlyObjectWrapper;
  46 import javafx.beans.value.ChangeListener;
  47 import javafx.beans.value.ObservableValue;
  48 import javafx.beans.value.WeakChangeListener;
  49 
  50 /**
  51  * The {@link Cell} type used with the {@link TreeView} control. In addition to
  52  * the API defined on {@link IndexedCell}, the TreeCell
  53  * exposes additional states and pseudo classes for use by CSS.
  54  * <p>
  55  * A TreeCell watches the selection model of the TreeView for which it is
  56  * associated, ensuring that it visually indicates to the user whether it is
  57  * selected. When a TreeCell is selected, this is exposed both via the
  58  * {@link #selectedProperty() selected} property, as well as via the 'selected'
  59  * CSS pseudo class state.
  60  * <p>
  61  * Due to the fact that TreeCell extends from {@link IndexedCell}, each TreeCell
  62  * also provides an {@link #indexProperty() index} property. The index will be
  63  * updated as cells are expanded and collapsed, and therefore should be
  64  * considered a view index rather than a model index.
  65  * <p>
  66  * Finally, each TreeCell also has a reference back to the TreeView that it is
  67  * being used with. Each TreeCell belongs to one and only one TreeView.
  68  *
  69  * @see TreeView
  70  * @see TreeItem
  71  * @param <T> The type of the value contained within the
  72  *      {@link #treeItemProperty() TreeItem} property.
  73  * @since JavaFX 2.0
  74  */
  75 public class TreeCell<T> extends IndexedCell<T> {
  76 
  77     /***************************************************************************
  78      *                                                                         *
  79      * Constructors                                                            *
  80      *                                                                         *
  81      **************************************************************************/
  82 
  83     /**
  84      * Creates a default TreeCell instance.
  85      */
  86     public TreeCell() {
  87         getStyleClass().addAll(DEFAULT_STYLE_CLASS);
  88         setAccessibleRole(AccessibleRole.TREE_ITEM);
  89     }
  90 
  91 
  92 
  93     /***************************************************************************
  94      *                                                                         *
  95      * Callbacks and events                                                    *
  96      *                                                                         *
  97      **************************************************************************/
  98 
  99     private final ListChangeListener<Integer> selectedListener = c -> {
 100         updateSelection();
 101     };
 102 
 103     /**
 104      * Listens to the selectionModel property on the TreeView. Whenever the entire model is changed,
 105      * we have to unhook the weakSelectedListener and update the selection.
 106      */
 107     private final ChangeListener<MultipleSelectionModel<TreeItem<T>>> selectionModelPropertyListener = new ChangeListener<MultipleSelectionModel<TreeItem<T>>>() {
 108         @Override public void changed(ObservableValue<? extends MultipleSelectionModel<TreeItem<T>>> observable,
 109                                       MultipleSelectionModel<TreeItem<T>> oldValue,
 110                                       MultipleSelectionModel<TreeItem<T>> newValue) {
 111             if (oldValue != null) {
 112                 oldValue.getSelectedIndices().removeListener(weakSelectedListener);
 113             }
 114             if (newValue != null) {
 115                 newValue.getSelectedIndices().addListener(weakSelectedListener);
 116             }
 117             updateSelection();
 118         }
 119     };
 120 
 121     private final InvalidationListener focusedListener = valueModel -> {
 122         updateFocus();
 123     };
 124 
 125     /**
 126      * Listens to the focusModel property on the TreeView. Whenever the entire model is changed,
 127      * we have to unhook the weakFocusedListener and update the focus.
 128      */
 129     private final ChangeListener<FocusModel<TreeItem<T>>> focusModelPropertyListener = new ChangeListener<FocusModel<TreeItem<T>>>() {
 130         @Override public void changed(ObservableValue<? extends FocusModel<TreeItem<T>>> observable,
 131                                       FocusModel<TreeItem<T>> oldValue,
 132                                       FocusModel<TreeItem<T>> newValue) {
 133             if (oldValue != null) {
 134                 oldValue.focusedIndexProperty().removeListener(weakFocusedListener);
 135             }
 136             if (newValue != null) {
 137                 newValue.focusedIndexProperty().addListener(weakFocusedListener);
 138             }
 139             updateFocus();
 140         }
 141     };
 142 
 143     private final InvalidationListener editingListener = valueModel -> {
 144         updateEditing();
 145     };
 146 
 147     private final InvalidationListener leafListener = new InvalidationListener() {
 148         @Override public void invalidated(Observable valueModel) {
 149             // necessary to update the disclosure node in the skin when the
 150             // leaf property changes
 151             TreeItem<T> treeItem = getTreeItem();
 152             if (treeItem != null) {
 153                 requestLayout();
 154             }
 155         }
 156     };
 157 
 158     /* proxy pseudo-class state change from treeItem's expandedProperty */
 159     private boolean oldIsExpanded;
 160     private final InvalidationListener treeItemExpandedInvalidationListener = new InvalidationListener() {
 161         @Override public void invalidated(Observable o) {
 162             boolean isExpanded = ((BooleanProperty)o).get();
 163             pseudoClassStateChanged(EXPANDED_PSEUDOCLASS_STATE,   isExpanded);
 164             pseudoClassStateChanged(COLLAPSED_PSEUDOCLASS_STATE, !isExpanded);
 165             if (isExpanded != oldIsExpanded) {
 166                 notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
 167             }
 168             oldIsExpanded = isExpanded;
 169         }
 170     };
 171 
 172     private final InvalidationListener rootPropertyListener = observable -> {
 173         updateItem(-1);
 174     };
 175 
 176     private final WeakListChangeListener<Integer> weakSelectedListener = new WeakListChangeListener<Integer>(selectedListener);
 177     private final WeakChangeListener<MultipleSelectionModel<TreeItem<T>>> weakSelectionModelPropertyListener = new WeakChangeListener<MultipleSelectionModel<TreeItem<T>>>(selectionModelPropertyListener);
 178     private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener);
 179     private final WeakChangeListener<FocusModel<TreeItem<T>>> weakFocusModelPropertyListener = new WeakChangeListener<FocusModel<TreeItem<T>>>(focusModelPropertyListener);
 180     private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener);
 181     private final WeakInvalidationListener weakLeafListener = new WeakInvalidationListener(leafListener);
 182     private final WeakInvalidationListener weakTreeItemExpandedInvalidationListener =
 183             new WeakInvalidationListener(treeItemExpandedInvalidationListener);
 184     private final WeakInvalidationListener weakRootPropertyListener = new WeakInvalidationListener(rootPropertyListener);
 185 
 186 
 187 
 188 
 189     /***************************************************************************
 190      *                                                                         *
 191      * Properties                                                              *
 192      *                                                                         *
 193      **************************************************************************/
 194 
 195     // --- TreeItem
 196     private ReadOnlyObjectWrapper<TreeItem<T>> treeItem =
 197         new ReadOnlyObjectWrapper<TreeItem<T>>(this, "treeItem") {
 198 
 199             TreeItem<T> oldValue = null;
 200 
 201             @Override protected void invalidated() {
 202                 if (oldValue != null) {
 203                     oldValue.expandedProperty().removeListener(weakTreeItemExpandedInvalidationListener);
 204                 }
 205 
 206                 oldValue = get();
 207 
 208                 if (oldValue != null) {
 209                     oldIsExpanded = oldValue.isExpanded();
 210                     oldValue.expandedProperty().addListener(weakTreeItemExpandedInvalidationListener);
 211                     // fake an invalidation to ensure updated pseudo-class state
 212                     weakTreeItemExpandedInvalidationListener.invalidated(oldValue.expandedProperty());
 213                 }
 214 
 215             }
 216     };
 217     private void setTreeItem(TreeItem<T> value) {
 218         treeItem.set(value);
 219     }
 220 
 221     /**
 222      * Returns the TreeItem currently set in this TreeCell.
 223      * @return the TreeItem currently set in this TreeCell
 224      */
 225     public final TreeItem<T> getTreeItem() { return treeItem.get(); }
 226 
 227     /**
 228      * Each TreeCell represents at most a single {@link TreeItem}, which is
 229      * represented by this property.
 230      * @return the TreeItem property representing this TreeCell
 231      */
 232     public final ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty() { return treeItem.getReadOnlyProperty(); }
 233 
 234 
 235 
 236     // --- Disclosure Node
 237     private ObjectProperty<Node> disclosureNode = new SimpleObjectProperty<Node>(this, "disclosureNode");
 238 
 239     /**
 240      * The node to use as the "disclosure" triangle, or toggle, used for
 241      * expanding and collapsing items. This is only used in the case of
 242      * an item in the tree which contains child items. If not specified, the
 243      * TreeCell's Skin implementation is responsible for providing a default
 244      * disclosure node.
 245      * @param value the disclosure node
 246      */
 247     public final void setDisclosureNode(Node value) { disclosureNodeProperty().set(value); }
 248 
 249     /**
 250      * Returns the current disclosure node set in this TreeCell.
 251      * @return the current disclosure node set in this TreeCell
 252      */
 253     public final Node getDisclosureNode() { return disclosureNode.get(); }
 254 
 255     /**
 256      * The disclosure node is commonly seen represented as a triangle that rotates
 257      * on screen to indicate whether or not the TreeItem that it is placed
 258      * beside is expanded or collapsed.
 259      * @return the disclosure node
 260      */
 261     public final ObjectProperty<Node> disclosureNodeProperty() { return disclosureNode; }
 262 
 263 
 264     // --- TreeView
 265     private ReadOnlyObjectWrapper<TreeView<T>> treeView = new ReadOnlyObjectWrapper<TreeView<T>>() {
 266         private WeakReference<TreeView<T>> weakTreeViewRef;
 267         @Override protected void invalidated() {
 268             MultipleSelectionModel<TreeItem<T>> sm;
 269             FocusModel<TreeItem<T>> fm;
 270 
 271             if (weakTreeViewRef != null) {
 272                 TreeView<T> oldTreeView = weakTreeViewRef.get();
 273                 if (oldTreeView != null) {
 274                     // remove old listeners
 275                     sm = oldTreeView.getSelectionModel();
 276                     if (sm != null) {
 277                         sm.getSelectedIndices().removeListener(weakSelectedListener);
 278                     }
 279 
 280                     fm = oldTreeView.getFocusModel();
 281                     if (fm != null) {
 282                         fm.focusedIndexProperty().removeListener(weakFocusedListener);
 283                     }
 284 
 285                     oldTreeView.editingItemProperty().removeListener(weakEditingListener);
 286                     oldTreeView.focusModelProperty().removeListener(weakFocusModelPropertyListener);
 287                     oldTreeView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener);
 288                     oldTreeView.rootProperty().removeListener(weakRootPropertyListener);
 289                 }
 290 
 291                 weakTreeViewRef = null;
 292             }
 293 
 294             TreeView<T> treeView = get();
 295             if (treeView != null) {
 296                 sm = treeView.getSelectionModel();
 297                 if (sm != null) {
 298                     // listening for changes to treeView.selectedIndex and IndexedCell.index,
 299                     // to determine if this cell is selected
 300                     sm.getSelectedIndices().addListener(weakSelectedListener);
 301                 }
 302 
 303                 fm = treeView.getFocusModel();
 304                 if (fm != null) {
 305                     // similar to above, but this time for focus
 306                     fm.focusedIndexProperty().addListener(weakFocusedListener);
 307                 }
 308 
 309                 treeView.editingItemProperty().addListener(weakEditingListener);
 310                 treeView.focusModelProperty().addListener(weakFocusModelPropertyListener);
 311                 treeView.selectionModelProperty().addListener(weakSelectionModelPropertyListener);
 312                 treeView.rootProperty().addListener(weakRootPropertyListener);
 313 
 314                 weakTreeViewRef = new WeakReference<TreeView<T>>(treeView);
 315             }
 316 
 317             updateItem(-1);
 318             requestLayout();
 319         }
 320 
 321         @Override
 322         public Object getBean() {
 323             return TreeCell.this;
 324         }
 325 
 326         @Override
 327         public String getName() {
 328             return "treeView";
 329         }
 330     };
 331 
 332     private void setTreeView(TreeView<T> value) { treeView.set(value); }
 333 
 334     /**
 335      * Returns the TreeView associated with this TreeCell.
 336      * @return the TreeView associated with this TreeCell
 337      */
 338     public final TreeView<T> getTreeView() { return treeView.get(); }
 339 
 340     /**
 341      * A TreeCell is explicitly linked to a single {@link TreeView} instance,
 342      * which is represented by this property.
 343      * @return the TreeView property of this TreeCell
 344      */
 345     public final ReadOnlyObjectProperty<TreeView<T>> treeViewProperty() { return treeView.getReadOnlyProperty(); }
 346 
 347 
 348 
 349     /***************************************************************************
 350      *                                                                         *
 351      * Public API                                                              *
 352      *                                                                         *
 353      **************************************************************************/
 354 
 355     /** {@inheritDoc} */
 356     @Override public void startEdit() {
 357         if (isEditing()) return;
 358 
 359         final TreeView<T> tree = getTreeView();
 360         if (! isEditable() || (tree != null && ! tree.isEditable())) {
 361 //            if (Logging.getControlsLogger().isLoggable(PlatformLogger.SEVERE)) {
 362 //                Logging.getControlsLogger().severe(
 363 //                    "Can not call TreeCell.startEdit() on this TreeCell, as it "
 364 //                        + "is not allowed to enter its editing state (TreeCell: "
 365 //                        + this + ", TreeView: " + tree + ").");
 366 //            }
 367             return;
 368         }
 369 
 370         updateItem(-1);
 371 
 372         // it makes sense to get the cell into its editing state before firing
 373         // the event to the TreeView below, so that's what we're doing here
 374         // by calling super.startEdit().
 375         super.startEdit();
 376 
 377          // Inform the TreeView of the edit starting.
 378         if (tree != null) {
 379             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 380                     TreeView.<T>editStartEvent(),
 381                     getTreeItem(),
 382                     getItem(),
 383                     null));
 384 
 385             tree.requestFocus();
 386         }
 387     }
 388 
 389      /** {@inheritDoc} */
 390     @Override public void commitEdit(T newValue) {
 391         if (! isEditing()) return;
 392         final TreeItem<T> treeItem = getTreeItem();
 393         final TreeView<T> tree = getTreeView();
 394         if (tree != null) {
 395             // Inform the TreeView of the edit being ready to be committed.
 396             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 397                     TreeView.<T>editCommitEvent(),
 398                     treeItem,
 399                     getItem(),
 400                     newValue));
 401         }
 402 
 403         // inform parent classes of the commit, so that they can switch us
 404         // out of the editing state.
 405         // This MUST come before the updateItem call below, otherwise it will
 406         // call cancelEdit(), resulting in both commit and cancel events being
 407         // fired (as identified in RT-29650)
 408         super.commitEdit(newValue);
 409 
 410         // update the item within this cell, so that it represents the new value
 411         if (treeItem != null) {
 412             treeItem.setValue(newValue);
 413             updateTreeItem(treeItem);
 414             updateItem(newValue, false);
 415         }
 416 
 417         if (tree != null) {
 418             // reset the editing item in the TreetView
 419             tree.edit(null);
 420 
 421             // request focus back onto the tree, only if the current focus
 422             // owner has the tree as a parent (otherwise the user might have
 423             // clicked out of the tree entirely and given focus to something else.
 424             // It would be rude of us to request it back again.
 425             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 426         }
 427     }
 428 
 429     /** {@inheritDoc} */
 430     @Override public void cancelEdit() {
 431         if (! isEditing()) return;
 432 
 433         TreeView<T> tree = getTreeView();
 434 
 435         super.cancelEdit();
 436 
 437         if (tree != null) {
 438             // reset the editing index on the TreeView
 439             if (updateEditingIndex) tree.edit(null);
 440 
 441             // request focus back onto the tree, only if the current focus
 442             // owner has the tree as a parent (otherwise the user might have
 443             // clicked out of the tree entirely and given focus to something else.
 444             // It would be rude of us to request it back again.
 445             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 446 
 447             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 448                     TreeView.<T>editCancelEvent(),
 449                     getTreeItem(),
 450                     getItem(),
 451                     null));
 452         }
 453     }
 454 
 455     /** {@inheritDoc} */
 456     @Override protected Skin<?> createDefaultSkin() {
 457         return new TreeCellSkin<T>(this);
 458     }
 459 
 460     /***************************************************************************
 461      *                                                                         *
 462      * Private Implementation                                                  *
 463      *                                                                         *
 464      **************************************************************************/
 465 
 466     /** {@inheritDoc} */
 467     @Override void indexChanged(int oldIndex, int newIndex) {
 468         super.indexChanged(oldIndex, newIndex);
 469 
 470         // when the cell index changes, this may result in the cell
 471         // changing state to be selected and/or focused.
 472         if (isEditing() && newIndex == oldIndex) {
 473             // no-op
 474             // Fix for RT-31165 - if we (needlessly) update the index whilst the
 475             // cell is being edited it will no longer be in an editing state.
 476             // This means that in certain (common) circumstances that it will
 477             // appear that a cell is uneditable as, despite being clicked, it
 478             // will not change to the editing state as a layout of VirtualFlow
 479             // is immediately invoked, which forces all cells to be updated.
 480         } else {
 481             updateItem(oldIndex);
 482             updateSelection();
 483             updateFocus();
 484         }
 485     }
 486 
 487     private boolean isFirstRun = true;
 488     private void updateItem(int oldIndex) {
 489         TreeView<T> tv = getTreeView();
 490         if (tv == null) return;
 491 
 492         // Compute whether the index for this cell is for a real item
 493         int index = getIndex();
 494         boolean valid = index >=0 && index < tv.getExpandedItemCount();
 495         final boolean isEmpty = isEmpty();
 496         final TreeItem<T> oldTreeItem = getTreeItem();
 497 
 498         // Cause the cell to update itself
 499         outer: if (valid) {
 500             // update the TreeCell state.
 501             // get the new treeItem that is about to go in to the TreeCell
 502             TreeItem<T> newTreeItem = tv.getTreeItem(index);
 503             T newValue = newTreeItem == null ? null : newTreeItem.getValue();
 504             T oldValue = oldTreeItem == null ? null : oldTreeItem.getValue();
 505 
 506             // For the sake of RT-14279, it is important that the order of these
 507             // method calls is as shown below. If the order is switched, it is
 508             // likely that events will be fired where the item is null, even
 509             // though calling cell.getTreeItem().getValue() returns the value
 510             // as expected
 511 
 512             // RT-35864 - if the index didn't change, then avoid calling updateItem
 513             // unless the item has changed.
 514             if (oldIndex == index) {
 515                 if (!isItemChanged(oldValue, newValue)) {
 516                     // RT-37054:  we break out of the if/else code here and
 517                     // proceed with the code following this, so that we may
 518                     // still update references, listeners, etc as required.
 519                     break outer;
 520                 }
 521             }
 522             updateTreeItem(newTreeItem);
 523             updateItem(newValue, false);
 524         } else {
 525             // RT-30484 We need to allow a first run to be special-cased to allow
 526             // for the updateItem method to be called at least once to allow for
 527             // the correct visual state to be set up. In particular, in RT-30484
 528             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 529             // shown as the new cells are instantiated with the arrows in the
 530             // children list, and are only hidden in updateItem.
 531             if ((!isEmpty && oldTreeItem != null) || isFirstRun) {
 532                 updateTreeItem(null);
 533                 updateItem(null, true);
 534                 isFirstRun = false;
 535             }
 536         }
 537     }
 538 
 539     private void updateSelection() {
 540         if (isEmpty()) return;
 541         if (getIndex() == -1 || getTreeView() == null) return;
 542 
 543         SelectionModel<TreeItem<T>> sm = getTreeView().getSelectionModel();
 544         if (sm == null) {
 545             updateSelected(false);
 546             return;
 547         }
 548 
 549         boolean isSelected = sm.isSelected(getIndex());
 550         if (isSelected() == isSelected) return;
 551 
 552         updateSelected(isSelected);
 553     }
 554 
 555     private void updateFocus() {
 556         if (getIndex() == -1 || getTreeView() == null) return;
 557 
 558         FocusModel<TreeItem<T>> fm = getTreeView().getFocusModel();
 559         if (fm == null) {
 560             setFocused(false);
 561             return;
 562         }
 563 
 564         setFocused(fm.isFocused(getIndex()));
 565     }
 566 
 567     private boolean updateEditingIndex = true;
 568     private void updateEditing() {
 569         final int index = getIndex();
 570         final TreeView<T> tree = getTreeView();
 571         final TreeItem<T> treeItem = getTreeItem();
 572         final TreeItem<T> editItem = tree == null ? null : tree.getEditingItem();
 573         final boolean editing = isEditing();
 574 
 575         if (index == -1 || tree == null || treeItem == null) return;
 576 
 577         final boolean match = treeItem.equals(editItem);
 578 
 579         // If my tree item is the item being edited and I'm not currently in
 580         // the edit mode, then I need to enter the edit mode
 581         if (match && !editing) {
 582             startEdit();
 583         } else if (! match && editing) {
 584             // If my tree item is not the one being edited then I need to cancel
 585             // the edit. The tricky thing here is that as part of this call
 586             // I cannot end up calling tree.edit(null) the way that the standard
 587             // cancelEdit method would do. Yet, I need to call cancelEdit
 588             // so that subclasses which override cancelEdit can execute. So,
 589             // I have to use a kind of hacky flag workaround.
 590             updateEditingIndex = false;
 591             cancelEdit();
 592             updateEditingIndex = true;
 593         }
 594     }
 595 
 596 
 597 
 598     /***************************************************************************
 599      *                                                                         *
 600      * Expert API                                                              *
 601      *                                                                         *
 602      **************************************************************************/
 603 
 604 
 605 
 606     /**
 607      * Updates the TreeView associated with this TreeCell.
 608      *
 609      * @param tree The new TreeView that should be associated with this TreeCell.
 610      * Note: This function is intended to be used by experts, primarily
 611      *       by those implementing new Skins. It is not common
 612      *       for developers or designers to access this function directly.
 613      */
 614     public final void updateTreeView(TreeView<T> tree) {
 615         setTreeView(tree);
 616     }
 617 
 618     /**
 619      * Updates the TreeItem associated with this TreeCell.
 620      *
 621      * @param treeItem The new TreeItem that should be associated with this
 622      *      TreeCell.
 623      * Note: This function is intended to be used by experts, primarily
 624      *       by those implementing new Skins. It is not common
 625      *       for developers or designers to access this function directly.
 626      */
 627     public final void updateTreeItem(TreeItem<T> treeItem) {
 628         TreeItem<T> _treeItem = getTreeItem();
 629         if (_treeItem != null) {
 630             _treeItem.leafProperty().removeListener(weakLeafListener);
 631         }
 632         setTreeItem(treeItem);
 633         if (treeItem != null) {
 634             treeItem.leafProperty().addListener(weakLeafListener);
 635         }
 636     }
 637 
 638 
 639 
 640     /***************************************************************************
 641      *                                                                         *
 642      * Stylesheet Handling                                                     *
 643      *                                                                         *
 644      **************************************************************************/
 645 
 646     private static final String DEFAULT_STYLE_CLASS = "tree-cell";
 647 
 648     private static final PseudoClass EXPANDED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("expanded");
 649     private static final PseudoClass COLLAPSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("collapsed");
 650 
 651 
 652     /***************************************************************************
 653      *                                                                         *
 654      * Accessibility handling                                                  *
 655      *                                                                         *
 656      **************************************************************************/
 657 
 658     @Override
 659     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 660         TreeItem<T> treeItem = getTreeItem();
 661         TreeView<T> treeView = getTreeView();
 662         switch (attribute) {
 663             case TREE_ITEM_PARENT: {
 664                 if (treeView == null) return null;
 665                 if (treeItem == null) return null;
 666                 TreeItem<T> parent = treeItem.getParent();
 667                 if (parent == null) return null;
 668                 int parentIndex = treeView.getRow(parent);
 669                 return treeView.queryAccessibleAttribute(AccessibleAttribute.ROW_AT_INDEX, parentIndex);
 670             }
 671             case TREE_ITEM_COUNT: {
 672                 if (treeItem == null) return 0;
 673                 if (!treeItem.isExpanded()) return 0;
 674                 return treeItem.getChildren().size();
 675             }
 676             case TREE_ITEM_AT_INDEX: {
 677                 if (treeItem == null) return null;
 678                 if (!treeItem.isExpanded()) return null;
 679                 int index = (Integer)parameters[0];
 680                 if (index >= treeItem.getChildren().size()) return null;
 681                 TreeItem<T> child = treeItem.getChildren().get(index);
 682                 if (child == null) return null;
 683                 int childIndex = treeView.getRow(child);
 684                 return treeView.queryAccessibleAttribute(AccessibleAttribute.ROW_AT_INDEX, childIndex);
 685             }
 686             case LEAF: return treeItem == null ? true : treeItem.isLeaf();
 687             case EXPANDED: return treeItem == null ? false : treeItem.isExpanded();
 688             case INDEX: return getIndex();
 689             case SELECTED: return isSelected();
 690             case DISCLOSURE_LEVEL: {
 691                 return treeView == null ? 0 : treeView.getTreeItemLevel(treeItem);
 692             }
 693             default: return super.queryAccessibleAttribute(attribute, parameters);
 694         }
 695     }
 696 
 697     @Override
 698     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 699         switch (action) {
 700             case EXPAND: {
 701                 TreeItem<T> treeItem = getTreeItem();
 702                 if (treeItem != null) treeItem.setExpanded(true);
 703                 break;
 704             }
 705             case COLLAPSE: {
 706                 TreeItem<T> treeItem = getTreeItem();
 707                 if (treeItem != null) treeItem.setExpanded(false);
 708                 break;
 709             }
 710             case REQUEST_FOCUS: {
 711                 TreeView<T> treeView = getTreeView();
 712                 if (treeView != null) {
 713                     FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
 714                     if (fm != null) {
 715                         fm.focus(getIndex());
 716                     }
 717                 }
 718                 break;
 719             }
 720             default: super.executeAccessibleAction(action);
 721         }
 722     }
 723 }