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