src/java.desktop/share/classes/javax/swing/tree/VariableHeightLayoutCache.java
changeset 47216 71c04702a3d5
parent 33253 78e735319356
child 48639 96ef7a0cf0b1
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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 package javax.swing.tree;
       
    26 
       
    27 import javax.swing.event.TreeModelEvent;
       
    28 import java.awt.Rectangle;
       
    29 import java.beans.BeanProperty;
       
    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      */
       
   107     @BeanProperty(description
       
   108             = "The TreeModel that will provide the data.")
       
   109     public void setModel(TreeModel newModel) {
       
   110         super.setModel(newModel);
       
   111         rebuild(false);
       
   112     }
       
   113 
       
   114     /**
       
   115      * Determines whether or not the root node from
       
   116      * the <code>TreeModel</code> is visible.
       
   117      *
       
   118      * @param rootVisible true if the root node of the tree is to be displayed
       
   119      * @see #rootVisible
       
   120      */
       
   121     @BeanProperty(description
       
   122             = "Whether or not the root node from the TreeModel is visible.")
       
   123     public void setRootVisible(boolean rootVisible) {
       
   124         if(isRootVisible() != rootVisible && root != null) {
       
   125             if(rootVisible) {
       
   126                 root.updatePreferredSize(0);
       
   127                 visibleNodes.insertElementAt(root, 0);
       
   128             }
       
   129             else if(visibleNodes.size() > 0) {
       
   130                 visibleNodes.removeElementAt(0);
       
   131                 if(treeSelectionModel != null)
       
   132                     treeSelectionModel.removeSelectionPath
       
   133                         (root.getTreePath());
       
   134             }
       
   135             if(treeSelectionModel != null)
       
   136                 treeSelectionModel.resetRowSelection();
       
   137             if(getRowCount() > 0)
       
   138                 getNode(0).setYOrigin(0);
       
   139             updateYLocationsFrom(0);
       
   140             visibleNodesChanged();
       
   141         }
       
   142         super.setRootVisible(rootVisible);
       
   143     }
       
   144 
       
   145     /**
       
   146      * Sets the height of each cell.  If the specified value
       
   147      * is less than or equal to zero the current cell renderer is
       
   148      * queried for each row's height.
       
   149      *
       
   150      * @param rowHeight the height of each cell, in pixels
       
   151      */
       
   152     @BeanProperty(description
       
   153             = "The height of each cell.")
       
   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(SwingUtilities2.getTreePath(e, getModel()), 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(SwingUtilities2.getTreePath(e, getModel()), 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(SwingUtilities2.getTreePath(e, getModel()), 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 = SwingUtilities2.getTreePath(e, getModel());
       
   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         @Override
       
  1102         public Enumeration<TreeNode> children() {
       
  1103             if (!this.isExpanded()) {
       
  1104                 return DefaultMutableTreeNode.EMPTY_ENUMERATION;
       
  1105             } else {
       
  1106                 return super.children();
       
  1107             }
       
  1108         }
       
  1109 
       
  1110         /**
       
  1111          * Returns true if the receiver is a leaf.
       
  1112          */
       
  1113         public boolean isLeaf() {
       
  1114             return getModel().isLeaf(this.getValue());
       
  1115         }
       
  1116 
       
  1117         //
       
  1118         // VariableHeightLayoutCache
       
  1119         //
       
  1120 
       
  1121         /**
       
  1122          * Returns the location and size of this node.
       
  1123          */
       
  1124         public Rectangle getNodeBounds(Rectangle placeIn) {
       
  1125             if(placeIn == null)
       
  1126                 placeIn = new Rectangle(getXOrigin(), getYOrigin(),
       
  1127                                         getPreferredWidth(),
       
  1128                                         getPreferredHeight());
       
  1129             else {
       
  1130                 placeIn.x = getXOrigin();
       
  1131                 placeIn.y = getYOrigin();
       
  1132                 placeIn.width = getPreferredWidth();
       
  1133                 placeIn.height = getPreferredHeight();
       
  1134             }
       
  1135             return placeIn;
       
  1136         }
       
  1137 
       
  1138         /**
       
  1139          * @return x location to draw node at.
       
  1140          */
       
  1141         public int getXOrigin() {
       
  1142             if(!hasValidSize())
       
  1143                 updatePreferredSize(getRow());
       
  1144             return xOrigin;
       
  1145         }
       
  1146 
       
  1147         /**
       
  1148          * Returns the y origin the user object will be drawn at.
       
  1149          */
       
  1150         public int getYOrigin() {
       
  1151             if(isFixedRowHeight()) {
       
  1152                 int      aRow = getRow();
       
  1153 
       
  1154                 if(aRow == -1)
       
  1155                     return -1;
       
  1156                 return getRowHeight() * aRow;
       
  1157             }
       
  1158             return yOrigin;
       
  1159         }
       
  1160 
       
  1161         /**
       
  1162          * Returns the preferred height of the receiver.
       
  1163          */
       
  1164         public int getPreferredHeight() {
       
  1165             if(isFixedRowHeight())
       
  1166                 return getRowHeight();
       
  1167             else if(!hasValidSize())
       
  1168                 updatePreferredSize(getRow());
       
  1169             return preferredHeight;
       
  1170         }
       
  1171 
       
  1172         /**
       
  1173          * Returns the preferred width of the receiver.
       
  1174          */
       
  1175         public int getPreferredWidth() {
       
  1176             if(!hasValidSize())
       
  1177                 updatePreferredSize(getRow());
       
  1178             return preferredWidth;
       
  1179         }
       
  1180 
       
  1181         /**
       
  1182          * Returns true if this node has a valid size.
       
  1183          */
       
  1184         public boolean hasValidSize() {
       
  1185             return (preferredHeight != 0);
       
  1186         }
       
  1187 
       
  1188         /**
       
  1189          * Returns the row of the receiver.
       
  1190          */
       
  1191         public int getRow() {
       
  1192             return visibleNodes.indexOf(this);
       
  1193         }
       
  1194 
       
  1195         /**
       
  1196          * Returns true if this node has been expanded at least once.
       
  1197          */
       
  1198         public boolean hasBeenExpanded() {
       
  1199             return hasBeenExpanded;
       
  1200         }
       
  1201 
       
  1202         /**
       
  1203          * Returns true if the receiver has been expanded.
       
  1204          */
       
  1205         public boolean isExpanded() {
       
  1206             return expanded;
       
  1207         }
       
  1208 
       
  1209         /**
       
  1210          * Returns the last visible node that is a child of this
       
  1211          * instance.
       
  1212          */
       
  1213         public TreeStateNode getLastVisibleNode() {
       
  1214             TreeStateNode                node = this;
       
  1215 
       
  1216             while(node.isExpanded() && node.getChildCount() > 0)
       
  1217                 node = (TreeStateNode)node.getLastChild();
       
  1218             return node;
       
  1219         }
       
  1220 
       
  1221         /**
       
  1222          * Returns true if the receiver is currently visible.
       
  1223          */
       
  1224         public boolean isVisible() {
       
  1225             if(this == root)
       
  1226                 return true;
       
  1227 
       
  1228             TreeStateNode        parent = (TreeStateNode)getParent();
       
  1229 
       
  1230             return (parent != null && parent.isExpanded() &&
       
  1231                     parent.isVisible());
       
  1232         }
       
  1233 
       
  1234         /**
       
  1235          * Returns the number of children this will have. If the children
       
  1236          * have not yet been loaded, this messages the model.
       
  1237          */
       
  1238         public int getModelChildCount() {
       
  1239             if(hasBeenExpanded)
       
  1240                 return super.getChildCount();
       
  1241             return getModel().getChildCount(getValue());
       
  1242         }
       
  1243 
       
  1244         /**
       
  1245          * Returns the number of visible children, that is the number of
       
  1246          * children that are expanded, or leafs.
       
  1247          */
       
  1248         public int getVisibleChildCount() {
       
  1249             int               childCount = 0;
       
  1250 
       
  1251             if(isExpanded()) {
       
  1252                 int         maxCounter = getChildCount();
       
  1253 
       
  1254                 childCount += maxCounter;
       
  1255                 for(int counter = 0; counter < maxCounter; counter++)
       
  1256                     childCount += ((TreeStateNode)getChildAt(counter)).
       
  1257                                     getVisibleChildCount();
       
  1258             }
       
  1259             return childCount;
       
  1260         }
       
  1261 
       
  1262         /**
       
  1263          * Toggles the receiver between expanded and collapsed.
       
  1264          */
       
  1265         public void toggleExpanded() {
       
  1266             if (isExpanded()) {
       
  1267                 collapse();
       
  1268             } else {
       
  1269                 expand();
       
  1270             }
       
  1271         }
       
  1272 
       
  1273         /**
       
  1274          * Makes the receiver visible, but invoking
       
  1275          * <code>expandParentAndReceiver</code> on the superclass.
       
  1276          */
       
  1277         public void makeVisible() {
       
  1278             TreeStateNode       parent = (TreeStateNode)getParent();
       
  1279 
       
  1280             if(parent != null)
       
  1281                 parent.expandParentAndReceiver();
       
  1282         }
       
  1283 
       
  1284         /**
       
  1285          * Expands the receiver.
       
  1286          */
       
  1287         public void expand() {
       
  1288             expand(true);
       
  1289         }
       
  1290 
       
  1291         /**
       
  1292          * Collapses the receiver.
       
  1293          */
       
  1294         public void collapse() {
       
  1295             collapse(true);
       
  1296         }
       
  1297 
       
  1298         /**
       
  1299          * Returns the value the receiver is representing. This is a cover
       
  1300          * for getUserObject.
       
  1301          */
       
  1302         public Object getValue() {
       
  1303             return getUserObject();
       
  1304         }
       
  1305 
       
  1306         /**
       
  1307          * Returns a TreePath instance for this node.
       
  1308          */
       
  1309         public TreePath getTreePath() {
       
  1310             return path;
       
  1311         }
       
  1312 
       
  1313         //
       
  1314         // Local methods
       
  1315         //
       
  1316 
       
  1317         /**
       
  1318          * Recreates the receivers path, and all its children's paths.
       
  1319          */
       
  1320         protected void resetChildrenPaths(TreePath parentPath) {
       
  1321             removeMapping(this);
       
  1322             if(parentPath == null)
       
  1323                 path = new TreePath(getUserObject());
       
  1324             else
       
  1325                 path = parentPath.pathByAddingChild(getUserObject());
       
  1326             addMapping(this);
       
  1327             for(int counter = getChildCount() - 1; counter >= 0; counter--)
       
  1328                 ((TreeStateNode)getChildAt(counter)).resetChildrenPaths(path);
       
  1329         }
       
  1330 
       
  1331         /**
       
  1332          * Sets y origin the user object will be drawn at to
       
  1333          * <I>newYOrigin</I>.
       
  1334          */
       
  1335         protected void setYOrigin(int newYOrigin) {
       
  1336             yOrigin = newYOrigin;
       
  1337         }
       
  1338 
       
  1339         /**
       
  1340          * Shifts the y origin by <code>offset</code>.
       
  1341          */
       
  1342         protected void shiftYOriginBy(int offset) {
       
  1343             yOrigin += offset;
       
  1344         }
       
  1345 
       
  1346         /**
       
  1347          * Updates the receivers preferredSize by invoking
       
  1348          * <code>updatePreferredSize</code> with an argument of -1.
       
  1349          */
       
  1350         protected void updatePreferredSize() {
       
  1351             updatePreferredSize(getRow());
       
  1352         }
       
  1353 
       
  1354         /**
       
  1355          * Updates the preferred size by asking the current renderer
       
  1356          * for the Dimension needed to draw the user object this
       
  1357          * instance represents.
       
  1358          */
       
  1359         protected void updatePreferredSize(int index) {
       
  1360             Rectangle       bounds = getNodeDimensions(this.getUserObject(),
       
  1361                                                        index, getLevel(),
       
  1362                                                        isExpanded(),
       
  1363                                                        boundsBuffer);
       
  1364 
       
  1365             if(bounds == null) {
       
  1366                 xOrigin = 0;
       
  1367                 preferredWidth = preferredHeight = 0;
       
  1368                 updateNodeSizes = true;
       
  1369             }
       
  1370             else if(bounds.height == 0) {
       
  1371                 xOrigin = 0;
       
  1372                 preferredWidth = preferredHeight = 0;
       
  1373                 updateNodeSizes = true;
       
  1374             }
       
  1375             else {
       
  1376                 xOrigin = bounds.x;
       
  1377                 preferredWidth = bounds.width;
       
  1378                 if(isFixedRowHeight())
       
  1379                     preferredHeight = getRowHeight();
       
  1380                 else
       
  1381                     preferredHeight = bounds.height;
       
  1382             }
       
  1383         }
       
  1384 
       
  1385         /**
       
  1386          * Marks the receivers size as invalid. Next time the size, location
       
  1387          * is asked for it will be obtained.
       
  1388          */
       
  1389         protected void markSizeInvalid() {
       
  1390             preferredHeight = 0;
       
  1391         }
       
  1392 
       
  1393         /**
       
  1394          * Marks the receivers size, and all its descendants sizes, as invalid.
       
  1395          */
       
  1396         protected void deepMarkSizeInvalid() {
       
  1397             markSizeInvalid();
       
  1398             for(int counter = getChildCount() - 1; counter >= 0; counter--)
       
  1399                 ((TreeStateNode)getChildAt(counter)).deepMarkSizeInvalid();
       
  1400         }
       
  1401 
       
  1402         /**
       
  1403          * Returns the children of the receiver. If the children haven't
       
  1404          * been loaded from the model and
       
  1405          * <code>createIfNeeded</code> is true, the children are first
       
  1406          * loaded.
       
  1407          */
       
  1408         protected Enumeration<TreeNode> getLoadedChildren(boolean createIfNeeded) {
       
  1409             if(!createIfNeeded || hasBeenExpanded)
       
  1410                 return super.children();
       
  1411 
       
  1412             TreeStateNode   newNode;
       
  1413             Object          realNode = getValue();
       
  1414             TreeModel       treeModel = getModel();
       
  1415             int             count = treeModel.getChildCount(realNode);
       
  1416 
       
  1417             hasBeenExpanded = true;
       
  1418 
       
  1419             int    childRow = getRow();
       
  1420 
       
  1421             if(childRow == -1) {
       
  1422                 for (int i = 0; i < count; i++) {
       
  1423                     newNode = createNodeForValue
       
  1424                         (treeModel.getChild(realNode, i));
       
  1425                     this.add(newNode);
       
  1426                     newNode.updatePreferredSize(-1);
       
  1427                 }
       
  1428             }
       
  1429             else {
       
  1430                 childRow++;
       
  1431                 for (int i = 0; i < count; i++) {
       
  1432                     newNode = createNodeForValue
       
  1433                         (treeModel.getChild(realNode, i));
       
  1434                     this.add(newNode);
       
  1435                     newNode.updatePreferredSize(childRow++);
       
  1436                 }
       
  1437             }
       
  1438             return super.children();
       
  1439         }
       
  1440 
       
  1441         /**
       
  1442          * Messaged from expand and collapse. This is meant for subclassers
       
  1443          * that may wish to do something interesting with this.
       
  1444          */
       
  1445         protected void didAdjustTree() {
       
  1446         }
       
  1447 
       
  1448         /**
       
  1449          * Invokes <code>expandParentAndReceiver</code> on the parent,
       
  1450          * and expands the receiver.
       
  1451          */
       
  1452         protected void expandParentAndReceiver() {
       
  1453             TreeStateNode       parent = (TreeStateNode)getParent();
       
  1454 
       
  1455             if(parent != null)
       
  1456                 parent.expandParentAndReceiver();
       
  1457             expand();
       
  1458         }
       
  1459 
       
  1460         /**
       
  1461          * Expands this node in the tree.  This will load the children
       
  1462          * from the treeModel if this node has not previously been
       
  1463          * expanded.  If <I>adjustTree</I> is true the tree and selection
       
  1464          * are updated accordingly.
       
  1465          */
       
  1466         protected void expand(boolean adjustTree) {
       
  1467             if (!isExpanded() && !isLeaf()) {
       
  1468                 boolean         isFixed = isFixedRowHeight();
       
  1469                 int             startHeight = getPreferredHeight();
       
  1470                 int             originalRow = getRow();
       
  1471 
       
  1472                 expanded = true;
       
  1473                 updatePreferredSize(originalRow);
       
  1474 
       
  1475                 if (!hasBeenExpanded) {
       
  1476                     TreeStateNode  newNode;
       
  1477                     Object         realNode = getValue();
       
  1478                     TreeModel      treeModel = getModel();
       
  1479                     int            count = treeModel.getChildCount(realNode);
       
  1480 
       
  1481                     hasBeenExpanded = true;
       
  1482                     if(originalRow == -1) {
       
  1483                         for (int i = 0; i < count; i++) {
       
  1484                             newNode = createNodeForValue(treeModel.getChild
       
  1485                                                             (realNode, i));
       
  1486                             this.add(newNode);
       
  1487                             newNode.updatePreferredSize(-1);
       
  1488                         }
       
  1489                     }
       
  1490                     else {
       
  1491                         int offset = originalRow + 1;
       
  1492                         for (int i = 0; i < count; i++) {
       
  1493                             newNode = createNodeForValue(treeModel.getChild
       
  1494                                                        (realNode, i));
       
  1495                             this.add(newNode);
       
  1496                             newNode.updatePreferredSize(offset);
       
  1497                         }
       
  1498                     }
       
  1499                 }
       
  1500 
       
  1501                 int i = originalRow;
       
  1502                 Enumeration<TreeNode> cursor = preorderEnumeration();
       
  1503                 cursor.nextElement(); // don't add me, I'm already in
       
  1504 
       
  1505                 int newYOrigin;
       
  1506 
       
  1507                 if(isFixed)
       
  1508                     newYOrigin = 0;
       
  1509                 else if(this == root && !isRootVisible())
       
  1510                     newYOrigin = 0;
       
  1511                 else
       
  1512                     newYOrigin = getYOrigin() + this.getPreferredHeight();
       
  1513                 TreeStateNode   aNode;
       
  1514                 if(!isFixed) {
       
  1515                     while (cursor.hasMoreElements()) {
       
  1516                         aNode = (TreeStateNode) cursor.nextElement();
       
  1517                         if(!updateNodeSizes && !aNode.hasValidSize())
       
  1518                             aNode.updatePreferredSize(i + 1);
       
  1519                         aNode.setYOrigin(newYOrigin);
       
  1520                         newYOrigin += aNode.getPreferredHeight();
       
  1521                         visibleNodes.insertElementAt(aNode, ++i);
       
  1522                     }
       
  1523                 }
       
  1524                 else {
       
  1525                     while (cursor.hasMoreElements()) {
       
  1526                         aNode = (TreeStateNode) cursor.nextElement();
       
  1527                         visibleNodes.insertElementAt(aNode, ++i);
       
  1528                     }
       
  1529                 }
       
  1530 
       
  1531                 if(adjustTree && (originalRow != i ||
       
  1532                                   getPreferredHeight() != startHeight)) {
       
  1533                     // Adjust the Y origin of any nodes following this row.
       
  1534                     if(!isFixed && ++i < getRowCount()) {
       
  1535                         int              counter;
       
  1536                         int              heightDiff = newYOrigin -
       
  1537                             (getYOrigin() + getPreferredHeight()) +
       
  1538                             (getPreferredHeight() - startHeight);
       
  1539 
       
  1540                         for(counter = visibleNodes.size() - 1;counter >= i;
       
  1541                             counter--)
       
  1542                             ((TreeStateNode)visibleNodes.elementAt(counter)).
       
  1543                                 shiftYOriginBy(heightDiff);
       
  1544                     }
       
  1545                     didAdjustTree();
       
  1546                     visibleNodesChanged();
       
  1547                 }
       
  1548 
       
  1549                 // Update the rows in the selection
       
  1550                 if(treeSelectionModel != null) {
       
  1551                     treeSelectionModel.resetRowSelection();
       
  1552                 }
       
  1553             }
       
  1554         }
       
  1555 
       
  1556         /**
       
  1557          * Collapses this node in the tree.  If <I>adjustTree</I> is
       
  1558          * true the tree and selection are updated accordingly.
       
  1559          */
       
  1560         protected void collapse(boolean adjustTree) {
       
  1561             if (isExpanded()) {
       
  1562                 Enumeration<TreeNode> cursor = preorderEnumeration();
       
  1563                 cursor.nextElement(); // don't remove me, I'm still visible
       
  1564                 int rowsDeleted = 0;
       
  1565                 boolean isFixed = isFixedRowHeight();
       
  1566                 int lastYEnd;
       
  1567                 if(isFixed)
       
  1568                     lastYEnd = 0;
       
  1569                 else
       
  1570                     lastYEnd = getPreferredHeight() + getYOrigin();
       
  1571                 int startHeight = getPreferredHeight();
       
  1572                 int startYEnd = lastYEnd;
       
  1573                 int myRow = getRow();
       
  1574 
       
  1575                 if(!isFixed) {
       
  1576                     while(cursor.hasMoreElements()) {
       
  1577                         TreeStateNode node = (TreeStateNode)cursor.
       
  1578                             nextElement();
       
  1579                         if (node.isVisible()) {
       
  1580                             rowsDeleted++;
       
  1581                             //visibleNodes.removeElement(node);
       
  1582                             lastYEnd = node.getYOrigin() +
       
  1583                                 node.getPreferredHeight();
       
  1584                         }
       
  1585                     }
       
  1586                 }
       
  1587                 else {
       
  1588                     while(cursor.hasMoreElements()) {
       
  1589                         TreeStateNode node = (TreeStateNode)cursor.
       
  1590                             nextElement();
       
  1591                         if (node.isVisible()) {
       
  1592                             rowsDeleted++;
       
  1593                             //visibleNodes.removeElement(node);
       
  1594                         }
       
  1595                     }
       
  1596                 }
       
  1597 
       
  1598                 // Clean up the visible nodes.
       
  1599                 for (int counter = rowsDeleted + myRow; counter > myRow;
       
  1600                      counter--) {
       
  1601                     visibleNodes.removeElementAt(counter);
       
  1602                 }
       
  1603 
       
  1604                 expanded = false;
       
  1605 
       
  1606                 if(myRow == -1)
       
  1607                     markSizeInvalid();
       
  1608                 else if (adjustTree)
       
  1609                     updatePreferredSize(myRow);
       
  1610 
       
  1611                 if(myRow != -1 && adjustTree &&
       
  1612                    (rowsDeleted > 0 || startHeight != getPreferredHeight())) {
       
  1613                     // Adjust the Y origin of any rows following this one.
       
  1614                     startYEnd += (getPreferredHeight() - startHeight);
       
  1615                     if(!isFixed && (myRow + 1) < getRowCount() &&
       
  1616                        startYEnd != lastYEnd) {
       
  1617                         int                 counter, maxCounter, shiftAmount;
       
  1618 
       
  1619                         shiftAmount = startYEnd - lastYEnd;
       
  1620                         for(counter = myRow + 1, maxCounter =
       
  1621                                 visibleNodes.size();
       
  1622                             counter < maxCounter;counter++)
       
  1623                             ((TreeStateNode)visibleNodes.elementAt(counter))
       
  1624                                 .shiftYOriginBy(shiftAmount);
       
  1625                     }
       
  1626                     didAdjustTree();
       
  1627                     visibleNodesChanged();
       
  1628                 }
       
  1629                 if(treeSelectionModel != null && rowsDeleted > 0 &&
       
  1630                    myRow != -1) {
       
  1631                     treeSelectionModel.resetRowSelection();
       
  1632                 }
       
  1633             }
       
  1634         }
       
  1635 
       
  1636         /**
       
  1637          * Removes the receiver, and all its children, from the mapping
       
  1638          * table.
       
  1639          */
       
  1640         protected void removeFromMapping() {
       
  1641             if(path != null) {
       
  1642                 removeMapping(this);
       
  1643                 for(int counter = getChildCount() - 1; counter >= 0; counter--)
       
  1644                     ((TreeStateNode)getChildAt(counter)).removeFromMapping();
       
  1645             }
       
  1646         }
       
  1647     } // End of VariableHeightLayoutCache.TreeStateNode
       
  1648 
       
  1649 
       
  1650     /**
       
  1651      * An enumerator to iterate through visible nodes.
       
  1652      */
       
  1653     private class VisibleTreeStateNodeEnumeration implements
       
  1654                      Enumeration<TreePath> {
       
  1655         /** Parent thats children are being enumerated. */
       
  1656         protected TreeStateNode       parent;
       
  1657         /** Index of next child. An index of -1 signifies parent should be
       
  1658          * visibled next. */
       
  1659         protected int                 nextIndex;
       
  1660         /** Number of children in parent. */
       
  1661         protected int                 childCount;
       
  1662 
       
  1663         protected VisibleTreeStateNodeEnumeration(TreeStateNode node) {
       
  1664             this(node, -1);
       
  1665         }
       
  1666 
       
  1667         protected VisibleTreeStateNodeEnumeration(TreeStateNode parent,
       
  1668                                                   int startIndex) {
       
  1669             this.parent = parent;
       
  1670             this.nextIndex = startIndex;
       
  1671             this.childCount = this.parent.getChildCount();
       
  1672         }
       
  1673 
       
  1674         /**
       
  1675          * @return true if more visible nodes.
       
  1676          */
       
  1677         public boolean hasMoreElements() {
       
  1678             return (parent != null);
       
  1679         }
       
  1680 
       
  1681         /**
       
  1682          * @return next visible TreePath.
       
  1683          */
       
  1684         public TreePath nextElement() {
       
  1685             if(!hasMoreElements())
       
  1686                 throw new NoSuchElementException("No more visible paths");
       
  1687 
       
  1688             TreePath                retObject;
       
  1689 
       
  1690             if(nextIndex == -1) {
       
  1691                 retObject = parent.getTreePath();
       
  1692             }
       
  1693             else {
       
  1694                 TreeStateNode   node = (TreeStateNode)parent.
       
  1695                                         getChildAt(nextIndex);
       
  1696 
       
  1697                 retObject = node.getTreePath();
       
  1698             }
       
  1699             updateNextObject();
       
  1700             return retObject;
       
  1701         }
       
  1702 
       
  1703         /**
       
  1704          * Determines the next object by invoking <code>updateNextIndex</code>
       
  1705          * and if not succesful <code>findNextValidParent</code>.
       
  1706          */
       
  1707         protected void updateNextObject() {
       
  1708             if(!updateNextIndex()) {
       
  1709                 findNextValidParent();
       
  1710             }
       
  1711         }
       
  1712 
       
  1713         /**
       
  1714          * Finds the next valid parent, this should be called when nextIndex
       
  1715          * is beyond the number of children of the current parent.
       
  1716          */
       
  1717         protected boolean findNextValidParent() {
       
  1718             if(parent == root) {
       
  1719                 // mark as invalid!
       
  1720                 parent = null;
       
  1721                 return false;
       
  1722             }
       
  1723             while(parent != null) {
       
  1724                 TreeStateNode      newParent = (TreeStateNode)parent.
       
  1725                                                   getParent();
       
  1726 
       
  1727                 if(newParent != null) {
       
  1728                     nextIndex = newParent.getIndex(parent);
       
  1729                     parent = newParent;
       
  1730                     childCount = parent.getChildCount();
       
  1731                     if(updateNextIndex())
       
  1732                         return true;
       
  1733                 }
       
  1734                 else
       
  1735                     parent = null;
       
  1736             }
       
  1737             return false;
       
  1738         }
       
  1739 
       
  1740         /**
       
  1741          * Updates <code>nextIndex</code> returning false if it is beyond
       
  1742          * the number of children of parent.
       
  1743          */
       
  1744         protected boolean updateNextIndex() {
       
  1745             // nextIndex == -1 identifies receiver, make sure is expanded
       
  1746             // before descend.
       
  1747             if(nextIndex == -1 && !parent.isExpanded())
       
  1748                 return false;
       
  1749 
       
  1750             // Check that it can have kids
       
  1751             if(childCount == 0)
       
  1752                 return false;
       
  1753             // Make sure next index not beyond child count.
       
  1754             else if(++nextIndex >= childCount)
       
  1755                 return false;
       
  1756 
       
  1757             TreeStateNode       child = (TreeStateNode)parent.
       
  1758                                         getChildAt(nextIndex);
       
  1759 
       
  1760             if(child != null && child.isExpanded()) {
       
  1761                 parent = child;
       
  1762                 nextIndex = -1;
       
  1763                 childCount = child.getChildCount();
       
  1764             }
       
  1765             return true;
       
  1766         }
       
  1767     } // VariableHeightLayoutCache.VisibleTreeStateNodeEnumeration
       
  1768 }