1 /*
   2  * Copyright (c) 1998, 2014, 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 import java.util.Vector;
  35 
  36 import sun.swing.SwingUtilities2;
  37 
  38 /**
  39  * NOTE: This will become more open in a future release.
  40  * <p>
  41  * <strong>Warning:</strong>
  42  * Serialized objects of this class will not be compatible with
  43  * future Swing releases. The current serialization support is
  44  * appropriate for short term storage or RMI between applications running
  45  * the same version of Swing.  As of 1.4, support for long term storage
  46  * of all JavaBeans&trade;
  47  * has been added to the <code>java.beans</code> package.
  48  * Please see {@link java.beans.XMLEncoder}.
  49  *
  50  * @author Rob Davis
  51  * @author Ray Ryan
  52  * @author Scott Violet
  53  */
  54 @SuppressWarnings("serial") // Same-version serialization only
  55 public class VariableHeightLayoutCache extends AbstractLayoutCache {
  56     /**
  57      * The array of nodes that are currently visible, in the order they
  58      * are displayed.
  59      */
  60     private Vector<Object> visibleNodes;
  61 
  62     /**
  63      * This is set to true if one of the entries has an invalid size.
  64      */
  65     private boolean           updateNodeSizes;
  66 
  67     /**
  68      * The root node of the internal cache of nodes that have been shown.
  69      * If the treeModel is vending a network rather than a true tree,
  70      * there may be one cached node for each path to a modeled node.
  71      */
  72     private TreeStateNode     root;
  73 
  74     /**
  75      * Used in getting sizes for nodes to avoid creating a new Rectangle
  76      * every time a size is needed.
  77      */
  78     private Rectangle         boundsBuffer;
  79 
  80     /**
  81      * Maps from <code>TreePath</code> to a <code>TreeStateNode</code>.
  82      */
  83     private Hashtable<TreePath, TreeStateNode> treePathMapping;
  84 
  85     /**
  86      * A stack of stacks.
  87      */
  88     private Stack<Stack<TreePath>> tempStacks;
  89 
  90 
  91     public VariableHeightLayoutCache() {
  92         super();
  93         tempStacks = new Stack<Stack<TreePath>>();
  94         visibleNodes = new Vector<Object>();
  95         boundsBuffer = new Rectangle();
  96         treePathMapping = new Hashtable<TreePath, TreeStateNode>();
  97     }
  98 
  99     /**
 100      * Sets the <code>TreeModel</code> that will provide the data.
 101      *
 102      * @param newModel the <code>TreeModel</code> that is to provide the data
 103      * @beaninfo
 104      *        bound: true
 105      *  description: The TreeModel that will provide the data.
 106      */
 107     public void setModel(TreeModel newModel) {
 108         super.setModel(newModel);
 109         rebuild(false);
 110     }
 111 
 112     /**
 113      * Determines whether or not the root node from
 114      * the <code>TreeModel</code> is visible.
 115      *
 116      * @param rootVisible true if the root node of the tree is to be displayed
 117      * @see #rootVisible
 118      * @beaninfo
 119      *        bound: true
 120      *  description: Whether or not the root node
 121      *               from the TreeModel is visible.
 122      */
 123     public void setRootVisible(boolean rootVisible) {
 124         if(isRootVisible() != rootVisible && root != null) {
 125             if(rootVisible) {
 126                 root.updatePreferredSize(0);
 127                 visibleNodes.insertElementAt(root, 0);
 128             }
 129             else if(visibleNodes.size() > 0) {
 130                 visibleNodes.removeElementAt(0);
 131                 if(treeSelectionModel != null)
 132                     treeSelectionModel.removeSelectionPath
 133                         (root.getTreePath());
 134             }
 135             if(treeSelectionModel != null)
 136                 treeSelectionModel.resetRowSelection();
 137             if(getRowCount() > 0)
 138                 getNode(0).setYOrigin(0);
 139             updateYLocationsFrom(0);
 140             visibleNodesChanged();
 141         }
 142         super.setRootVisible(rootVisible);
 143     }
 144 
 145     /**
 146      * Sets the height of each cell.  If the specified value
 147      * is less than or equal to zero the current cell renderer is
 148      * queried for each row's height.
 149      *
 150      * @param rowHeight the height of each cell, in pixels
 151      * @beaninfo
 152      *        bound: true
 153      *  description: The height of each cell.
 154      */
 155     public void setRowHeight(int rowHeight) {
 156         if(rowHeight != getRowHeight()) {
 157             super.setRowHeight(rowHeight);
 158             invalidateSizes();
 159             this.visibleNodesChanged();
 160         }
 161     }
 162 
 163     /**
 164      * Sets the renderer that is responsible for drawing nodes in the tree.
 165      * @param nd the renderer
 166      */
 167     public void setNodeDimensions(NodeDimensions nd) {
 168         super.setNodeDimensions(nd);
 169         invalidateSizes();
 170         visibleNodesChanged();
 171     }
 172 
 173     /**
 174      * Marks the path <code>path</code> expanded state to
 175      * <code>isExpanded</code>.
 176      * @param path the <code>TreePath</code> of interest
 177      * @param isExpanded true if the path should be expanded, otherwise false
 178      */
 179     public void setExpandedState(TreePath path, boolean isExpanded) {
 180         if(path != null) {
 181             if(isExpanded)
 182                 ensurePathIsExpanded(path, true);
 183             else {
 184                 TreeStateNode        node = getNodeForPath(path, false, true);
 185 
 186                 if(node != null) {
 187                     node.makeVisible();
 188                     node.collapse();
 189                 }
 190             }
 191         }
 192     }
 193 
 194     /**
 195      * Returns true if the path is expanded, and visible.
 196      * @return true if the path is expanded and visible, otherwise false
 197      */
 198     public boolean getExpandedState(TreePath path) {
 199         TreeStateNode       node = getNodeForPath(path, true, false);
 200 
 201         return (node != null) ? (node.isVisible() && node.isExpanded()) :
 202                                  false;
 203     }
 204 
 205     /**
 206       * Returns the <code>Rectangle</code> enclosing the label portion
 207       * into which the item identified by <code>path</code> will be drawn.
 208       *
 209       * @param path  the path to be drawn
 210       * @param placeIn the bounds of the enclosing rectangle
 211       * @return the bounds of the enclosing rectangle or <code>null</code>
 212       *    if the node could not be ascertained
 213       */
 214     public Rectangle getBounds(TreePath path, Rectangle placeIn) {
 215         TreeStateNode       node = getNodeForPath(path, true, false);
 216 
 217         if(node != null) {
 218             if(updateNodeSizes)
 219                 updateNodeSizes(false);
 220             return node.getNodeBounds(placeIn);
 221         }
 222         return null;
 223     }
 224 
 225     /**
 226       * Returns the path for <code>row</code>.  If <code>row</code>
 227       * is not visible, <code>null</code> is returned.
 228       *
 229       * @param row the location of interest
 230       * @return the path for <code>row</code>, or <code>null</code>
 231       * if <code>row</code> is not visible
 232       */
 233     public TreePath getPathForRow(int row) {
 234         if(row >= 0 && row < getRowCount()) {
 235             return getNode(row).getTreePath();
 236         }
 237         return null;
 238     }
 239 
 240     /**
 241       * Returns the row where the last item identified in path is visible.
 242       * Will return -1 if any of the elements in path are not
 243       * currently visible.
 244       *
 245       * @param path the <code>TreePath</code> of interest
 246       * @return the row where the last item in path is visible
 247       */
 248     public int getRowForPath(TreePath path) {
 249         if(path == null)
 250             return -1;
 251 
 252         TreeStateNode    visNode = getNodeForPath(path, true, false);
 253 
 254         if(visNode != null)
 255             return visNode.getRow();
 256         return -1;
 257     }
 258 
 259     /**
 260      * Returns the number of visible rows.
 261      * @return the number of visible rows
 262      */
 263     public int getRowCount() {
 264         return visibleNodes.size();
 265     }
 266 
 267     /**
 268      * Instructs the <code>LayoutCache</code> that the bounds for
 269      * <code>path</code> are invalid, and need to be updated.
 270      *
 271      * @param path the <code>TreePath</code> which is now invalid
 272      */
 273     public void invalidatePathBounds(TreePath path) {
 274         TreeStateNode       node = getNodeForPath(path, true, false);
 275 
 276         if(node != null) {
 277             node.markSizeInvalid();
 278             if(node.isVisible())
 279                 updateYLocationsFrom(node.getRow());
 280         }
 281     }
 282 
 283     /**
 284      * Returns the preferred height.
 285      * @return the preferred height
 286      */
 287     public int getPreferredHeight() {
 288         // Get the height
 289         int           rowCount = getRowCount();
 290 
 291         if(rowCount > 0) {
 292             TreeStateNode  node = getNode(rowCount - 1);
 293 
 294             return node.getYOrigin() + node.getPreferredHeight();
 295         }
 296         return 0;
 297     }
 298 
 299     /**
 300      * Returns the preferred width and height for the region in
 301      * <code>visibleRegion</code>.
 302      *
 303      * @param bounds  the region being queried
 304      */
 305     public int getPreferredWidth(Rectangle bounds) {
 306         if(updateNodeSizes)
 307             updateNodeSizes(false);
 308 
 309         return getMaxNodeWidth();
 310     }
 311 
 312     /**
 313       * Returns the path to the node that is closest to x,y.  If
 314       * there is nothing currently visible this will return <code>null</code>,
 315       * otherwise it will always return a valid path.
 316       * If you need to test if the
 317       * returned object is exactly at x, y you should get the bounds for
 318       * the returned path and test x, y against that.
 319       *
 320       * @param x  the x-coordinate
 321       * @param y  the y-coordinate
 322       * @return the path to the node that is closest to x, y
 323       */
 324     public TreePath getPathClosestTo(int x, int y) {
 325         if(getRowCount() == 0)
 326             return null;
 327 
 328         if(updateNodeSizes)
 329             updateNodeSizes(false);
 330 
 331         int                row = getRowContainingYLocation(y);
 332 
 333         return getNode(row).getTreePath();
 334     }
 335 
 336     /**
 337      * Returns an <code>Enumerator</code> that increments over the visible paths
 338      * starting at the passed in location. The ordering of the enumeration
 339      * is based on how the paths are displayed.
 340      *
 341      * @param path the location in the <code>TreePath</code> to start
 342      * @return an <code>Enumerator</code> that increments over the visible
 343      *     paths
 344      */
 345     public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
 346         TreeStateNode       node = getNodeForPath(path, true, false);
 347 
 348         if(node != null) {
 349             return new VisibleTreeStateNodeEnumeration(node);
 350         }
 351         return null;
 352     }
 353 
 354     /**
 355      * Returns the number of visible children for <code>path</code>.
 356      * @return the number of visible children for <code>path</code>
 357      */
 358     public int getVisibleChildCount(TreePath path) {
 359         TreeStateNode         node = getNodeForPath(path, true, false);
 360 
 361         return (node != null) ? node.getVisibleChildCount() : 0;
 362     }
 363 
 364     /**
 365      * Informs the <code>TreeState</code> that it needs to recalculate
 366      * all the sizes it is referencing.
 367      */
 368     public void invalidateSizes() {
 369         if(root != null)
 370             root.deepMarkSizeInvalid();
 371         if(!isFixedRowHeight() && visibleNodes.size() > 0) {
 372             updateNodeSizes(true);
 373         }
 374     }
 375 
 376     /**
 377       * Returns true if the value identified by <code>path</code> is
 378       * currently expanded.
 379       * @return true if the value identified by <code>path</code> is
 380       *    currently expanded
 381       */
 382     public boolean isExpanded(TreePath path) {
 383         if(path != null) {
 384             TreeStateNode     lastNode = getNodeForPath(path, true, false);
 385 
 386             return (lastNode != null && lastNode.isExpanded());
 387         }
 388         return false;
 389     }
 390 
 391     //
 392     // TreeModelListener methods
 393     //
 394 
 395     /**
 396      * Invoked after a node (or a set of siblings) has changed in some
 397      * way. The node(s) have not changed locations in the tree or
 398      * altered their children arrays, but other attributes have
 399      * changed and may affect presentation. Example: the name of a
 400      * file has changed, but it is in the same location in the file
 401      * system.
 402      *
 403      * <p><code>e.path</code> returns the path the parent of the
 404      * changed node(s).
 405      *
 406      * <p><code>e.childIndices</code> returns the index(es) of the
 407      * changed node(s).
 408      *
 409      * @param e the <code>TreeModelEvent</code> of interest
 410      */
 411     public void treeNodesChanged(TreeModelEvent e) {
 412         if(e != null) {
 413             int               changedIndexs[];
 414             TreeStateNode     changedNode;
 415 
 416             changedIndexs = e.getChildIndices();
 417             changedNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
 418             if(changedNode != null) {
 419                 Object            changedValue = changedNode.getValue();
 420 
 421                 /* Update the size of the changed node, as well as all the
 422                    child indexs that are passed in. */
 423                 changedNode.updatePreferredSize();
 424                 if(changedNode.hasBeenExpanded() && changedIndexs != null) {
 425                     int                counter;
 426                     TreeStateNode      changedChildNode;
 427 
 428                     for(counter = 0; counter < changedIndexs.length;
 429                         counter++) {
 430                         changedChildNode = (TreeStateNode)changedNode
 431                                     .getChildAt(changedIndexs[counter]);
 432                         /* Reset the user object. */
 433                         changedChildNode.setUserObject
 434                                     (treeModel.getChild(changedValue,
 435                                                      changedIndexs[counter]));
 436                         changedChildNode.updatePreferredSize();
 437                     }
 438                 }
 439                 else if (changedNode == root) {
 440                     // Null indicies for root indicates it changed.
 441                     changedNode.updatePreferredSize();
 442                 }
 443                 if(!isFixedRowHeight()) {
 444                     int          aRow = changedNode.getRow();
 445 
 446                     if(aRow != -1)
 447                         this.updateYLocationsFrom(aRow);
 448                 }
 449                 this.visibleNodesChanged();
 450             }
 451         }
 452     }
 453 
 454 
 455     /**
 456      * Invoked after nodes have been inserted into the tree.
 457      *
 458      * <p><code>e.path</code> returns the parent of the new nodes.
 459      * <p><code>e.childIndices</code> returns the indices of the new nodes in
 460      * ascending order.
 461      *
 462      * @param e the <code>TreeModelEvent</code> of interest
 463      */
 464     public void treeNodesInserted(TreeModelEvent e) {
 465         if(e != null) {
 466             int               changedIndexs[];
 467             TreeStateNode     changedParentNode;
 468 
 469             changedIndexs = e.getChildIndices();
 470             changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
 471             /* Only need to update the children if the node has been
 472                expanded once. */
 473             // PENDING(scott): make sure childIndexs is sorted!
 474             if(changedParentNode != null && changedIndexs != null &&
 475                changedIndexs.length > 0) {
 476                 if(changedParentNode.hasBeenExpanded()) {
 477                     boolean            makeVisible;
 478                     int                counter;
 479                     Object             changedParent;
 480                     TreeStateNode      newNode;
 481                     int                oldChildCount = changedParentNode.
 482                                           getChildCount();
 483 
 484                     changedParent = changedParentNode.getValue();
 485                     makeVisible = ((changedParentNode == root &&
 486                                     !rootVisible) ||
 487                                    (changedParentNode.getRow() != -1 &&
 488                                     changedParentNode.isExpanded()));
 489                     for(counter = 0;counter < changedIndexs.length;counter++)
 490                     {
 491                         newNode = this.createNodeAt(changedParentNode,
 492                                                     changedIndexs[counter]);
 493                     }
 494                     if(oldChildCount == 0) {
 495                         // Update the size of the parent.
 496                         changedParentNode.updatePreferredSize();
 497                     }
 498                     if(treeSelectionModel != null)
 499                         treeSelectionModel.resetRowSelection();
 500                     /* Update the y origins from the index of the parent
 501                        to the end of the visible rows. */
 502                     if(!isFixedRowHeight() && (makeVisible ||
 503                                                (oldChildCount == 0 &&
 504                                         changedParentNode.isVisible()))) {
 505                         if(changedParentNode == root)
 506                             this.updateYLocationsFrom(0);
 507                         else
 508                             this.updateYLocationsFrom(changedParentNode.
 509                                                       getRow());
 510                         this.visibleNodesChanged();
 511                     }
 512                     else if(makeVisible)
 513                         this.visibleNodesChanged();
 514                 }
 515                 else if(treeModel.getChildCount(changedParentNode.getValue())
 516                         - changedIndexs.length == 0) {
 517                     changedParentNode.updatePreferredSize();
 518                     if(!isFixedRowHeight() && changedParentNode.isVisible())
 519                         updateYLocationsFrom(changedParentNode.getRow());
 520                 }
 521             }
 522         }
 523     }
 524 
 525     /**
 526      * Invoked after nodes have been removed from the tree.  Note that
 527      * if a subtree is removed from the tree, this method may only be
 528      * invoked once for the root of the removed subtree, not once for
 529      * each individual set of siblings removed.
 530      *
 531      * <p><code>e.path</code> returns the former parent of the deleted nodes.
 532      *
 533      * <p><code>e.childIndices</code> returns the indices the nodes had
 534      * before they were deleted in ascending order.
 535      *
 536      * @param e the <code>TreeModelEvent</code> of interest
 537      */
 538     public void treeNodesRemoved(TreeModelEvent e) {
 539         if(e != null) {
 540             int               changedIndexs[];
 541             TreeStateNode     changedParentNode;
 542 
 543             changedIndexs = e.getChildIndices();
 544             changedParentNode = getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
 545             // PENDING(scott): make sure that changedIndexs are sorted in
 546             // ascending order.
 547             if(changedParentNode != null && changedIndexs != null &&
 548                changedIndexs.length > 0) {
 549                 if(changedParentNode.hasBeenExpanded()) {
 550                     boolean            makeInvisible;
 551                     int                counter;
 552                     int                removedRow;
 553                     TreeStateNode      removedNode;
 554 
 555                     makeInvisible = ((changedParentNode == root &&
 556                                       !rootVisible) ||
 557                                      (changedParentNode.getRow() != -1 &&
 558                                       changedParentNode.isExpanded()));
 559                     for(counter = changedIndexs.length - 1;counter >= 0;
 560                         counter--) {
 561                         removedNode = (TreeStateNode)changedParentNode.
 562                                 getChildAt(changedIndexs[counter]);
 563                         if(removedNode.isExpanded()) {
 564                             removedNode.collapse(false);
 565                         }
 566 
 567                         /* Let the selection model now. */
 568                         if(makeInvisible) {
 569                             removedRow = removedNode.getRow();
 570                             if(removedRow != -1) {
 571                                 visibleNodes.removeElementAt(removedRow);
 572                             }
 573                         }
 574                         changedParentNode.remove(changedIndexs[counter]);
 575                     }
 576                     if(changedParentNode.getChildCount() == 0) {
 577                         // Update the size of the parent.
 578                         changedParentNode.updatePreferredSize();
 579                         if (changedParentNode.isExpanded() &&
 580                                    changedParentNode.isLeaf()) {
 581                             // Node has become a leaf, collapse it.
 582                             changedParentNode.collapse(false);
 583                         }
 584                     }
 585                     if(treeSelectionModel != null)
 586                         treeSelectionModel.resetRowSelection();
 587                     /* Update the y origins from the index of the parent
 588                        to the end of the visible rows. */
 589                     if(!isFixedRowHeight() && (makeInvisible ||
 590                                (changedParentNode.getChildCount() == 0 &&
 591                                 changedParentNode.isVisible()))) {
 592                         if(changedParentNode == root) {
 593                             /* It is possible for first row to have been
 594                                removed if the root isn't visible, in which
 595                                case ylocations will be off! */
 596                             if(getRowCount() > 0)
 597                                 getNode(0).setYOrigin(0);
 598                             updateYLocationsFrom(0);
 599                         }
 600                         else
 601                             updateYLocationsFrom(changedParentNode.getRow());
 602                         this.visibleNodesChanged();
 603                     }
 604                     else if(makeInvisible)
 605                         this.visibleNodesChanged();
 606                 }
 607                 else if(treeModel.getChildCount(changedParentNode.getValue())
 608                         == 0) {
 609                     changedParentNode.updatePreferredSize();
 610                     if(!isFixedRowHeight() && changedParentNode.isVisible())
 611                         this.updateYLocationsFrom(changedParentNode.getRow());
 612                 }
 613             }
 614         }
 615     }
 616 
 617     /**
 618      * Invoked after the tree has drastically changed structure from a
 619      * given node down.  If the path returned by <code>e.getPath</code>
 620      * is of length one and the first element does not identify the
 621      * current root node the first element should become the new root
 622      * of the tree.
 623      *
 624      * <p><code>e.path</code> holds the path to the node.
 625      * <p><code>e.childIndices</code> returns <code>null</code>.
 626      *
 627      * @param e the <code>TreeModelEvent</code> of interest
 628      */
 629     public void treeStructureChanged(TreeModelEvent e) {
 630         if(e != null)
 631         {
 632             TreePath          changedPath = SwingUtilities2.getTreePath(e, getModel());
 633             TreeStateNode     changedNode;
 634 
 635             changedNode = getNodeForPath(changedPath, false, false);
 636 
 637             // Check if root has changed, either to a null root, or
 638             // to an entirely new root.
 639             if(changedNode == root ||
 640                (changedNode == null &&
 641                 ((changedPath == null && treeModel != null &&
 642                   treeModel.getRoot() == null) ||
 643                  (changedPath != null && changedPath.getPathCount() == 1)))) {
 644                 rebuild(true);
 645             }
 646             else if(changedNode != null) {
 647                 int                              nodeIndex, oldRow;
 648                 TreeStateNode                    newNode, parent;
 649                 boolean                          wasExpanded, wasVisible;
 650                 int                              newIndex;
 651 
 652                 wasExpanded = changedNode.isExpanded();
 653                 wasVisible = (changedNode.getRow() != -1);
 654                 /* Remove the current node and recreate a new one. */
 655                 parent = (TreeStateNode)changedNode.getParent();
 656                 nodeIndex = parent.getIndex(changedNode);
 657                 if(wasVisible && wasExpanded) {
 658                     changedNode.collapse(false);
 659                 }
 660                 if(wasVisible)
 661                     visibleNodes.removeElement(changedNode);
 662                 changedNode.removeFromParent();
 663                 createNodeAt(parent, nodeIndex);
 664                 newNode = (TreeStateNode)parent.getChildAt(nodeIndex);
 665                 if(wasVisible && wasExpanded)
 666                     newNode.expand(false);
 667                 newIndex = newNode.getRow();
 668                 if(!isFixedRowHeight() && wasVisible) {
 669                     if(newIndex == 0)
 670                         updateYLocationsFrom(newIndex);
 671                     else
 672                         updateYLocationsFrom(newIndex - 1);
 673                     this.visibleNodesChanged();
 674                 }
 675                 else if(wasVisible)
 676                     this.visibleNodesChanged();
 677             }
 678         }
 679     }
 680 
 681 
 682     //
 683     // Local methods
 684     //
 685 
 686     private void visibleNodesChanged() {
 687     }
 688 
 689     /**
 690      * Adds a mapping for node.
 691      */
 692     private void addMapping(TreeStateNode node) {
 693         treePathMapping.put(node.getTreePath(), node);
 694     }
 695 
 696     /**
 697      * Removes the mapping for a previously added node.
 698      */
 699     private void removeMapping(TreeStateNode node) {
 700         treePathMapping.remove(node.getTreePath());
 701     }
 702 
 703     /**
 704      * Returns the node previously added for <code>path</code>. This may
 705      * return null, if you to create a node use getNodeForPath.
 706      */
 707     private TreeStateNode getMapping(TreePath path) {
 708         return treePathMapping.get(path);
 709     }
 710 
 711     /**
 712      * Retursn the bounds for row, <code>row</code> by reference in
 713      * <code>placeIn</code>. If <code>placeIn</code> is null a new
 714      * Rectangle will be created and returned.
 715      */
 716     private Rectangle getBounds(int row, Rectangle placeIn) {
 717         if(updateNodeSizes)
 718             updateNodeSizes(false);
 719 
 720         if(row >= 0 && row < getRowCount()) {
 721             return getNode(row).getNodeBounds(placeIn);
 722         }
 723         return null;
 724     }
 725 
 726     /**
 727      * Completely rebuild the tree, all expanded state, and node caches are
 728      * removed. All nodes are collapsed, except the root.
 729      */
 730     private void rebuild(boolean clearSelection) {
 731         Object rootObject;
 732 
 733         treePathMapping.clear();
 734         if(treeModel != null && (rootObject = treeModel.getRoot()) != null) {
 735             root = createNodeForValue(rootObject);
 736             root.path = new TreePath(rootObject);
 737             addMapping(root);
 738             root.updatePreferredSize(0);
 739             visibleNodes.removeAllElements();
 740             if (isRootVisible())
 741                 visibleNodes.addElement(root);
 742             if(!root.isExpanded())
 743                 root.expand();
 744             else {
 745                 Enumeration<?> cursor = root.children();
 746                 while(cursor.hasMoreElements()) {
 747                     visibleNodes.addElement(cursor.nextElement());
 748                 }
 749                 if(!isFixedRowHeight())
 750                     updateYLocationsFrom(0);
 751             }
 752         }
 753         else {
 754             visibleNodes.removeAllElements();
 755             root = null;
 756         }
 757         if(clearSelection && treeSelectionModel != null) {
 758             treeSelectionModel.clearSelection();
 759         }
 760         this.visibleNodesChanged();
 761     }
 762 
 763     /**
 764       * Creates a new node to represent the node at <I>childIndex</I> in
 765       * <I>parent</I>s children.  This should be called if the node doesn't
 766       * already exist and <I>parent</I> has been expanded at least once.
 767       * The newly created node will be made visible if <I>parent</I> is
 768       * currently expanded.  This does not update the position of any
 769       * cells, nor update the selection if it needs to be.  If succesful
 770       * in creating the new TreeStateNode, it is returned, otherwise
 771       * null is returned.
 772       */
 773     private TreeStateNode createNodeAt(TreeStateNode parent,
 774                                          int childIndex) {
 775         boolean                isParentRoot;
 776         Object                 newValue;
 777         TreeStateNode          newChildNode;
 778 
 779         newValue = treeModel.getChild(parent.getValue(), childIndex);
 780         newChildNode = createNodeForValue(newValue);
 781         parent.insert(newChildNode, childIndex);
 782         newChildNode.updatePreferredSize(-1);
 783         isParentRoot = (parent == root);
 784         if(newChildNode != null && parent.isExpanded() &&
 785            (parent.getRow() != -1 || isParentRoot)) {
 786             int                 newRow;
 787 
 788             /* Find the new row to insert this newly visible node at. */
 789             if(childIndex == 0) {
 790                 if(isParentRoot && !isRootVisible())
 791                     newRow = 0;
 792                 else
 793                     newRow = parent.getRow() + 1;
 794             }
 795             else if(childIndex == parent.getChildCount())
 796                 newRow = parent.getLastVisibleNode().getRow() + 1;
 797             else {
 798                 TreeStateNode          previousNode;
 799 
 800                 previousNode = (TreeStateNode)parent.
 801                     getChildAt(childIndex - 1);
 802                 newRow = previousNode.getLastVisibleNode().getRow() + 1;
 803             }
 804             visibleNodes.insertElementAt(newChildNode, newRow);
 805         }
 806         return newChildNode;
 807     }
 808 
 809     /**
 810       * Returns the TreeStateNode identified by path.  This mirrors
 811       * the behavior of getNodeForPath, but tries to take advantage of
 812       * path if it is an instance of AbstractTreePath.
 813       */
 814     private TreeStateNode getNodeForPath(TreePath path,
 815                                            boolean onlyIfVisible,
 816                                            boolean shouldCreate) {
 817         if(path != null) {
 818             TreeStateNode      node;
 819 
 820             node = getMapping(path);
 821             if(node != null) {
 822                 if(onlyIfVisible && !node.isVisible())
 823                     return null;
 824                 return node;
 825             }
 826 
 827             // Check all the parent paths, until a match is found.
 828             Stack<TreePath> paths;
 829 
 830             if(tempStacks.size() == 0) {
 831                 paths = new Stack<TreePath>();
 832             }
 833             else {
 834                 paths = tempStacks.pop();
 835             }
 836 
 837             try {
 838                 paths.push(path);
 839                 path = path.getParentPath();
 840                 node = null;
 841                 while(path != null) {
 842                     node = getMapping(path);
 843                     if(node != null) {
 844                         // Found a match, create entries for all paths in
 845                         // paths.
 846                         while(node != null && paths.size() > 0) {
 847                             path = paths.pop();
 848                             node.getLoadedChildren(shouldCreate);
 849 
 850                             int            childIndex = treeModel.
 851                                       getIndexOfChild(node.getUserObject(),
 852                                                   path.getLastPathComponent());
 853 
 854                             if(childIndex == -1 ||
 855                                childIndex >= node.getChildCount() ||
 856                                (onlyIfVisible && !node.isVisible())) {
 857                                 node = null;
 858                             }
 859                             else
 860                                 node = (TreeStateNode)node.getChildAt
 861                                                (childIndex);
 862                         }
 863                         return node;
 864                     }
 865                     paths.push(path);
 866                     path = path.getParentPath();
 867                 }
 868             }
 869             finally {
 870                 paths.removeAllElements();
 871                 tempStacks.push(paths);
 872             }
 873             // If we get here it means they share a different root!
 874             // We could throw an exception...
 875         }
 876         return null;
 877     }
 878 
 879     /**
 880       * Updates the y locations of all of the visible nodes after
 881       * location.
 882       */
 883     private void updateYLocationsFrom(int location) {
 884         if(location >= 0 && location < getRowCount()) {
 885             int                    counter, maxCounter, newYOrigin;
 886             TreeStateNode          aNode;
 887 
 888             aNode = getNode(location);
 889             newYOrigin = aNode.getYOrigin() + aNode.getPreferredHeight();
 890             for(counter = location + 1, maxCounter = visibleNodes.size();
 891                 counter < maxCounter;counter++) {
 892                 aNode = (TreeStateNode)visibleNodes.
 893                     elementAt(counter);
 894                 aNode.setYOrigin(newYOrigin);
 895                 newYOrigin += aNode.getPreferredHeight();
 896             }
 897         }
 898     }
 899 
 900     /**
 901       * Resets the y origin of all the visible nodes as well as messaging
 902       * all the visible nodes to updatePreferredSize().  You should not
 903       * normally have to call this.  Expanding and contracting the nodes
 904       * automaticly adjusts the locations.
 905       * updateAll determines if updatePreferredSize() is call on all nodes
 906       * or just those that don't have a valid size.
 907       */
 908     private void updateNodeSizes(boolean updateAll) {
 909         int                      aY, counter, maxCounter;
 910         TreeStateNode            node;
 911 
 912         updateNodeSizes = false;
 913         for(aY = counter = 0, maxCounter = visibleNodes.size();
 914             counter < maxCounter; counter++) {
 915             node = (TreeStateNode)visibleNodes.elementAt(counter);
 916             node.setYOrigin(aY);
 917             if(updateAll || !node.hasValidSize())
 918                 node.updatePreferredSize(counter);
 919             aY += node.getPreferredHeight();
 920         }
 921     }
 922 
 923     /**
 924       * Returns the index of the row containing location.  If there
 925       * are no rows, -1 is returned.  If location is beyond the last
 926       * row index, the last row index is returned.
 927       */
 928     private int getRowContainingYLocation(int location) {
 929         if(isFixedRowHeight()) {
 930             if(getRowCount() == 0)
 931                 return -1;
 932             return Math.max(0, Math.min(getRowCount() - 1,
 933                                         location / getRowHeight()));
 934         }
 935 
 936         int                    max, maxY, mid, min, minY;
 937         TreeStateNode          node;
 938 
 939         if((max = getRowCount()) <= 0)
 940             return -1;
 941         mid = min = 0;
 942         while(min < max) {
 943             mid = (max - min) / 2 + min;
 944             node = (TreeStateNode)visibleNodes.elementAt(mid);
 945             minY = node.getYOrigin();
 946             maxY = minY + node.getPreferredHeight();
 947             if(location < minY) {
 948                 max = mid - 1;
 949             }
 950             else if(location >= maxY) {
 951                 min = mid + 1;
 952             }
 953             else
 954                 break;
 955         }
 956         if(min == max) {
 957             mid = min;
 958             if(mid >= getRowCount())
 959                 mid = getRowCount() - 1;
 960         }
 961         return mid;
 962     }
 963 
 964     /**
 965      * Ensures that all the path components in path are expanded, accept
 966      * for the last component which will only be expanded if expandLast
 967      * is true.
 968      * Returns true if succesful in finding the path.
 969      */
 970     private void ensurePathIsExpanded(TreePath aPath, boolean expandLast) {
 971         if(aPath != null) {
 972             // Make sure the last entry isn't a leaf.
 973             if(treeModel.isLeaf(aPath.getLastPathComponent())) {
 974                 aPath = aPath.getParentPath();
 975                 expandLast = true;
 976             }
 977             if(aPath != null) {
 978                 TreeStateNode     lastNode = getNodeForPath(aPath, false,
 979                                                             true);
 980 
 981                 if(lastNode != null) {
 982                     lastNode.makeVisible();
 983                     if(expandLast)
 984                         lastNode.expand();
 985                 }
 986             }
 987         }
 988     }
 989 
 990     /**
 991      * Returns the AbstractTreeUI.VisibleNode displayed at the given row
 992      */
 993     private TreeStateNode getNode(int row) {
 994         return (TreeStateNode)visibleNodes.elementAt(row);
 995     }
 996 
 997     /**
 998       * Returns the maximum node width.
 999       */
