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