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 }