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 
 395         // inform parent classes of the commit, so that they can switch us
 396         // out of the editing state.
 397         // This MUST come before the updateItem call below, otherwise it will
 398         // call cancelEdit(), resulting in both commit and cancel events being
 399         // fired (as identified in RT-29650)
 400         super.commitEdit(newValue);
 401 
 402         if (tree != null) {
 403             // Inform the TreeView of the edit being ready to be committed.
 404             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 405                     TreeView.<T>editCommitEvent(),
 406                     treeItem,
 407                     getItem(),
 408                     newValue));
 409         }
 410 
 411         // update the item within this cell, so that it represents the new value
 412         if (treeItem != null) {
 413             treeItem.setValue(newValue);
 414             updateTreeItem(treeItem);
 415             updateItem(newValue, false);
 416         }
 417 
 418         if (tree != null) {
 419             // reset the editing item in the TreetView
 420             tree.edit(null);
 421 
 422             // request focus back onto the tree, only if the current focus
 423             // owner has the tree as a parent (otherwise the user might have
 424             // clicked out of the tree entirely and given focus to something else.
 425             // It would be rude of us to request it back again.
 426             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 427         }
 428     }
 429 
 430     /** {@inheritDoc} */
 431     @Override public void cancelEdit() {
 432         if (! isEditing()) return;
 433 
 434         TreeView<T> tree = getTreeView();
 435 
 436         super.cancelEdit();
 437 
 438         if (tree != null) {
 439             // reset the editing index on the TreeView
 440             tree.edit(null);
 441 
 442             // request focus back onto the tree, only if the current focus
 443             // owner has the tree as a parent (otherwise the user might have
 444             // clicked out of the tree entirely and given focus to something else.
 445             // It would be rude of us to request it back again.
 446             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 447 
 448             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 449                     TreeView.<T>editCancelEvent(),
 450                     getTreeItem(),
 451                     getItem(),
 452                     null));
 453         }
 454     }
 455 
 456     /** {@inheritDoc} */
 457     @Override protected Skin<?> createDefaultSkin() {
 458         return new TreeCellSkin<T>(this);
 459     }
 460 
 461     /***************************************************************************
 462      *                                                                         *
 463      * Private Implementation                                                  *
 464      *                                                                         *
 465      **************************************************************************/
 466 
 467     /** {@inheritDoc} */
 468     @Override void indexChanged(int oldIndex, int newIndex) {
 469         super.indexChanged(oldIndex, newIndex);
 470 
 471         // when the cell index changes, this may result in the cell
 472         // changing state to be selected and/or focused.
 473         if (isEditing()) {
 474             // no-op
 475             // Fix for RT-31165 - if we (needlessly) update the index whilst the
 476             // cell is being edited it will no longer be in an editing state.
 477             // This means that in certain (common) circumstances that it will
 478             // appear that a cell is uneditable as, despite being clicked, it
 479             // will not change to the editing state as a layout of VirtualFlow
 480             // is immediately invoked, which forces all cells to be updated.
 481         } else {
 482             updateItem(oldIndex);
 483         }
 484 
 485         updateSelection();
 486         updateFocus();
 487     }
 488 
 489     private boolean isFirstRun = true;
 490     private void updateItem(int oldIndex) {
 491         TreeView<T> tv = getTreeView();
 492         if (tv == null) return;
 493 
 494         // Compute whether the index for this cell is for a real item
 495         int index = getIndex();
 496         boolean valid = index >=0 && index < tv.getExpandedItemCount();
 497         final boolean isEmpty = isEmpty();
 498         final TreeItem<T> oldTreeItem = getTreeItem();
 499 
 500         // Cause the cell to update itself
 501         outer: if (valid) {
 502             // update the TreeCell state.
 503             // get the new treeItem that is about to go in to the TreeCell
 504             TreeItem<T> newTreeItem = tv.getTreeItem(index);
 505             T newValue = newTreeItem == null ? null : newTreeItem.getValue();
 506             T oldValue = oldTreeItem == null ? null : oldTreeItem.getValue();
 507 
 508             // For the sake of RT-14279, it is important that the order of these
 509             // method calls is as shown below. If the order is switched, it is
 510             // likely that events will be fired where the item is null, even
 511             // though calling cell.getTreeItem().getValue() returns the value
 512             // as expected
 513 
 514             // RT-35864 - if the index didn't change, then avoid calling updateItem
 515             // unless the item has changed.
 516             if (oldIndex == index) {
 517                 if (!isItemChanged(oldValue, newValue)) {
 518                     // RT-37054:  we break out of the if/else code here and
 519                     // proceed with the code following this, so that we may
 520                     // still update references, listeners, etc as required.
 521                     break outer;
 522                 }
 523             }
 524             updateTreeItem(newTreeItem);
 525             updateItem(newValue, false);
 526         } else {
 527             // RT-30484 We need to allow a first run to be special-cased to allow
 528             // for the updateItem method to be called at least once to allow for
 529             // the correct visual state to be set up. In particular, in RT-30484
 530             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 531             // shown as the new cells are instantiated with the arrows in the
 532             // children list, and are only hidden in updateItem.
 533             if ((!isEmpty && oldTreeItem != null) || isFirstRun) {
 534                 updateTreeItem(null);
 535                 updateItem(null, true);
 536                 isFirstRun = false;
 537             }
 538         }
 539     }
 540 
 541     private void updateSelection() {
 542         if (isEmpty()) return;
 543         if (getIndex() == -1 || getTreeView() == null) return;
 544 
 545         SelectionModel<TreeItem<T>> sm = getTreeView().getSelectionModel();
 546         if (sm == null) {
 547             updateSelected(false);
 548             return;
 549         }
 550 
 551         boolean isSelected = sm.isSelected(getIndex());
 552         if (isSelected() == isSelected) return;
 553 
 554         updateSelected(isSelected);
 555     }
 556 
 557     private void updateFocus() {
 558         if (getIndex() == -1 || getTreeView() == null) return;
 559 
 560         FocusModel<TreeItem<T>> fm = getTreeView().getFocusModel();
 561         if (fm == null) {
 562             setFocused(false);
 563             return;
 564         }
 565 
 566         setFocused(fm.isFocused(getIndex()));
 567     }
 568 
 569     private void updateEditing() {
 570         final int index = getIndex();
 571         final TreeView<T> tree = getTreeView();
 572         final TreeItem<T> treeItem = getTreeItem();
 573         final TreeItem<T> editItem = tree == null ? null : tree.getEditingItem();
 574         final boolean editing = isEditing();
 575 
 576         if (index == -1 || tree == null || treeItem == null) return;
 577 
 578         final boolean match = treeItem.equals(editItem);
 579 
 580         // If my tree item is the item being edited and I'm not currently in
 581         // the edit mode, then I need to enter the edit mode
 582         if (match && !editing) {
 583             startEdit();
 584         } else if (! match && editing) {
 585             attemptEditCommit();
 586         }
 587     }
 588 
 589 
 590 
 591     /***************************************************************************
 592      *                                                                         *
 593      * Expert API                                                              *
 594      *                                                                         *
 595      **************************************************************************/
 596 
 597 
 598 
 599     /**
 600      * Updates the TreeView associated with this TreeCell.
 601      *
 602      * @param tree The new TreeView that should be associated with this TreeCell.
 603      * Note: This function is intended to be used by experts, primarily
 604      *       by those implementing new Skins. It is not common
 605      *       for developers or designers to access this function directly.
 606      */
 607     public final void updateTreeView(TreeView<T> tree) {
 608         setTreeView(tree);
 609     }
 610 
 611     /**
 612      * Updates the TreeItem associated with this TreeCell.
 613      *
 614      * @param treeItem The new TreeItem that should be associated with this
 615      *      TreeCell.
 616      * Note: This function is intended to be used by experts, primarily
 617      *       by those implementing new Skins. It is not common
 618      *       for developers or designers to access this function directly.
 619      */
 620     public final void updateTreeItem(TreeItem<T> treeItem) {
 621         TreeItem<T> _treeItem = getTreeItem();
 622         if (_treeItem != null) {
 623             _treeItem.leafProperty().removeListener(weakLeafListener);
 624         }
 625         setTreeItem(treeItem);
 626         if (treeItem != null) {
 627             treeItem.leafProperty().addListener(weakLeafListener);
 628         }
 629     }
 630 
 631 
 632 
 633     /***************************************************************************
 634      *                                                                         *
 635      * Stylesheet Handling                                                     *
 636      *                                                                         *
 637      **************************************************************************/
 638 
 639     private static final String DEFAULT_STYLE_CLASS = "tree-cell";
 640 
 641     private static final PseudoClass EXPANDED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("expanded");
 642     private static final PseudoClass COLLAPSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("collapsed");
 643 
 644 
 645     /***************************************************************************
 646      *                                                                         *
 647      * Accessibility handling                                                  *
 648      *                                                                         *
 649      **************************************************************************/
 650 
 651     @Override
 652     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 653         TreeItem<T> treeItem = getTreeItem();
 654         TreeView<T> treeView = getTreeView();
 655         switch (attribute) {
 656             case TREE_ITEM_PARENT: {
 657                 if (treeView == null) return null;
 658                 if (treeItem == null) return null;
 659                 TreeItem<T> parent = treeItem.getParent();
 660                 if (parent == null) return null;
 661                 int parentIndex = treeView.getRow(parent);
 662                 return treeView.queryAccessibleAttribute(AccessibleAttribute.ROW_AT_INDEX, parentIndex);
 663             }
 664             case TREE_ITEM_COUNT: {
 665                 if (treeItem == null) return 0;
 666                 if (!treeItem.isExpanded()) return 0;
 667                 return treeItem.getChildren().size();
 668             }
 669             case TREE_ITEM_AT_INDEX: {
 670                 if (treeItem == null) return null;
 671                 if (!treeItem.isExpanded()) return null;
 672                 int index = (Integer)parameters[0];
 673                 if (index >= treeItem.getChildren().size()) return null;
 674                 TreeItem<T> child = treeItem.getChildren().get(index);
 675                 if (child == null) return null;
 676                 int childIndex = treeView.getRow(child);
 677                 return treeView.queryAccessibleAttribute(AccessibleAttribute.ROW_AT_INDEX, childIndex);
 678             }
 679             case LEAF: return treeItem == null ? true : treeItem.isLeaf();
 680             case EXPANDED: return treeItem == null ? false : treeItem.isExpanded();
 681             case INDEX: return getIndex();
 682             case SELECTED: return isSelected();
 683             case DISCLOSURE_LEVEL: {
 684                 return treeView == null ? 0 : treeView.getTreeItemLevel(treeItem);
 685             }
 686             default: return super.queryAccessibleAttribute(attribute, parameters);
 687         }
 688     }
 689 
 690     @Override
 691     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 692         switch (action) {
 693             case EXPAND: {
 694                 TreeItem<T> treeItem = getTreeItem();
 695                 if (treeItem != null) treeItem.setExpanded(true);
 696                 break;
 697             }
 698             case COLLAPSE: {
 699                 TreeItem<T> treeItem = getTreeItem();
 700                 if (treeItem != null) treeItem.setExpanded(false);
 701                 break;
 702             }
 703             case REQUEST_FOCUS: {
 704                 TreeView<T> treeView = getTreeView();
 705                 if (treeView != null) {
 706                     FocusModel<TreeItem<T>> fm = treeView.getFocusModel();
 707                     if (fm != null) {
 708                         fm.focus(getIndex());
 709                     }
 710                 }
 711                 break;
 712             }
 713             default: super.executeAccessibleAction(action);
 714         }
 715     }
 716 }