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