1 /*
   2  * Copyright (c) 2011, 2014, 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 com.sun.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      */
 224     public final TreeItem<T> getTreeItem() { return treeItem.get(); }
 225     
 226     /**
 227      * Each TreeCell represents at most a single {@link TreeItem}, which is
 228      * represented by this property.
 229      */
 230     public final ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty() { return treeItem.getReadOnlyProperty(); }
 231 
 232 
 233     
 234     // --- Disclosure Node
 235     private ObjectProperty<Node> disclosureNode = new SimpleObjectProperty<Node>(this, "disclosureNode");
 236 
 237     /**
 238      * The node to use as the "disclosure" triangle, or toggle, used for
 239      * expanding and collapsing items. This is only used in the case of
 240      * an item in the tree which contains child items. If not specified, the
 241      * TreeCell's Skin implementation is responsible for providing a default
 242      * disclosure node.
 243      */
 244     public final void setDisclosureNode(Node value) { disclosureNodeProperty().set(value); }
 245     
 246     /**
 247      * Returns the current disclosure node set in this TreeCell.
 248      */
 249     public final Node getDisclosureNode() { return disclosureNode.get(); }
 250     
 251     /**
 252      * The disclosure node is commonly seen represented as a triangle that rotates
 253      * on screen to indicate whether or not the TreeItem that it is placed
 254      * beside is expanded or collapsed.
 255      */
 256     public final ObjectProperty<Node> disclosureNodeProperty() { return disclosureNode; }
 257     
 258     
 259     // --- TreeView
 260     private ReadOnlyObjectWrapper<TreeView<T>> treeView = new ReadOnlyObjectWrapper<TreeView<T>>() {
 261         private WeakReference<TreeView<T>> weakTreeViewRef;
 262         @Override protected void invalidated() {
 263             MultipleSelectionModel<TreeItem<T>> sm;
 264             FocusModel<TreeItem<T>> fm;
 265             
 266             if (weakTreeViewRef != null) {
 267                 TreeView<T> oldTreeView = weakTreeViewRef.get();
 268                 if (oldTreeView != null) {
 269                     // remove old listeners
 270                     sm = oldTreeView.getSelectionModel();
 271                     if (sm != null) {
 272                         sm.getSelectedIndices().removeListener(weakSelectedListener);
 273                     }
 274 
 275                     fm = oldTreeView.getFocusModel();
 276                     if (fm != null) {
 277                         fm.focusedIndexProperty().removeListener(weakFocusedListener);
 278                     }
 279 
 280                     oldTreeView.editingItemProperty().removeListener(weakEditingListener);
 281                     oldTreeView.focusModelProperty().removeListener(weakFocusModelPropertyListener);
 282                     oldTreeView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener);
 283                     oldTreeView.rootProperty().removeListener(weakRootPropertyListener);
 284                 }
 285                 
 286                 weakTreeViewRef = null;
 287             }
 288 
 289             TreeView<T> treeView = get();
 290             if (treeView != null) {
 291                 sm = treeView.getSelectionModel();
 292                 if (sm != null) {
 293                     // listening for changes to treeView.selectedIndex and IndexedCell.index,
 294                     // to determine if this cell is selected
 295                     sm.getSelectedIndices().addListener(weakSelectedListener);
 296                 }
 297 
 298                 fm = treeView.getFocusModel();
 299                 if (fm != null) {
 300                     // similar to above, but this time for focus
 301                     fm.focusedIndexProperty().addListener(weakFocusedListener);
 302                 }
 303 
 304                 treeView.editingItemProperty().addListener(weakEditingListener);
 305                 treeView.focusModelProperty().addListener(weakFocusModelPropertyListener);
 306                 treeView.selectionModelProperty().addListener(weakSelectionModelPropertyListener);
 307                 treeView.rootProperty().addListener(weakRootPropertyListener);
 308 
 309                 weakTreeViewRef = new WeakReference<TreeView<T>>(treeView);
 310             }
 311 
 312             updateItem(-1);
 313             requestLayout();
 314         }
 315 
 316         @Override
 317         public Object getBean() {
 318             return TreeCell.this;
 319         }
 320 
 321         @Override
 322         public String getName() {
 323             return "treeView";
 324         }
 325     };
 326     
 327     private void setTreeView(TreeView<T> value) { treeView.set(value); }
 328 
 329     /**
 330      * Returns the TreeView associated with this TreeCell.
 331      */
 332     public final TreeView<T> getTreeView() { return treeView.get(); }
 333     
 334     /**
 335      * A TreeCell is explicitly linked to a single {@link TreeView} instance,
 336      * which is represented by this property.
 337      */
 338     public final ReadOnlyObjectProperty<TreeView<T>> treeViewProperty() { return treeView.getReadOnlyProperty(); }
 339 
 340 
 341 
 342     /***************************************************************************
 343      *                                                                         *
 344      * Public API                                                              *
 345      *                                                                         *
 346      **************************************************************************/
 347 
 348     /** {@inheritDoc} */
 349     @Override public void startEdit() {
 350         if (isEditing()) return;
 351 
 352         final TreeView<T> tree = getTreeView();
 353         if (! isEditable() || (tree != null && ! tree.isEditable())) {
 354 //            if (Logging.getControlsLogger().isLoggable(PlatformLogger.SEVERE)) {
 355 //                Logging.getControlsLogger().severe(
 356 //                    "Can not call TreeCell.startEdit() on this TreeCell, as it "
 357 //                        + "is not allowed to enter its editing state (TreeCell: "
 358 //                        + this + ", TreeView: " + tree + ").");
 359 //            }
 360             return;
 361         }
 362 
 363         updateItem(-1);
 364         
 365         // it makes sense to get the cell into its editing state before firing
 366         // the event to the TreeView below, so that's what we're doing here
 367         // by calling super.startEdit().
 368         super.startEdit();
 369         
 370          // Inform the TreeView of the edit starting.
 371         if (tree != null) {
 372             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 373                     TreeView.<T>editStartEvent(),
 374                     getTreeItem(),
 375                     getItem(),
 376                     null));
 377             
 378             tree.requestFocus();
 379         }
 380     }
 381 
 382      /** {@inheritDoc} */
 383     @Override public void commitEdit(T newValue) {
 384         if (! isEditing()) return;
 385         final TreeItem<T> treeItem = getTreeItem();
 386         final TreeView<T> tree = getTreeView();
 387         if (tree != null) {
 388             // Inform the TreeView of the edit being ready to be committed.
 389             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 390                     TreeView.<T>editCommitEvent(),
 391                     treeItem,
 392                     getItem(),
 393                     newValue));
 394         }
 395 
 396         // inform parent classes of the commit, so that they can switch us
 397         // out of the editing state.
 398         // This MUST come before the updateItem call below, otherwise it will
 399         // call cancelEdit(), resulting in both commit and cancel events being
 400         // fired (as identified in RT-29650)
 401         super.commitEdit(newValue);
 402 
 403         // update the item within this cell, so that it represents the new value
 404         if (treeItem != null) {
 405             treeItem.setValue(newValue);
 406             updateTreeItem(treeItem);
 407             updateItem(newValue, false);
 408         }
 409         
 410         if (tree != null) {
 411             // reset the editing item in the TreetView
 412             tree.edit(null);
 413 
 414             // request focus back onto the tree, only if the current focus
 415             // owner has the tree as a parent (otherwise the user might have
 416             // clicked out of the tree entirely and given focus to something else.
 417             // It would be rude of us to request it back again.
 418             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 419         }
 420     }
 421 
 422     /** {@inheritDoc} */
 423     @Override public void cancelEdit() {
 424         if (! isEditing()) return;
 425         
 426         TreeView<T> tree = getTreeView();
 427 
 428         super.cancelEdit();
 429 
 430         if (tree != null) {
 431             // reset the editing index on the TreeView
 432             if (updateEditingIndex) tree.edit(null);
 433 
 434             // request focus back onto the tree, only if the current focus
 435             // owner has the tree as a parent (otherwise the user might have
 436             // clicked out of the tree entirely and given focus to something else.
 437             // It would be rude of us to request it back again.
 438             ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tree);
 439         
 440             tree.fireEvent(new TreeView.EditEvent<T>(tree,
 441                     TreeView.<T>editCancelEvent(),
 442                     getTreeItem(),
 443                     getItem(),
 444                     null));
 445         }
 446     }
 447 
 448     /** {@inheritDoc} */
 449     @Override protected Skin<?> createDefaultSkin() {
 450         return new TreeCellSkin<T>(this);
 451     }
 452 
 453     /***************************************************************************
 454      *                                                                         *
 455      * Private Implementation                                                  *
 456      *                                                                         *
 457      **************************************************************************/
 458 
 459     /** {@inheritDoc} */
 460     @Override void indexChanged(int oldIndex, int newIndex) {
 461         super.indexChanged(oldIndex, newIndex);
 462 
 463         // when the cell index changes, this may result in the cell
 464         // changing state to be selected and/or focused.
 465         if (isEditing() && newIndex == oldIndex) {
 466             // no-op
 467             // Fix for RT-31165 - if we (needlessly) update the index whilst the
 468             // cell is being edited it will no longer be in an editing state.
 469             // This means that in certain (common) circumstances that it will
 470             // appear that a cell is uneditable as, despite being clicked, it
 471             // will not change to the editing state as a layout of VirtualFlow
 472             // is immediately invoked, which forces all cells to be updated.
 473         } else {
 474             updateItem(oldIndex);
 475             updateSelection();
 476             updateFocus();
 477         }
 478     }
 479 
 480     private boolean isFirstRun = true;
 481     private void updateItem(int oldIndex) {
 482         TreeView<T> tv = getTreeView();
 483         if (tv == null) return;
 484         
 485         // Compute whether the index for this cell is for a real item
 486         int index = getIndex();
 487         boolean valid = index >=0 && index < tv.getExpandedItemCount();
 488         final boolean isEmpty = isEmpty();
 489         final TreeItem<T> oldTreeItem = getTreeItem();
 490 
 491         // Cause the cell to update itself
 492         outer: if (valid) {
 493             // update the TreeCell state.
 494             // get the new treeItem that is about to go in to the TreeCell
 495             TreeItem<T> newTreeItem = tv.getTreeItem(index);
 496             T newValue = newTreeItem == null ? null : newTreeItem.getValue();
 497             T oldValue = oldTreeItem == null ? null : oldTreeItem.getValue();
 498 
 499             // For the sake of RT-14279, it is important that the order of these
 500             // method calls is as shown below. If the order is switched, it is
 501             // likely that events will be fired where the item is null, even
 502             // though calling cell.getTreeItem().getValue() returns the value
 503             // as expected
 504 
 505             // RT-35864 - if the index didn't change, then avoid calling updateItem
 506             // unless the item has changed.
 507             if (oldIndex == index) {
 508                 if (!isItemChanged(oldValue, newValue)) {
 509                     // RT-37054:  we break out of the if/else code here and
 510                     // proceed with the code following this, so that we may
 511                     // still update references, listeners, etc as required.
 512                     break outer;
 513                 }
 514             }
 515             updateTreeItem(newTreeItem);
 516             updateItem(newValue, false);
 517         } else {
 518             // RT-30484 We need to allow a first run to be special-cased to allow
 519             // for the updateItem method to be called at least once to allow for
 520             // the correct visual state to be set up. In particular, in RT-30484
 521             // refer to Ensemble8PopUpTree.png - in this case the arrows are being
 522             // shown as the new cells are instantiated with the arrows in the
 523             // children list, and are only hidden in updateItem.
 524             if ((!isEmpty && oldTreeItem != null) || isFirstRun) {
 525                 updateTreeItem(null);
 526                 updateItem(null, true);
 527                 isFirstRun = false;
 528             }
 529         }
 530     }
 531 
 532     private void updateSelection() {
 533         if (isEmpty()) return;
 534         if (getIndex() == -1 || getTreeView() == null) return;
 535 
 536         SelectionModel<TreeItem<T>> sm = getTreeView().getSelectionModel();
 537         if (sm == null) {
 538             updateSelected(false);
 539             return;
 540         }
 541         
 542         boolean isSelected = sm.isSelected(getIndex());
 543         if (isSelected() == isSelected) return;
 544         
 545         updateSelected(isSelected);
 546     }
 547 
 548     private void updateFocus() {
 549         if (getIndex() == -1 || getTreeView() == null) return;
 550 
 551         FocusModel<TreeItem<T>> fm = getTreeView().getFocusModel();
 552         if (fm == null) {
 553             setFocused(false);
 554             return;
 555         }
 556         
 557         setFocused(fm.isFocused(getIndex()));
 558     }
 559 
 560     private boolean updateEditingIndex = true;
 561     private void updateEditing() {
 562         final int index = getIndex();
 563         final TreeView<T> tree = getTreeView();
 564         final TreeItem<T> treeItem = getTreeItem();
 565         final TreeItem<T> editItem = tree == null ? null : tree.getEditingItem();
 566         final boolean editing = isEditing();
 567         
 568         if (index == -1 || tree == null || treeItem == null) return;
 569         
 570         final boolean match = treeItem.equals(editItem);
 571         
 572         // If my tree item is the item being edited and I'm not currently in
 573         // the edit mode, then I need to enter the edit mode
 574         if (match && !editing) {
 575             startEdit();
 576         } else if (! match && editing) {
 577             // If my tree item is not the one being edited then I need to cancel
 578             // the edit. The tricky thing here is that as part of this call
 579             // I cannot end up calling tree.edit(null) the way that the standard
 580             // cancelEdit method would do. Yet, I need to call cancelEdit
 581             // so that subclasses which override cancelEdit can execute. So,
 582             // I have to use a kind of hacky flag workaround.
 583             updateEditingIndex = false;
 584             cancelEdit();
 585             updateEditingIndex = true;
 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      * @expert 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      * @expert 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 }