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