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 }