Home » openjdk-7 » javax » swing » tree » [javadoc | source]

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

Home » openjdk-7 » javax » swing » tree » [javadoc | source]