1 /* 2 * Copyright (c) 1998, 2018, 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 javax.swing.tree; 27 28 import javax.swing.event.TreeModelEvent; 29 import java.awt.Rectangle; 30 import java.util.Enumeration; 31 import java.util.Hashtable; 32 import java.util.NoSuchElementException; 33 import java.util.Stack; 34 35 import sun.swing.SwingUtilities2; 36 37 /** 38 * NOTE: This will become more open in a future release. 39 * <p> 40 * <strong>Warning:</strong> 41 * Serialized objects of this class will not be compatible with 42 * future Swing releases. The current serialization support is 43 * appropriate for short term storage or RMI between applications running 44 * the same version of Swing. As of 1.4, support for long term storage 45 * of all JavaBeans™ 46 * has been added to the <code>java.beans</code> package. 47 * Please see {@link java.beans.XMLEncoder}. 48 * 49 * @author Scott Violet 50 */ 51 @SuppressWarnings("serial") // Same-version serialization only 52 public class FixedHeightLayoutCache extends AbstractLayoutCache { 53 /** Root node. */ 54 private FHTreeStateNode root; 55 56 /** Number of rows currently visible. */ 57 private int rowCount; 58 59 /** 60 * Used in getting sizes for nodes to avoid creating a new Rectangle 61 * every time a size is needed. 62 */ 63 private Rectangle boundsBuffer; 64 65 /** 66 * Maps from TreePath to a FHTreeStateNode. 67 */ 68 private Hashtable<TreePath, FHTreeStateNode> treePathMapping; 69 70 /** 71 * Used for getting path/row information. 72 */ 73 private SearchInfo info; 74 75 private Stack<Stack<TreePath>> tempStacks; 76 77 /** 78 * Constructs a {@code FixedHeightLayoutCache}. 79 */ 80 public FixedHeightLayoutCache() { 81 super(); 82 tempStacks = new Stack<Stack<TreePath>>(); 83 boundsBuffer = new Rectangle(); 84 treePathMapping = new Hashtable<TreePath, FHTreeStateNode>(); 85 info = new SearchInfo(); 86 setRowHeight(1); 87 } 88 89 /** 90 * Sets the TreeModel that will provide the data. 91 * 92 * @param newModel the TreeModel that is to provide the data 93 */ 94 public void setModel(TreeModel newModel) { 95 super.setModel(newModel); 96 rebuild(false); 97 } 98 99 /** 100 * Determines whether or not the root node from 101 * the TreeModel is visible. 102 * 103 * @param rootVisible true if the root node of the tree is to be displayed 104 * @see #rootVisible 105 */ 106 public void setRootVisible(boolean rootVisible) { 107 if(isRootVisible() != rootVisible) { 108 super.setRootVisible(rootVisible); 109 if(root != null) { 110 if(rootVisible) { 111 rowCount++; 112 root.adjustRowBy(1); 113 } 114 else { 115 rowCount--; 116 root.adjustRowBy(-1); 117 } 118 visibleNodesChanged(); 119 } 120 } 121 } 122 123 /** 124 * Sets the height of each cell. If rowHeight is less than or equal to 125 * 0 this will throw an IllegalArgumentException. 126 * 127 * @param rowHeight the height of each cell, in pixels 128 */ 129 public void setRowHeight(int rowHeight) { 130 if(rowHeight <= 0) 131 throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0"); 132 if(getRowHeight() != rowHeight) { 133 super.setRowHeight(rowHeight); 134 visibleNodesChanged(); 135 } 136 } 137 138 /** 139 * Returns the number of visible rows. 140 */ 141 public int getRowCount() { 142 return rowCount; 143 } 144 145 /** 146 * Does nothing, FixedHeightLayoutCache doesn't cache width, and that 147 * is all that could change. 148 */ 149 public void invalidatePathBounds(TreePath path) { 150 } 151 152 153 /** 154 * Informs the TreeState that it needs to recalculate all the sizes 155 * it is referencing. 156 */ 157 public void invalidateSizes() { 158 // Nothing to do here, rowHeight still same, which is all 159 // this is interested in, visible region may have changed though. 160 visibleNodesChanged(); 161 } 162 163 /** 164 * Returns true if the value identified by row is currently expanded. 165 */ 166 public boolean isExpanded(TreePath path) { 167 if(path != null) { 168 FHTreeStateNode lastNode = getNodeForPath(path, true, false); 169 170 return (lastNode != null && lastNode.isExpanded()); 171 } 172 return false; 173 } 174 175 /** 176 * Returns a rectangle giving the bounds needed to draw path. 177 * 178 * @param path a TreePath specifying a node 179 * @param placeIn a Rectangle object giving the available space 180 * @return a Rectangle object specifying the space to be used 181 */ 182 public Rectangle getBounds(TreePath path, Rectangle placeIn) { 183 if(path == null) 184 return null; 185 186 FHTreeStateNode node = getNodeForPath(path, true, false); 187 188 if(node != null) 189 return getBounds(node, -1, placeIn); 190 191 // node hasn't been created yet. 192 TreePath parentPath = path.getParentPath(); 193 194 node = getNodeForPath(parentPath, true, false); 195 if (node != null && node.isExpanded()) { 196 int childIndex = treeModel.getIndexOfChild 197 (parentPath.getLastPathComponent(), 198 path.getLastPathComponent()); 199 200 if(childIndex != -1) 201 return getBounds(node, childIndex, placeIn); 202 } 203 return null; 204 } 205 206 /** 207 * Returns the path for passed in row. If row is not visible 208 * null is returned. 209 */ 210 public TreePath getPathForRow(int row) { 211 if(row >= 0 && row < getRowCount()) { 212 if(root.getPathForRow(row, getRowCount(), info)) { 213 return info.getPath(); 214 } 215 } 216 return null; 217 } 218 219 /** 220 * Returns the row that the last item identified in path is visible 221 * at. Will return -1 if any of the elements in path are not 222 * currently visible. 223 */ 224 public int getRowForPath(TreePath path) { 225 if(path == null || root == null) 226 return -1; 227 228 FHTreeStateNode node = getNodeForPath(path, true, false); 229 230 if(node != null) 231 return node.getRow(); 232 233 TreePath parentPath = path.getParentPath(); 234 235 node = getNodeForPath(parentPath, true, false); 236 if(node != null && node.isExpanded()) { 237 return node.getRowToModelIndex(treeModel.getIndexOfChild 238 (parentPath.getLastPathComponent(), 239 path.getLastPathComponent())); 240 } 241 return -1; 242 } 243 244 /** 245 * Returns the path to the node that is closest to x,y. If 246 * there is nothing currently visible this will return null, otherwise 247 * it'll always return a valid path. If you need to test if the 248 * returned object is exactly at x, y you should get the bounds for 249 * the returned path and test x, y against that. 250 */ 251 public TreePath getPathClosestTo(int x, int y) { 252 if(getRowCount() == 0) 253 return null; 254 255 int row = getRowContainingYLocation(y); 256 257 return getPathForRow(row); 258 } 259 260 /** 261 * Returns the number of visible children for row. 262 */ 263 public int getVisibleChildCount(TreePath path) { 264 FHTreeStateNode node = getNodeForPath(path, true, false); 265 266 if(node == null) 267 return 0; 268 return node.getTotalChildCount(); 269 } 270 271 /** 272 * Returns an Enumerator that increments over the visible paths 273 * starting at the passed in location. The ordering of the enumeration 274 * is based on how the paths are displayed. 275 */ 276 public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) { 277 if(path == null) 278 return null; 279 280 FHTreeStateNode node = getNodeForPath(path, true, false); 281 282 if(node != null) { 283 return new VisibleFHTreeStateNodeEnumeration(node); 284 } 285 TreePath parentPath = path.getParentPath(); 286 287 node = getNodeForPath(parentPath, true, false); 288 if(node != null && node.isExpanded()) { 289 return new VisibleFHTreeStateNodeEnumeration(node, 290 treeModel.getIndexOfChild(parentPath.getLastPathComponent(), 291 path.getLastPathComponent())); 292 } 293 return null; 294 } 295 296 /** 297 * Marks the path <code>path</code> expanded state to 298 * <code>isExpanded</code>. 299 */ 300 public void setExpandedState(TreePath path, boolean isExpanded) { 301 if(isExpanded) 302 ensurePathIsExpanded(path, true); 303 else if(path != null) { 304 TreePath parentPath = path.getParentPath(); 305 306 // YECK! Make the parent expanded. 307 if(parentPath != null) { 308 FHTreeStateNode parentNode = getNodeForPath(parentPath, 309 false, true); 310 if(parentNode != null) 311 parentNode.makeVisible(); 312 } 313 // And collapse the child. 314 FHTreeStateNode childNode = getNodeForPath(path, true, 315 false); 316 317 if(childNode != null) 318 childNode.collapse(true); 319 } 320 } 321 322 /** 323 * Returns true if the path is expanded, and visible. 324 */ 325 public boolean getExpandedState(TreePath path) { 326 FHTreeStateNode node = getNodeForPath(path, true, false); 327 328 return (node != null) ? (node.isVisible() && node.isExpanded()) : 329 false; 330 } 331 332 // 333 // TreeModelListener methods 334 // 335 336 /** 337 * <p>Invoked after a node (or a set of siblings) has changed in some 338 * way. The node(s) have not changed locations in the tree or 339 * altered their children arrays, but other attributes have 340 * changed and may affect presentation. Example: the name of a 341 * file has changed, but it is in the same location in the file 342 * system.</p> 343 * 344 * <p>e.path() returns the path the parent of the changed node(s).</p> 345 * 346 * <p>e.childIndices() returns the index(es) of the changed node(s).</p> 347 */ 348 public void treeNodesChanged(TreeModelEvent e) { 349 if(e != null) { 350 int[] changedIndexs; 351 FHTreeStateNode changedParent = getNodeForPath 352 (SwingUtilities2.getTreePath(e, getModel()), false, false); 353 int maxCounter; 354 355 changedIndexs = e.getChildIndices(); 356 /* Only need to update the children if the node has been 357 expanded once. */ 358 // PENDING(scott): make sure childIndexs is sorted! 359 if (changedParent != null) { 360 if (changedIndexs != null && 361 (maxCounter = changedIndexs.length) > 0) { 362 Object parentValue = changedParent.getUserObject(); 363 364 for(int counter = 0; counter < maxCounter; counter++) { 365 FHTreeStateNode child = changedParent. 366 getChildAtModelIndex(changedIndexs[counter]); 367 368 if(child != null) { 369 child.setUserObject(treeModel.getChild(parentValue, 370 changedIndexs[counter])); 371 } 372 } 373 if(changedParent.isVisible() && changedParent.isExpanded()) 374 visibleNodesChanged(); 375 } 376 // Null for root indicates it changed. 377 else if (changedParent == root && changedParent.isVisible() && 378 changedParent.isExpanded()) { 379 visibleNodesChanged(); 380 } 381 } 382 } 383 } 384 385 /** 386 * <p>Invoked after nodes have been inserted into the tree.</p> 387 * 388 * <p>e.path() returns the parent of the new nodes 389 * <p>e.childIndices() returns the indices of the new nodes in 390 * ascending order. 391 */ 392 public void treeNodesInserted(TreeModelEvent e) { 393 if(e != null) { 394 int[] changedIndexs; 395 FHTreeStateNode changedParent = getNodeForPath 396 (SwingUtilities2.getTreePath(e, getModel()), false, false); 397 int maxCounter; 398 399 changedIndexs = e.getChildIndices(); 400 /* Only need to update the children if the node has been 401 expanded once. */ 402 // PENDING(scott): make sure childIndexs is sorted! 403 if(changedParent != null && changedIndexs != null && 404 (maxCounter = changedIndexs.length) > 0) { 405 boolean isVisible = 406 (changedParent.isVisible() && 407 changedParent.isExpanded()); 408 409 for(int counter = 0; counter < maxCounter; counter++) { 410 changedParent.childInsertedAtModelIndex 411 (changedIndexs[counter], isVisible); 412 } 413 if(isVisible && treeSelectionModel != null) 414 treeSelectionModel.resetRowSelection(); 415 if(changedParent.isVisible()) 416 this.visibleNodesChanged(); 417 } 418 } 419 } 420 421 /** 422 * <p>Invoked after nodes have been removed from the tree. Note that 423 * if a subtree is removed from the tree, this method may only be 424 * invoked once for the root of the removed subtree, not once for 425 * each individual set of siblings removed.</p> 426 * 427 * <p>e.path() returns the former parent of the deleted nodes.</p> 428 * 429 * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p> 430 */ 431 public void treeNodesRemoved(TreeModelEvent e) { 432 if(e != null) { 433 int[] changedIndexs; 434 int maxCounter; 435 TreePath parentPath = SwingUtilities2.getTreePath(e, getModel()); 436 FHTreeStateNode changedParentNode = getNodeForPath 437 (parentPath, false, false); 438 439 changedIndexs = e.getChildIndices(); 440 // PENDING(scott): make sure that changedIndexs are sorted in 441 // ascending order. 442 if(changedParentNode != null && changedIndexs != null && 443 (maxCounter = changedIndexs.length) > 0) { 444 Object[] children = e.getChildren(); 445 boolean isVisible = 446 (changedParentNode.isVisible() && 447 changedParentNode.isExpanded()); 448 449 for(int counter = maxCounter - 1; counter >= 0; counter--) { 450 changedParentNode.removeChildAtModelIndex 451 (changedIndexs[counter], isVisible); 452 } 453 if(isVisible) { 454 if(treeSelectionModel != null) 455 treeSelectionModel.resetRowSelection(); 456 if (treeModel.getChildCount(changedParentNode. 457 getUserObject()) == 0 && 458 changedParentNode.isLeaf()) { 459 // Node has become a leaf, collapse it. 460 changedParentNode.collapse(false); 461 } 462 visibleNodesChanged(); 463 } 464 else if(changedParentNode.isVisible()) 465 visibleNodesChanged(); 466 } 467 } 468 } 469 470 /** 471 * <p>Invoked after the tree has drastically changed structure from a 472 * given node down. If the path returned by e.getPath() is of length 473 * one and the first element does not identify the current root node 474 * the first element should become the new root of the tree. 475 * 476 * <p>e.path() holds the path to the node.</p> 477 * <p>e.childIndices() returns null.</p> 478 */ 479 public void treeStructureChanged(TreeModelEvent e) { 480 if(e != null) { 481 TreePath changedPath = SwingUtilities2.getTreePath(e, getModel()); 482 FHTreeStateNode changedNode = getNodeForPath 483 (changedPath, false, false); 484 485 // Check if root has changed, either to a null root, or 486 // to an entirely new root. 487 if (changedNode == root || 488 (changedNode == null && 489 ((changedPath == null && treeModel != null && 490 treeModel.getRoot() == null) || 491 (changedPath != null && changedPath.getPathCount() <= 1)))) { 492 rebuild(true); 493 } 494 else if(changedNode != null) { 495 boolean wasExpanded, wasVisible; 496 FHTreeStateNode parent = (FHTreeStateNode) 497 changedNode.getParent(); 498 499 wasExpanded = changedNode.isExpanded(); 500 wasVisible = changedNode.isVisible(); 501 502 int index = parent.getIndex(changedNode); 503 changedNode.collapse(false); 504 parent.remove(index); 505 506 if(wasVisible && wasExpanded) { 507 int row = changedNode.getRow(); 508 parent.resetChildrenRowsFrom(row, index, 509 changedNode.getChildIndex()); 510 changedNode = getNodeForPath(changedPath, false, true); 511 changedNode.expand(); 512 } 513 if(treeSelectionModel != null && wasVisible && wasExpanded) 514 treeSelectionModel.resetRowSelection(); 515 if(wasVisible) 516 this.visibleNodesChanged(); 517 } 518 } 519 } 520 521 522 // 523 // Local methods 524 // 525 526 private void visibleNodesChanged() { 527 } 528 529 /** 530 * Returns the bounds for the given node. If <code>childIndex</code> 531 * is -1, the bounds of <code>parent</code> are returned, otherwise 532 * the bounds of the node at <code>childIndex</code> are returned. 533 */ 534 private Rectangle getBounds(FHTreeStateNode parent, int childIndex, 535 Rectangle placeIn) { 536 boolean expanded; 537 int level; 538 int row; 539 Object value; 540 541 if(childIndex == -1) { 542 // Getting bounds for parent 543 row = parent.getRow(); 544 value = parent.getUserObject(); 545 expanded = parent.isExpanded(); 546 level = parent.getLevel(); 547 } 548 else { 549 row = parent.getRowToModelIndex(childIndex); 550 value = treeModel.getChild(parent.getUserObject(), childIndex); 551 expanded = false; 552 level = parent.getLevel() + 1; 553 } 554 555 Rectangle bounds = getNodeDimensions(value, row, level, 556 expanded, boundsBuffer); 557 // No node dimensions, bail. 558 if(bounds == null) 559 return null; 560 561 if(placeIn == null) 562 placeIn = new Rectangle(); 563 564 placeIn.x = bounds.x; 565 placeIn.height = getRowHeight(); 566 placeIn.y = row * placeIn.height; 567 placeIn.width = bounds.width; 568 return placeIn; 569 } 570 571 /** 572 * Adjust the large row count of the AbstractTreeUI the receiver was 573 * created with. 574 */ 575 private void adjustRowCountBy(int changeAmount) { 576 rowCount += changeAmount; 577 } 578 579 /** 580 * Adds a mapping for node. 581 */ 582 private void addMapping(FHTreeStateNode node) { 583 treePathMapping.put(node.getTreePath(), node); 584 } 585 586 /** 587 * Removes the mapping for a previously added node. 588 */ 589 private void removeMapping(FHTreeStateNode node) { 590 treePathMapping.remove(node.getTreePath()); 591 } 592 593 /** 594 * Returns the node previously added for <code>path</code>. This may 595 * return null, if you to create a node use getNodeForPath. 596 */ 597 private FHTreeStateNode getMapping(TreePath path) { 598 return treePathMapping.get(path); 599 } 600 601 /** 602 * Sent to completely rebuild the visible tree. All nodes are collapsed. 603 */ 604 private void rebuild(boolean clearSelection) { 605 Object rootUO; 606 607 treePathMapping.clear(); 608 if(treeModel != null && (rootUO = treeModel.getRoot()) != null) { 609 root = createNodeForValue(rootUO, 0); 610 root.path = new TreePath(rootUO); 611 addMapping(root); 612 if(isRootVisible()) { 613 rowCount = 1; 614 root.row = 0; 615 } 616 else { 617 rowCount = 0; 618 root.row = -1; 619 } 620 root.expand(); 621 } 622 else { 623 root = null; 624 rowCount = 0; 625 } 626 if(clearSelection && treeSelectionModel != null) { 627 treeSelectionModel.clearSelection(); 628 } 629 this.visibleNodesChanged(); 630 } 631 632 /** 633 * Returns the index of the row containing location. If there 634 * are no rows, -1 is returned. If location is beyond the last 635 * row index, the last row index is returned. 636 */ 637 private int getRowContainingYLocation(int location) { 638 if(getRowCount() == 0) 639 return -1; 640 return Math.max(0, Math.min(getRowCount() - 1, 641 location / getRowHeight())); 642 } 643 644 /** 645 * Ensures that all the path components in path are expanded, accept 646 * for the last component which will only be expanded if expandLast 647 * is true. 648 * Returns true if succesful in finding the path. 649 */ 650 private boolean ensurePathIsExpanded(TreePath aPath, 651 boolean expandLast) { 652 if(aPath != null) { 653 // Make sure the last entry isn't a leaf. 654 if(treeModel.isLeaf(aPath.getLastPathComponent())) { 655 aPath = aPath.getParentPath(); 656 expandLast = true; 657 } 658 if(aPath != null) { 659 FHTreeStateNode lastNode = getNodeForPath(aPath, false, 660 true); 661 662 if(lastNode != null) { 663 lastNode.makeVisible(); 664 if(expandLast) 665 lastNode.expand(); 666 return true; 667 } 668 } 669 } 670 return false; 671 } 672 673 /** 674 * Creates and returns an instance of FHTreeStateNode. 675 */ 676 private FHTreeStateNode createNodeForValue(Object value,int childIndex) { 677 return new FHTreeStateNode(value, childIndex, -1); 678 } 679 680 /** 681 * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate, 682 * path.length) as long as path is non-null and the length is {@literal >} 0. 683 * Otherwise returns null. 684 */ 685 private FHTreeStateNode getNodeForPath(TreePath path, 686 boolean onlyIfVisible, 687 boolean shouldCreate) { 688 if(path != null) { 689 FHTreeStateNode node; 690 691 node = getMapping(path); 692 if(node != null) { 693 if(onlyIfVisible && !node.isVisible()) 694 return null; 695 return node; 696 } 697 if(onlyIfVisible) 698 return null; 699 700 // Check all the parent paths, until a match is found. 701 Stack<TreePath> paths; 702 703 if(tempStacks.size() == 0) { 704 paths = new Stack<TreePath>(); 705 } 706 else { 707 paths = tempStacks.pop(); 708 } 709 710 try { 711 paths.push(path); 712 path = path.getParentPath(); 713 node = null; 714 while(path != null) { 715 node = getMapping(path); 716 if(node != null) { 717 // Found a match, create entries for all paths in 718 // paths. 719 while(node != null && paths.size() > 0) { 720 path = paths.pop(); 721 node = node.createChildFor(path. 722 getLastPathComponent()); 723 } 724 return node; 725 } 726 paths.push(path); 727 path = path.getParentPath(); 728 } 729 } 730 finally { 731 paths.removeAllElements(); 732 tempStacks.push(paths); 733 } 734 // If we get here it means they share a different root! 735 return null; 736 } 737 return null; 738 } 739 740 /** 741 * FHTreeStateNode is used to track what has been expanded. 742 * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode 743 * in that it is highly model intensive. That is almost all queries to a 744 * FHTreeStateNode result in the TreeModel being queried. And it 745 * obviously does not support variable sized row heights. 746 */ 747 private class FHTreeStateNode extends DefaultMutableTreeNode { 748 /** Is this node expanded? */ 749 protected boolean isExpanded; 750 751 /** Index of this node from the model. */ 752 protected int childIndex; 753 754 /** Child count of the receiver. */ 755 protected int childCount; 756 757 /** Row of the receiver. This is only valid if the row is expanded. 758 */ 759 protected int row; 760 761 /** Path of this node. */ 762 protected TreePath path; 763 764 765 public FHTreeStateNode(Object userObject, int childIndex, int row) { 766 super(userObject); 767 this.childIndex = childIndex; 768 this.row = row; 769 } 770 771 // 772 // Overriden DefaultMutableTreeNode methods 773 // 774 775 /** 776 * Messaged when this node is added somewhere, resets the path 777 * and adds a mapping from path to this node. 778 */ 779 public void setParent(MutableTreeNode parent) { 780 super.setParent(parent); 781 if(parent != null) { 782 path = ((FHTreeStateNode)parent).getTreePath(). 783 pathByAddingChild(getUserObject()); 784 addMapping(this); 785 } 786 } 787 788 /** 789 * Messaged when this node is removed from its parent, this messages 790 * <code>removedFromMapping</code> to remove all the children. 791 */ 792 public void remove(int childIndex) { 793 FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex); 794 795 node.removeFromMapping(); 796 super.remove(childIndex); 797 } 798 799 /** 800 * Messaged to set the user object. This resets the path. 801 */ 802 public void setUserObject(Object o) { 803 super.setUserObject(o); 804 if(path != null) { 805 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 806 807 if(parent != null) 808 resetChildrenPaths(parent.getTreePath()); 809 else 810 resetChildrenPaths(null); 811 } 812 } 813 814 // 815 // 816 817 /** 818 * Returns the index of the receiver in the model. 819 */ 820 public int getChildIndex() { 821 return childIndex; 822 } 823 824 /** 825 * Returns the <code>TreePath</code> of the receiver. 826 */ 827 public TreePath getTreePath() { 828 return path; 829 } 830 831 /** 832 * Returns the child for the passed in model index, this will 833 * return <code>null</code> if the child for <code>index</code> 834 * has not yet been created (expanded). 835 */ 836 public FHTreeStateNode getChildAtModelIndex(int index) { 837 // PENDING: Make this a binary search! 838 for(int counter = getChildCount() - 1; counter >= 0; counter--) 839 if(((FHTreeStateNode)getChildAt(counter)).childIndex == index) 840 return (FHTreeStateNode)getChildAt(counter); 841 return null; 842 } 843 844 /** 845 * Returns true if this node is visible. This is determined by 846 * asking all the parents if they are expanded. 847 */ 848 public boolean isVisible() { 849 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 850 851 if(parent == null) 852 return true; 853 return (parent.isExpanded() && parent.isVisible()); 854 } 855 856 /** 857 * Returns the row of the receiver. 858 */ 859 public int getRow() { 860 return row; 861 } 862 863 /** 864 * Returns the row of the child with a model index of 865 * <code>index</code>. 866 */ 867 public int getRowToModelIndex(int index) { 868 FHTreeStateNode child; 869 int lastRow = getRow() + 1; 870 int retValue = lastRow; 871 872 // This too could be a binary search! 873 for(int counter = 0, maxCounter = getChildCount(); 874 counter < maxCounter; counter++) { 875 child = (FHTreeStateNode)getChildAt(counter); 876 if(child.childIndex >= index) { 877 if(child.childIndex == index) 878 return child.row; 879 if(counter == 0) 880 return getRow() + 1 + index; 881 return child.row - (child.childIndex - index); 882 } 883 } 884 // YECK! 885 return getRow() + 1 + getTotalChildCount() - 886 (childCount - index); 887 } 888 889 /** 890 * Returns the number of children in the receiver by descending all 891 * expanded nodes and messaging them with getTotalChildCount. 892 */ 893 public int getTotalChildCount() { 894 if(isExpanded()) { 895 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 896 int pIndex; 897 898 if(parent != null && (pIndex = parent.getIndex(this)) + 1 < 899 parent.getChildCount()) { 900 // This node has a created sibling, to calc total 901 // child count directly from that! 902 FHTreeStateNode nextSibling = (FHTreeStateNode)parent. 903 getChildAt(pIndex + 1); 904 905 return nextSibling.row - row - 906 (nextSibling.childIndex - childIndex); 907 } 908 else { 909 int retCount = childCount; 910 911 for(int counter = getChildCount() - 1; counter >= 0; 912 counter--) { 913 retCount += ((FHTreeStateNode)getChildAt(counter)) 914 .getTotalChildCount(); 915 } 916 return retCount; 917 } 918 } 919 return 0; 920 } 921 922 /** 923 * Returns true if this node is expanded. 924 */ 925 public boolean isExpanded() { 926 return isExpanded; 927 } 928 929 /** 930 * The highest visible nodes have a depth of 0. 931 */ 932 public int getVisibleLevel() { 933 if (isRootVisible()) { 934 return getLevel(); 935 } else { 936 return getLevel() - 1; 937 } 938 } 939 940 /** 941 * Recreates the receivers path, and all its children's paths. 942 */ 943 protected void resetChildrenPaths(TreePath parentPath) { 944 removeMapping(this); 945 if(parentPath == null) 946 path = new TreePath(getUserObject()); 947 else 948 path = parentPath.pathByAddingChild(getUserObject()); 949 addMapping(this); 950 for(int counter = getChildCount() - 1; counter >= 0; counter--) 951 ((FHTreeStateNode)getChildAt(counter)). 952 resetChildrenPaths(path); 953 } 954 955 /** 956 * Removes the receiver, and all its children, from the mapping 957 * table. 958 */ 959 protected void removeFromMapping() { 960 if(path != null) { 961 removeMapping(this); 962 for(int counter = getChildCount() - 1; counter >= 0; counter--) 963 ((FHTreeStateNode)getChildAt(counter)).removeFromMapping(); 964 } 965 } 966 967 /** 968 * Creates a new node to represent <code>userObject</code>. 969 * This does NOT check to ensure there isn't already a child node 970 * to manage <code>userObject</code>. 971 */ 972 protected FHTreeStateNode createChildFor(Object userObject) { 973 int newChildIndex = treeModel.getIndexOfChild 974 (getUserObject(), userObject); 975 976 if(newChildIndex < 0) 977 return null; 978 979 FHTreeStateNode aNode; 980 FHTreeStateNode child = createNodeForValue(userObject, 981 newChildIndex); 982 int childRow; 983 984 if(isVisible()) { 985 childRow = getRowToModelIndex(newChildIndex); 986 } 987 else { 988 childRow = -1; 989 } 990 child.row = childRow; 991 for(int counter = 0, maxCounter = getChildCount(); 992 counter < maxCounter; counter++) { 993 aNode = (FHTreeStateNode)getChildAt(counter); 994 if(aNode.childIndex > newChildIndex) { 995 insert(child, counter); 996 return child; 997 } 998 } 999 add(child); 1000 return child; 1001 } 1002 1003 /** 1004 * Adjusts the receiver, and all its children rows by 1005 * <code>amount</code>. 1006 */ 1007 protected void adjustRowBy(int amount) { 1008 row += amount; 1009 if(isExpanded) { 1010 for(int counter = getChildCount() - 1; counter >= 0; 1011 counter--) 1012 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount); 1013 } 1014 } 1015 1016 /** 1017 * Adjusts this node, its child, and its parent starting at 1018 * an index of <code>index</code> index is the index of the child 1019 * to start adjusting from, which is not necessarily the model 1020 * index. 1021 */ 1022 protected void adjustRowBy(int amount, int startIndex) { 1023 // Could check isVisible, but probably isn't worth it. 1024 if(isExpanded) { 1025 // children following startIndex. 1026 for(int counter = getChildCount() - 1; counter >= startIndex; 1027 counter--) 1028 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount); 1029 } 1030 // Parent 1031 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 1032 1033 if(parent != null) { 1034 parent.adjustRowBy(amount, parent.getIndex(this) + 1); 1035 } 1036 } 1037 1038 /** 1039 * Messaged when the node has expanded. This updates all of 1040 * the receivers children rows, as well as the total row count. 1041 */ 1042 protected void didExpand() { 1043 int nextRow = setRowAndChildren(row); 1044 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 1045 int childRowCount = nextRow - row - 1; 1046 1047 if(parent != null) { 1048 parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1); 1049 } 1050 adjustRowCountBy(childRowCount); 1051 } 1052 1053 /** 1054 * Sets the receivers row to <code>nextRow</code> and recursively 1055 * updates all the children of the receivers rows. The index the 1056 * next row is to be placed as is returned. 1057 */ 1058 protected int setRowAndChildren(int nextRow) { 1059 row = nextRow; 1060 1061 if(!isExpanded()) 1062 return row + 1; 1063 1064 int lastRow = row + 1; 1065 int lastModelIndex = 0; 1066 FHTreeStateNode child; 1067 int maxCounter = getChildCount(); 1068 1069 for(int counter = 0; counter < maxCounter; counter++) { 1070 child = (FHTreeStateNode)getChildAt(counter); 1071 lastRow += (child.childIndex - lastModelIndex); 1072 lastModelIndex = child.childIndex + 1; 1073 if(child.isExpanded) { 1074 lastRow = child.setRowAndChildren(lastRow); 1075 } 1076 else { 1077 child.row = lastRow++; 1078 } 1079 } 1080 return lastRow + childCount - lastModelIndex; 1081 } 1082 1083 /** 1084 * Resets the receivers children's rows. Starting with the child 1085 * at <code>childIndex</code> (and <code>modelIndex</code>) to 1086 * <code>newRow</code>. This uses <code>setRowAndChildren</code> 1087 * to recursively descend children, and uses 1088 * <code>resetRowSelection</code> to ascend parents. 1089 */ 1090 // This can be rather expensive, but is needed for the collapse 1091 // case this is resulting from a remove (although I could fix 1092 // that by having instances of FHTreeStateNode hold a ref to 1093 // the number of children). I prefer this though, making determing 1094 // the row of a particular node fast is very nice! 1095 protected void resetChildrenRowsFrom(int newRow, int childIndex, 1096 int modelIndex) { 1097 int lastRow = newRow; 1098 int lastModelIndex = modelIndex; 1099 FHTreeStateNode node; 1100 int maxCounter = getChildCount(); 1101 1102 for(int counter = childIndex; counter < maxCounter; counter++) { 1103 node = (FHTreeStateNode)getChildAt(counter); 1104 lastRow += (node.childIndex - lastModelIndex); 1105 lastModelIndex = node.childIndex + 1; 1106 if(node.isExpanded) { 1107 lastRow = node.setRowAndChildren(lastRow); 1108 } 1109 else { 1110 node.row = lastRow++; 1111 } 1112 } 1113 lastRow += childCount - lastModelIndex; 1114 node = (FHTreeStateNode)getParent(); 1115 if(node != null) { 1116 node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1, 1117 this.childIndex + 1); 1118 } 1119 else { // This is the root, reset total ROWCOUNT! 1120 rowCount = lastRow; 1121 } 1122 } 1123 1124 /** 1125 * Makes the receiver visible, but invoking 1126 * <code>expandParentAndReceiver</code> on the superclass. 1127 */ 1128 protected void makeVisible() { 1129 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 1130 1131 if(parent != null) 1132 parent.expandParentAndReceiver(); 1133 } 1134 1135 /** 1136 * Invokes <code>expandParentAndReceiver</code> on the parent, 1137 * and expands the receiver. 1138 */ 1139 protected void expandParentAndReceiver() { 1140 FHTreeStateNode parent = (FHTreeStateNode)getParent(); 1141 1142 if(parent != null) 1143 parent.expandParentAndReceiver(); 1144 expand(); 1145 } 1146 1147 /** 1148 * Expands the receiver. 1149 */ 1150 protected void expand() { 1151 if(!isExpanded && !isLeaf()) { 1152 boolean visible = isVisible(); 1153 1154 isExpanded = true; 1155 childCount = treeModel.getChildCount(getUserObject()); 1156 1157 if(visible) { 1158 didExpand(); 1159 } 1160 1161 // Update the selection model. 1162 if(visible && treeSelectionModel != null) { 1163 treeSelectionModel.resetRowSelection(); 1164 } 1165 } 1166 } 1167 1168 /** 1169 * Collapses the receiver. If <code>adjustRows</code> is true, 1170 * the rows of nodes after the receiver are adjusted. 1171 */ 1172 protected void collapse(boolean adjustRows) { 1173 if(isExpanded) { 1174 if(isVisible() && adjustRows) { 1175 int childCount = getTotalChildCount(); 1176 1177 isExpanded = false; 1178 adjustRowCountBy(-childCount); 1179 // We can do this because adjustRowBy won't descend 1180 // the children. 1181 adjustRowBy(-childCount, 0); 1182 } 1183 else 1184 isExpanded = false; 1185 1186 if(adjustRows && isVisible() && treeSelectionModel != null) 1187 treeSelectionModel.resetRowSelection(); 1188 } 1189 } 1190 1191 /** 1192 * Returns true if the receiver is a leaf. 1193 */ 1194 public boolean isLeaf() { 1195 TreeModel model = getModel(); 1196 1197 return (model != null) ? model.isLeaf(this.getUserObject()) : 1198 true; 1199 } 1200 1201 /** 1202 * Adds newChild to this nodes children at the appropriate location. 1203 * The location is determined from the childIndex of newChild. 1204 */ 1205 protected void addNode(FHTreeStateNode newChild) { 1206 boolean added = false; 1207 int childIndex = newChild.getChildIndex(); 1208 1209 for(int counter = 0, maxCounter = getChildCount(); 1210 counter < maxCounter; counter++) { 1211 if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() > 1212 childIndex) { 1213 added = true; 1214 insert(newChild, counter); 1215 counter = maxCounter; 1216 } 1217 } 1218 if(!added) 1219 add(newChild); 1220 } 1221 1222 /** 1223 * Removes the child at <code>modelIndex</code>. 1224 * <code>isChildVisible</code> should be true if the receiver 1225 * is visible and expanded. 1226 */ 1227 protected void removeChildAtModelIndex(int modelIndex, 1228 boolean isChildVisible) { 1229 FHTreeStateNode childNode = getChildAtModelIndex(modelIndex); 1230 1231 if(childNode != null) { 1232 int row = childNode.getRow(); 1233 int index = getIndex(childNode); 1234 1235 childNode.collapse(false); 1236 remove(index); 1237 adjustChildIndexs(index, -1); 1238 childCount--; 1239 if(isChildVisible) { 1240 // Adjust the rows. 1241 resetChildrenRowsFrom(row, index, modelIndex); 1242 } 1243 } 1244 else { 1245 int maxCounter = getChildCount(); 1246 FHTreeStateNode aChild; 1247 1248 for(int counter = 0; counter < maxCounter; counter++) { 1249 aChild = (FHTreeStateNode)getChildAt(counter); 1250 if(aChild.childIndex >= modelIndex) { 1251 if(isChildVisible) { 1252 adjustRowBy(-1, counter); 1253 adjustRowCountBy(-1); 1254 } 1255 // Since matched and children are always sorted by 1256 // index, no need to continue testing with the 1257 // above. 1258 for(; counter < maxCounter; counter++) 1259 ((FHTreeStateNode)getChildAt(counter)). 1260 childIndex--; 1261 childCount--; 1262 return; 1263 } 1264 } 1265 // No children to adjust, but it was a child, so we still need 1266 // to adjust nodes after this one. 1267 if(isChildVisible) { 1268 adjustRowBy(-1, maxCounter); 1269 adjustRowCountBy(-1); 1270 } 1271 childCount--; 1272 } 1273 } 1274 1275 /** 1276 * Adjusts the child indexs of the receivers children by 1277 * <code>amount</code>, starting at <code>index</code>. 1278 */ 1279 protected void adjustChildIndexs(int index, int amount) { 1280 for(int counter = index, maxCounter = getChildCount(); 1281 counter < maxCounter; counter++) { 1282 ((FHTreeStateNode)getChildAt(counter)).childIndex += amount; 1283 } 1284 } 1285 1286 /** 1287 * Messaged when a child has been inserted at index. For all the 1288 * children that have a childIndex ≥ index their index is incremented 1289 * by one. 1290 */ 1291 protected void childInsertedAtModelIndex(int index, 1292 boolean isExpandedAndVisible) { 1293 FHTreeStateNode aChild; 1294 int maxCounter = getChildCount(); 1295 1296 for(int counter = 0; counter < maxCounter; counter++) { 1297 aChild = (FHTreeStateNode)getChildAt(counter); 1298 if(aChild.childIndex >= index) { 1299 if(isExpandedAndVisible) { 1300 adjustRowBy(1, counter); 1301 adjustRowCountBy(1); 1302 } 1303 /* Since matched and children are always sorted by 1304 index, no need to continue testing with the above. */ 1305 for(; counter < maxCounter; counter++) 1306 ((FHTreeStateNode)getChildAt(counter)).childIndex++; 1307 childCount++; 1308 return; 1309 } 1310 } 1311 // No children to adjust, but it was a child, so we still need 1312 // to adjust nodes after this one. 1313 if(isExpandedAndVisible) { 1314 adjustRowBy(1, maxCounter); 1315 adjustRowCountBy(1); 1316 } 1317 childCount++; 1318 } 1319 1320 /** 1321 * Returns true if there is a row for <code>row</code>. 1322 * <code>nextRow</code> gives the bounds of the receiver. 1323 * Information about the found row is returned in <code>info</code>. 1324 * This should be invoked on root with <code>nextRow</code> set 1325 * to <code>getRowCount</code>(). 1326 */ 1327 protected boolean getPathForRow(int row, int nextRow, 1328 SearchInfo info) { 1329 if(this.row == row) { 1330 info.node = this; 1331 info.isNodeParentNode = false; 1332 info.childIndex = childIndex; 1333 return true; 1334 } 1335 1336 FHTreeStateNode child; 1337 FHTreeStateNode lastChild = null; 1338 1339 for(int counter = 0, maxCounter = getChildCount(); 1340 counter < maxCounter; counter++) { 1341 child = (FHTreeStateNode)getChildAt(counter); 1342 if(child.row > row) { 1343 if(counter == 0) { 1344 // No node exists for it, and is first. 1345 info.node = this; 1346 info.isNodeParentNode = true; 1347 info.childIndex = row - this.row - 1; 1348 return true; 1349 } 1350 else { 1351 // May have been in last child's bounds. 1352 int lastChildEndRow = 1 + child.row - 1353 (child.childIndex - lastChild.childIndex); 1354 1355 if(row < lastChildEndRow) { 1356 return lastChild.getPathForRow(row, 1357 lastChildEndRow, info); 1358 } 1359 // Between last child and child, but not in last child 1360 info.node = this; 1361 info.isNodeParentNode = true; 1362 info.childIndex = row - lastChildEndRow + 1363 lastChild.childIndex + 1; 1364 return true; 1365 } 1366 } 1367 lastChild = child; 1368 } 1369 1370 // Not in children, but we should have it, offset from 1371 // nextRow. 1372 if(lastChild != null) { 1373 int lastChildEndRow = nextRow - 1374 (childCount - lastChild.childIndex) + 1; 1375 1376 if(row < lastChildEndRow) { 1377 return lastChild.getPathForRow(row, lastChildEndRow, info); 1378 } 1379 // Between last child and child, but not in last child 1380 info.node = this; 1381 info.isNodeParentNode = true; 1382 info.childIndex = row - lastChildEndRow + 1383 lastChild.childIndex + 1; 1384 return true; 1385 } 1386 else { 1387 // No children. 1388 int retChildIndex = row - this.row - 1; 1389 1390 if(retChildIndex >= childCount) { 1391 return false; 1392 } 1393 info.node = this; 1394 info.isNodeParentNode = true; 1395 info.childIndex = retChildIndex; 1396 return true; 1397 } 1398 } 1399 1400 /** 1401 * Asks all the children of the receiver for their totalChildCount 1402 * and returns this value (plus stopIndex). 1403 */ 1404 protected int getCountTo(int stopIndex) { 1405 FHTreeStateNode aChild; 1406 int retCount = stopIndex + 1; 1407 1408 for(int counter = 0, maxCounter = getChildCount(); 1409 counter < maxCounter; counter++) { 1410 aChild = (FHTreeStateNode)getChildAt(counter); 1411 if(aChild.childIndex >= stopIndex) 1412 counter = maxCounter; 1413 else 1414 retCount += aChild.getTotalChildCount(); 1415 } 1416 if(parent != null) 1417 return retCount + ((FHTreeStateNode)getParent()) 1418 .getCountTo(childIndex); 1419 if(!isRootVisible()) 1420 return (retCount - 1); 1421 return retCount; 1422 } 1423 1424 /** 1425 * Returns the number of children that are expanded to 1426 * <code>stopIndex</code>. This does not include the number 1427 * of children that the child at <code>stopIndex</code> might 1428 * have. 1429 */ 1430 protected int getNumExpandedChildrenTo(int stopIndex) { 1431 FHTreeStateNode aChild; 1432 int retCount = stopIndex; 1433 1434 for(int counter = 0, maxCounter = getChildCount(); 1435 counter < maxCounter; counter++) { 1436 aChild = (FHTreeStateNode)getChildAt(counter); 1437 if(aChild.childIndex >= stopIndex) 1438 return retCount; 1439 else { 1440 retCount += aChild.getTotalChildCount(); 1441 } 1442 } 1443 return retCount; 1444 } 1445 1446 /** 1447 * Messaged when this node either expands or collapses. 1448 */ 1449 protected void didAdjustTree() { 1450 } 1451 1452 } // FixedHeightLayoutCache.FHTreeStateNode 1453 1454 1455 /** 1456 * Used as a placeholder when getting the path in FHTreeStateNodes. 1457 */ 1458 private class SearchInfo { 1459 protected FHTreeStateNode node; 1460 protected boolean isNodeParentNode; 1461 protected int childIndex; 1462 1463 protected TreePath getPath() { 1464 if(node == null) 1465 return null; 1466 1467 if(isNodeParentNode) 1468 return node.getTreePath().pathByAddingChild(treeModel.getChild 1469 (node.getUserObject(), 1470 childIndex)); 1471 return node.path; 1472 } 1473 } // FixedHeightLayoutCache.SearchInfo 1474 1475 1476 /** 1477 * An enumerator to iterate through visible nodes. 1478 */ 1479 // This is very similar to 1480 // VariableHeightTreeState.VisibleTreeStateNodeEnumeration 1481 private class VisibleFHTreeStateNodeEnumeration 1482 implements Enumeration<TreePath> 1483 { 1484 /** Parent thats children are being enumerated. */ 1485 protected FHTreeStateNode parent; 1486 /** Index of next child. An index of -1 signifies parent should be 1487 * visibled next. */ 1488 protected int nextIndex; 1489 /** Number of children in parent. */ 1490 protected int childCount; 1491 1492 protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) { 1493 this(node, -1); 1494 } 1495 1496 protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent, 1497 int startIndex) { 1498 this.parent = parent; 1499 this.nextIndex = startIndex; 1500 this.childCount = treeModel.getChildCount(this.parent. 1501 getUserObject()); 1502 } 1503 1504 /** 1505 * @return true if more visible nodes. 1506 */ 1507 public boolean hasMoreElements() { 1508 return (parent != null); 1509 } 1510 1511 /** 1512 * @return next visible TreePath. 1513 */ 1514 public TreePath nextElement() { 1515 if(!hasMoreElements()) 1516 throw new NoSuchElementException("No more visible paths"); 1517 1518 TreePath retObject; 1519 1520 if(nextIndex == -1) 1521 retObject = parent.getTreePath(); 1522 else { 1523 FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex); 1524 1525 if(node == null) 1526 retObject = parent.getTreePath().pathByAddingChild 1527 (treeModel.getChild(parent.getUserObject(), 1528 nextIndex)); 1529 else 1530 retObject = node.getTreePath(); 1531 } 1532 updateNextObject(); 1533 return retObject; 1534 } 1535 1536 /** 1537 * Determines the next object by invoking <code>updateNextIndex</code> 1538 * and if not succesful <code>findNextValidParent</code>. 1539 */ 1540 protected void updateNextObject() { 1541 if(!updateNextIndex()) { 1542 findNextValidParent(); 1543 } 1544 } 1545 1546 /** 1547 * Finds the next valid parent, this should be called when nextIndex 1548 * is beyond the number of children of the current parent. 1549 */ 1550 protected boolean findNextValidParent() { 1551 if(parent == root) { 1552 // mark as invalid! 1553 parent = null; 1554 return false; 1555 } 1556 while(parent != null) { 1557 FHTreeStateNode newParent = (FHTreeStateNode)parent. 1558 getParent(); 1559 1560 if(newParent != null) { 1561 nextIndex = parent.childIndex; 1562 parent = newParent; 1563 childCount = treeModel.getChildCount 1564 (parent.getUserObject()); 1565 if(updateNextIndex()) 1566 return true; 1567 } 1568 else 1569 parent = null; 1570 } 1571 return false; 1572 } 1573 1574 /** 1575 * Updates <code>nextIndex</code> returning false if it is beyond 1576 * the number of children of parent. 1577 */ 1578 protected boolean updateNextIndex() { 1579 // nextIndex == -1 identifies receiver, make sure is expanded 1580 // before descend. 1581 if(nextIndex == -1 && !parent.isExpanded()) { 1582 return false; 1583 } 1584 1585 // Check that it can have kids 1586 if(childCount == 0) { 1587 return false; 1588 } 1589 // Make sure next index not beyond child count. 1590 else if(++nextIndex >= childCount) { 1591 return false; 1592 } 1593 1594 FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex); 1595 1596 if(child != null && child.isExpanded()) { 1597 parent = child; 1598 nextIndex = -1; 1599 childCount = treeModel.getChildCount(child.getUserObject()); 1600 } 1601 return true; 1602 } 1603 } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration 1604 }