1000     private int getMaxNodeWidth() {
1001         int                     maxWidth = 0;
1002         int                     nodeWidth;
1003         int                     counter;
1004         TreeStateNode           node;
1005 
1006         for(counter = getRowCount() - 1;counter >= 0;counter--) {
1007             node = this.getNode(counter);
1008             nodeWidth = node.getPreferredWidth() + node.getXOrigin();
1009             if(nodeWidth > maxWidth)
1010                 maxWidth = nodeWidth;
1011         }
1012         return maxWidth;
1013     }
1014 
1015     /**
1016       * Responsible for creating a TreeStateNode that will be used
1017       * to track display information about value.
1018       */
1019     private TreeStateNode createNodeForValue(Object value) {
1020         return new TreeStateNode(value);
1021     }
1022 
1023 
1024     /**
1025      * TreeStateNode is used to keep track of each of
1026      * the nodes that have been expanded. This will also cache the preferred
1027      * size of the value it represents.
1028      */
1029     private class TreeStateNode extends DefaultMutableTreeNode {
1030         /** Preferred size needed to draw the user object. */
1031         protected int             preferredWidth;
1032         protected int             preferredHeight;
1033 
1034         /** X location that the user object will be drawn at. */
1035         protected int             xOrigin;
1036 
1037         /** Y location that the user object will be drawn at. */
1038         protected int             yOrigin;
1039 
1040         /** Is this node currently expanded? */
1041         protected boolean         expanded;
1042 
1043         /** Has this node been expanded at least once? */
1044         protected boolean         hasBeenExpanded;
1045 
1046         /** Path of this node. */
1047         protected TreePath        path;
1048 
1049 
1050         public TreeStateNode(Object value) {
1051             super(value);
1052         }
1053 
1054         //
1055         // Overriden DefaultMutableTreeNode methods
1056         //
1057 
1058         /**
1059          * Messaged when this node is added somewhere, resets the path
1060          * and adds a mapping from path to this node.
1061          */
1062         public void setParent(MutableTreeNode parent) {
1063             super.setParent(parent);
1064             if(parent != null) {
1065                 path = ((TreeStateNode)parent).getTreePath().
1066                                        pathByAddingChild(getUserObject());
1067                 addMapping(this);
1068             }
1069         }
1070 
1071         /**
1072          * Messaged when this node is removed from its parent, this messages
1073          * <code>removedFromMapping</code> to remove all the children.
1074          */
1075         public void remove(int childIndex) {
1076             TreeStateNode     node = (TreeStateNode)getChildAt(childIndex);
1077 
1078             node.removeFromMapping();
1079             super.remove(childIndex);
1080         }
1081 
1082         /**
1083          * Messaged to set the user object. This resets the path.
1084          */
1085         public void setUserObject(Object o) {
1086             super.setUserObject(o);
1087             if(path != null) {
1088                 TreeStateNode      parent = (TreeStateNode)getParent();
1089 
1090                 if(parent != null)
1091                     resetChildrenPaths(parent.getTreePath());
1092                 else
1093                     resetChildrenPaths(null);
1094             }
1095         }
1096 
1097         /**
1098          * Returns the children of the receiver.
1099          * If the receiver is not currently expanded, this will return an
1100          * empty enumeration.
1101          */
1102         @Override
1103         public Enumeration<TreeNode> children() {
1104             if (!this.isExpanded()) {
1105                 return DefaultMutableTreeNode.EMPTY_ENUMERATION;
1106             } else {
1107                 return super.children();
1108             }
1109         }
1110 
1111         /**
1112          * Returns true if the receiver is a leaf.
1113          */
1114         public boolean isLeaf() {
1115             return getModel().isLeaf(this.getValue());
1116         }
1117 
1118         //
1119         // VariableHeightLayoutCache
1120         //
1121 
1122         /**
1123          * Returns the location and size of this node.
1124          */
1125         public Rectangle getNodeBounds(Rectangle placeIn) {
1126             if(placeIn == null)
1127                 placeIn = new Rectangle(getXOrigin(), getYOrigin(),
1128                                         getPreferredWidth(),
1129                                         getPreferredHeight());
1130             else {
1131                 placeIn.x = getXOrigin();
1132                 placeIn.y = getYOrigin();
1133                 placeIn.width = getPreferredWidth();
1134                 placeIn.height = getPreferredHeight();
1135             }
1136             return placeIn;
1137         }
1138 
1139         /**
1140          * @return x location to draw node at.
1141          */
1142         public int getXOrigin() {
1143             if(!hasValidSize())
1144                 updatePreferredSize(getRow());
1145             return xOrigin;
1146         }
1147 
1148         /**
1149          * Returns the y origin the user object will be drawn at.
1150          */
1151         public int getYOrigin() {
1152             if(isFixedRowHeight()) {
1153                 int      aRow = getRow();
1154 
1155                 if(aRow == -1)
1156                     return -1;
1157                 return getRowHeight() * aRow;
1158             }
1159             return yOrigin;
1160         }
1161 
1162         /**
1163          * Returns the preferred height of the receiver.
1164          */
1165         public int getPreferredHeight() {
1166             if(isFixedRowHeight())
1167                 return getRowHeight();
1168             else if(!hasValidSize())
1169                 updatePreferredSize(getRow());
1170             return preferredHeight;
1171         }
1172 
1173         /**
1174          * Returns the preferred width of the receiver.
1175          */
1176         public int getPreferredWidth() {
1177             if(!hasValidSize())
1178                 updatePreferredSize(getRow());
1179             return preferredWidth;
1180         }
1181 
1182         /**
1183          * Returns true if this node has a valid size.
1184          */
1185         public boolean hasValidSize() {
1186             return (preferredHeight != 0);
1187         }
1188 
1189         /**
1190          * Returns the row of the receiver.
1191          */
1192         public int getRow() {
1193             return visibleNodes.indexOf(this);
1194         }
1195 
1196         /**
1197          * Returns true if this node has been expanded at least once.
1198          */
1199         public boolean hasBeenExpanded() {
1200             return hasBeenExpanded;
1201         }
1202 
1203         /**
1204          * Returns true if the receiver has been expanded.
1205          */
1206         public boolean isExpanded() {
1207             return expanded;
1208         }
1209 
1210         /**
1211          * Returns the last visible node that is a child of this
1212          * instance.
1213          */
1214         public TreeStateNode getLastVisibleNode() {
1215             TreeStateNode                node = this;
1216 
1217             while(node.isExpanded() && node.getChildCount() > 0)
1218                 node = (TreeStateNode)node.getLastChild();
1219             return node;
1220         }
1221 
1222         /**
1223          * Returns true if the receiver is currently visible.
1224          */
1225         public boolean isVisible() {
1226             if(this == root)
1227                 return true;
1228 
1229             TreeStateNode        parent = (TreeStateNode)getParent();
1230 
1231             return (parent != null && parent.isExpanded() &&
1232                     parent.isVisible());
1233         }
1234 
1235         /**
1236          * Returns the number of children this will have. If the children
1237          * have not yet been loaded, this messages the model.
1238          */
1239         public int getModelChildCount() {
1240             if(hasBeenExpanded)
1241                 return super.getChildCount();
1242             return getModel().getChildCount(getValue());
1243         }
1244 
1245         /**
1246          * Returns the number of visible children, that is the number of
1247          * children that are expanded, or leafs.
1248          */
1249         public int getVisibleChildCount() {
1250             int               childCount = 0;
1251 
1252             if(isExpanded()) {
1253                 int         maxCounter = getChildCount();
1254 
1255                 childCount += maxCounter;
1256                 for(int counter = 0; counter < maxCounter; counter++)
1257                     childCount += ((TreeStateNode)getChildAt(counter)).
1258                                     getVisibleChildCount();
1259             }
1260             return childCount;
1261         }
1262 
1263         /**
1264          * Toggles the receiver between expanded and collapsed.
1265          */
1266         public void toggleExpanded() {
1267             if (isExpanded()) {
1268                 collapse();
1269             } else {
1270                 expand();
1271             }
1272         }
1273 
1274         /**
1275          * Makes the receiver visible, but invoking
1276          * <code>expandParentAndReceiver</code> on the superclass.
1277          */
1278         public void makeVisible() {
1279             TreeStateNode       parent = (TreeStateNode)getParent();
1280 
1281             if(parent != null)
1282                 parent.expandParentAndReceiver();
1283         }
1284 
1285         /**
1286          * Expands the receiver.
1287          */
1288         public void expand() {
1289             expand(true);
1290         }
1291 
1292         /**
1293          * Collapses the receiver.
1294          */
1295         public void collapse() {
1296             collapse(true);
1297         }
1298 
1299         /**
1300          * Returns the value the receiver is representing. This is a cover
1301          * for getUserObject.
1302          */
1303         public Object getValue() {
1304             return getUserObject();
1305         }
1306 
1307         /**
1308          * Returns a TreePath instance for this node.
1309          */
1310         public TreePath getTreePath() {
1311             return path;
1312         }
1313 
1314         //
1315         // Local methods
1316         //
1317 
1318         /**
1319          * Recreates the receivers path, and all its children's paths.
1320          */
1321         protected void resetChildrenPaths(TreePath parentPath) {
1322             removeMapping(this);
1323             if(parentPath == null)
1324                 path = new TreePath(getUserObject());
1325             else
1326                 path = parentPath.pathByAddingChild(getUserObject());
1327             addMapping(this);
1328             for(int counter = getChildCount() - 1; counter >= 0; counter--)
1329                 ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
1330         }
1331 
1332         /**
1333          * Sets y origin the user object will be drawn at to
1334          * <I>newYOrigin</I>.
1335          */
1336         protected void setYOrigin(int newYOrigin) {
1337             yOrigin = newYOrigin;
1338         }
1339 
1340         /**
1341          * Shifts the y origin by <code>offset</code>.
1342          */
1343         protected void shiftYOriginBy(int offset) {
1344             yOrigin += offset;
1345         }
1346 
1347         /**
1348          * Updates the receivers preferredSize by invoking
1349          * <code>updatePreferredSize</code> with an argument of -1.
1350          */
1351         protected void updatePreferredSize() {
1352             updatePreferredSize(getRow());
1353         }
1354 
1355         /**
1356          * Updates the preferred size by asking the current renderer
1357          * for the Dimension needed to draw the user object this
1358          * instance represents.
1359          */
1360         protected void updatePreferredSize(int index) {
1361             Rectangle       bounds = getNodeDimensions(this.getUserObject(),
1362                                                        index, getLevel(),
1363                                                        isExpanded(),
1364                                                        boundsBuffer);
1365 
1366             if(bounds == null) {
1367                 xOrigin = 0;
1368                 preferredWidth = preferredHeight = 0;
1369                 updateNodeSizes = true;
1370             }
1371             else if(bounds.height == 0) {
1372                 xOrigin = 0;
1373                 preferredWidth = preferredHeight = 0;
1374                 updateNodeSizes = true;
1375             }
1376             else {
1377                 xOrigin = bounds.x;
1378                 preferredWidth = bounds.width;
1379                 if(isFixedRowHeight())
1380                     preferredHeight = getRowHeight();
1381                 else
1382                     preferredHeight = bounds.height;
1383             }
1384         }
1385 
1386         /**
1387          * Marks the receivers size as invalid. Next time the size, location
1388          * is asked for it will be obtained.
1389          */
1390         protected void markSizeInvalid() {
1391             preferredHeight = 0;
1392         }
1393 
1394         /**
1395          * Marks the receivers size, and all its descendants sizes, as invalid.
1396          */
1397         protected void deepMarkSizeInvalid() {
1398             markSizeInvalid();
1399             for(int counter = getChildCount() - 1; counter >= 0; counter--)
1400                 ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
1401         }
1402 
1403         /**
1404          * Returns the children of the receiver. If the children haven't
1405          * been loaded from the model and
1406          * <code>createIfNeeded</code> is true, the children are first
1407          * loaded.
1408          */
1409         protected Enumeration<TreeNode> getLoadedChildren(boolean createIfNeeded) {
1410             if(!createIfNeeded || hasBeenExpanded)
1411                 return super.children();
1412 
1413             TreeStateNode   newNode;
1414             Object          realNode = getValue();
1415             TreeModel       treeModel = getModel();
1416             int             count = treeModel.getChildCount(realNode);
1417 
1418             hasBeenExpanded = true;
1419 
1420             int    childRow = getRow();
1421 
1422             if(childRow == -1) {
1423                 for (int i = 0; i < count; i++) {
1424                     newNode = createNodeForValue
1425                         (treeModel.getChild(realNode, i));
1426                     this.add(newNode);
1427                     newNode.updatePreferredSize(-1);
1428                 }
1429             }
1430             else {
1431                 childRow++;
1432                 for (int i = 0; i < count; i++) {
1433                     newNode = createNodeForValue
1434                         (treeModel.getChild(realNode, i));
1435                     this.add(newNode);
1436                     newNode.updatePreferredSize(childRow++);
1437                 }
1438             }
1439             return super.children();
1440         }
1441 
1442         /**
1443          * Messaged from expand and collapse. This is meant for subclassers
1444          * that may wish to do something interesting with this.
1445          */
1446         protected void didAdjustTree() {
1447         }
1448 
1449         /**
1450          * Invokes <code>expandParentAndReceiver</code> on the parent,
1451          * and expands the receiver.
1452          */
1453         protected void expandParentAndReceiver() {
1454             TreeStateNode       parent = (TreeStateNode)getParent();
1455 
1456             if(parent != null)
1457                 parent.expandParentAndReceiver();
1458             expand();
1459         }
1460 
1461         /**
1462          * Expands this node in the tree.  This will load the children
1463          * from the treeModel if this node has not previously been
1464          * expanded.  If <I>adjustTree</I> is true the tree and selection
1465          * are updated accordingly.
1466          */
1467         protected void expand(boolean adjustTree) {
1468             if (!isExpanded() && !isLeaf()) {
1469                 boolean         isFixed = isFixedRowHeight();
1470                 int             startHeight = getPreferredHeight();
1471                 int             originalRow = getRow();
1472 
1473                 expanded = true;
1474                 updatePreferredSize(originalRow);
1475 
1476                 if (!hasBeenExpanded) {
1477                     TreeStateNode  newNode;
1478                     Object         realNode = getValue();
1479                     TreeModel      treeModel = getModel();
1480                     int            count = treeModel.getChildCount(realNode);
1481 
1482                     hasBeenExpanded = true;
1483                     if(originalRow == -1) {
1484                         for (int i = 0; i < count; i++) {
1485                             newNode = createNodeForValue(treeModel.getChild
1486                                                             (realNode, i));
1487                             this.add(newNode);
1488                             newNode.updatePreferredSize(-1);
1489                         }
1490                     }
1491                     else {
1492                         int offset = originalRow + 1;
1493                         for (int i = 0; i < count; i++) {
1494                             newNode = createNodeForValue(treeModel.getChild
1495                                                        (realNode, i));
1496                             this.add(newNode);
1497                             newNode.updatePreferredSize(offset);
1498                         }
1499                     }
1500                 }
1501 
1502                 int i = originalRow;
1503                 Enumeration<TreeNode> cursor = preorderEnumeration();
1504                 cursor.nextElement(); // don't add me, I'm already in
1505 
1506                 int newYOrigin;
1507 
1508                 if(isFixed)
1509                     newYOrigin = 0;
1510                 else if(this == root && !isRootVisible())
1511                     newYOrigin = 0;
1512                 else
1513                     newYOrigin = getYOrigin() + this.getPreferredHeight();
1514                 TreeStateNode   aNode;
1515                 if(!isFixed) {
1516                     while (cursor.hasMoreElements()) {
1517                         aNode = (TreeStateNode) cursor.nextElement();
1518                         if(!updateNodeSizes && !aNode.hasValidSize())
1519                             aNode.updatePreferredSize(i + 1);
1520                         aNode.setYOrigin(newYOrigin);
1521                         newYOrigin += aNode.getPreferredHeight();
1522                         visibleNodes.insertElementAt(aNode, ++i);
1523                     }
1524                 }
1525                 else {
1526                     while (cursor.hasMoreElements()) {
1527                         aNode = (TreeStateNode) cursor.nextElement();
1528                         visibleNodes.insertElementAt(aNode, ++i);
1529                     }
1530                 }
1531 
1532                 if(adjustTree && (originalRow != i ||
1533                                   getPreferredHeight() != startHeight)) {
1534                     // Adjust the Y origin of any nodes following this row.
1535                     if(!isFixed && ++i < getRowCount()) {
1536                         int              counter;
1537                         int              heightDiff = newYOrigin -
1538                             (getYOrigin() + getPreferredHeight()) +
1539                             (getPreferredHeight() - startHeight);
1540 
1541                         for(counter = visibleNodes.size() - 1;counter >= i;
1542                             counter--)
1543                             ((TreeStateNode)visibleNodes.elementAt(counter)).
1544                                 shiftYOriginBy(heightDiff);
1545                     }
1546                     didAdjustTree();
1547                     visibleNodesChanged();
1548                 }
1549 
1550                 // Update the rows in the selection
1551                 if(treeSelectionModel != null) {
1552                     treeSelectionModel.resetRowSelection();
1553                 }
1554             }
1555         }
1556 
1557         /**
1558          * Collapses this node in the tree.  If <I>adjustTree</I> is
1559          * true the tree and selection are updated accordingly.
1560          */
1561         protected void collapse(boolean adjustTree) {
1562             if (isExpanded()) {
1563                 Enumeration<TreeNode> cursor = preorderEnumeration();
1564                 cursor.nextElement(); // don't remove me, I'm still visible
1565                 int rowsDeleted = 0;
1566                 boolean isFixed = isFixedRowHeight();
1567                 int lastYEnd;
1568                 if(isFixed)
1569                     lastYEnd = 0;
1570                 else
1571                     lastYEnd = getPreferredHeight() + getYOrigin();
1572                 int startHeight = getPreferredHeight();
1573                 int startYEnd = lastYEnd;
1574                 int myRow = getRow();
1575 
1576                 if(!isFixed) {
1577                     while(cursor.hasMoreElements()) {
1578                         TreeStateNode node = (TreeStateNode)cursor.
1579                             nextElement();
1580                         if (node.isVisible()) {
1581                             rowsDeleted++;
1582                             //visibleNodes.removeElement(node);
1583                             lastYEnd = node.getYOrigin() +
1584                                 node.getPreferredHeight();
1585                         }
1586                     }
1587                 }
1588                 else {
1589                     while(cursor.hasMoreElements()) {
1590                         TreeStateNode node = (TreeStateNode)cursor.
1591                             nextElement();
1592                         if (node.isVisible()) {
1593                             rowsDeleted++;
1594                             //visibleNodes.removeElement(node);
1595                         }
1596                     }
1597                 }
1598 
1599                 // Clean up the visible nodes.
1600                 for (int counter = rowsDeleted + myRow; counter > myRow;
1601                      counter--) {
1602                     visibleNodes.removeElementAt(counter);
1603                 }
1604 
1605                 expanded = false;
1606 
1607                 if(myRow == -1)
1608                     markSizeInvalid();
1609                 else if (adjustTree)
1610                     updatePreferredSize(myRow);
1611 
1612                 if(myRow != -1 && adjustTree &&
1613                    (rowsDeleted > 0 || startHeight != getPreferredHeight())) {
1614                     // Adjust the Y origin of any rows following this one.
1615                     startYEnd += (getPreferredHeight() - startHeight);
1616                     if(!isFixed && (myRow + 1) < getRowCount() &&
1617                        startYEnd != lastYEnd) {
1618                         int                 counter, maxCounter, shiftAmount;
1619 
1620                         shiftAmount = startYEnd - lastYEnd;
1621                         for(counter = myRow + 1, maxCounter =
1622                                 visibleNodes.size();
1623                             counter < maxCounter;counter++)
1624                             ((TreeStateNode)visibleNodes.elementAt(counter))
1625                                 .shiftYOriginBy(shiftAmount);
1626                     }
1627                     didAdjustTree();
1628                     visibleNodesChanged();
1629                 }
1630                 if(treeSelectionModel != null && rowsDeleted > 0 &&
1631                    myRow != -1) {
1632                     treeSelectionModel.resetRowSelection();
1633                 }
1634             }
1635         }
1636 
1637         /**
1638          * Removes the receiver, and all its children, from the mapping
1639          * table.
1640          */
1641         protected void removeFromMapping() {
1642             if(path != null) {
1643                 removeMapping(this);
1644                 for(int counter = getChildCount() - 1; counter >= 0; counter--)
1645                     ((TreeStateNode)getChildAt(counter)).removeFromMapping();
1646             }
1647         }
1648     } // End of VariableHeightLayoutCache.TreeStateNode
1649 
1650 
1651     /**
1652      * An enumerator to iterate through visible nodes.
1653      */
1654     private class VisibleTreeStateNodeEnumeration implements
1655                      Enumeration<TreePath> {
1656         /** Parent thats children are being enumerated. */
1657         protected TreeStateNode       parent;
1658         /** Index of next child. An index of -1 signifies parent should be
1659          * visibled next. */
1660         protected int                 nextIndex;
1661         /** Number of children in parent. */
1662         protected int                 childCount;
1663 
1664         protected VisibleTreeStateNodeEnumeration(TreeStateNode node) {
1665             this(node, -1);
1666         }
1667 
1668         protected VisibleTreeStateNodeEnumeration(TreeStateNode parent,
1669                                                   int startIndex) {
1670             this.parent = parent;
1671             this.nextIndex = startIndex;
1672             this.childCount = this.parent.getChildCount();
1673         }
1674 
1675         /**
1676          * @return true if more visible nodes.
1677          */
1678         public boolean hasMoreElements() {
1679             return (parent != null);
1680         }
1681 
1682         /**
1683          * @return next visible TreePath.
1684          */
1685         public TreePath nextElement() {
1686             if(!hasMoreElements())
1687                 throw new NoSuchElementException("No more visible paths");
1688 
1689             TreePath                retObject;
1690 
1691             if(nextIndex == -1) {
1692                 retObject = parent.getTreePath();
1693             }
1694             else {
1695                 TreeStateNode   node = (TreeStateNode)parent.
1696                                         getChildAt(nextIndex);
1697 
1698                 retObject = node.getTreePath();
1699             }
1700             updateNextObject();
1701             return retObject;
1702         }
1703 
1704         /**
1705          * Determines the next object by invoking <code>updateNextIndex</code>
1706          * and if not succesful <code>findNextValidParent</code>.
1707          */
1708         protected void updateNextObject() {
1709             if(!updateNextIndex()) {
1710                 findNextValidParent();
1711             }
1712         }
1713 
1714         /**
1715          * Finds the next valid parent, this should be called when nextIndex
1716          * is beyond the number of children of the current parent.
1717          */
1718         protected boolean findNextValidParent() {
1719             if(parent == root) {
1720                 // mark as invalid!
1721                 parent = null;
1722                 return false;
1723             }
1724             while(parent != null) {
1725                 TreeStateNode      newParent = (TreeStateNode)parent.
1726                                                   getParent();
1727 
1728                 if(newParent != null) {
1729                     nextIndex = newParent.getIndex(parent);
1730                     parent = newParent;
1731                     childCount = parent.getChildCount();
1732                     if(updateNextIndex())
1733                         return true;
1734                 }
1735                 else
1736                     parent = null;
1737             }
1738             return false;
1739         }
1740 
1741         /**
1742          * Updates <code>nextIndex</code> returning false if it is beyond
1743          * the number of children of parent.
1744          */
1745         protected boolean updateNextIndex() {
1746             // nextIndex == -1 identifies receiver, make sure is expanded
1747             // before descend.
1748             if(nextIndex == -1 && !parent.isExpanded())
1749                 return false;
1750 
1751             // Check that it can have kids
1752             if(childCount == 0)
1753                 return false;
1754             // Make sure next index not beyond child count.
1755             else if(++nextIndex >= childCount)
1756                 return false;
1757 
1758             TreeStateNode       child = (TreeStateNode)parent.
1759                                         getChildAt(nextIndex);
1760 
1761             if(child != null && child.isExpanded()) {
1762                 parent = child;
1763                 nextIndex = -1;
1764                 childCount = child.getChildCount();
1765             }
1766             return true;
1767         }
1768     } // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
1769 }