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 }