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